Skip to content

Commit 2c02881

Browse files
authored
feat: make CLI deployment default, gate API behind experimental-api flag (#363)
* docs(track): add cli-default-experimental-api-20260430 Plan to make CLI deployment the default and gate API behind an opt-in experimental-api input. Refs #362. * chore(track): start cli-default-experimental-api-20260430 implementation * feat(config): add experimental-api input with mutual-exclusion validation - Add experimental-api boolean input to action.yml (default: false) - Add experimentalApi field to ActionConfig type - Parse the new input in getActionConfig(); throw on mutual exclusion with vercel-args - Cover defaults, parsing, and mutual-exclusion error in config tests Phase 1 (T001-T004) of cli-default-experimental-api-20260430. Refs #362. * feat(vercel): default routing to CLI; gate API behind experimentalApi flag createVercelClient() now routes to VercelCliClient by default and to VercelApiClient only when config.experimentalApi is true, emitting a core.warning that the API path is experimental and may break across @vercel/client updates. Routing tests rewritten as the four-case matrix from spec AC-1; the mutual-exclusion case is enforced upstream in getActionConfig(). Phase 2 (T005-T007) of cli-default-experimental-api-20260430. Refs #362. * docs: document CLI default with experimental-api opt-in - action.yml: real description for vercel-args; reword scope deprecation message so it no longer ties scope to vercel-args presence - README.md: replace 'Migration to API-based Deployment' section with a new 'Deployment Mode' section covering CLI default, experimental-api opt-in, the experimental warning, mutual-exclusion rule, CLI ↔ API input mapping, API-only inputs, and a migration note for users who relied on the previous v42 API default - README.md: rename 'API Deployment Inputs (New)' to 'Experimental API Deployment Inputs' and gate them on experimental-api: true - README.md: drop 'Deprecated' label on vercel-args / scope; add an experimental-api row to the inputs table Phase 3 + Phase 4 (T008-T010) of cli-default-experimental-api-20260430. Refs #362. * test(integration): add factory routing tests + rebuild dist - Add experimentalApi to integration createConfig helper (compile fix) - Add factory routing tests: default config -> VercelCliClient (no warning); experimental-api: true -> VercelApiClient (with warning containing 'experimental' and '@vercel/client') - Rebuild dist/index.js with the new routing and config validation Phase 5 (T011-T012) of cli-default-experimental-api-20260430. All 244 unit tests + 19 integration tests pass. Refs #362. * docs(track): sync product.md with cli-default-experimental-api * fix(action): remove deprecationMessage from scope input Spec FR-6 requires removing the deprecationMessage from scope; the description retains the vercel-org-id preference guidance, so the input is still discoverable but no longer marked deprecated. CLI is now the standard mode and scope is only relevant there. Refs #362. * refactor(types): encode deployment mode as discriminated union Replace ActionConfig.vercelArgs (string) and ActionConfig.experimentalApi (boolean) with a single discriminated union field: type DeploymentMode = | { kind: 'cli', vercelArgs: string } | { kind: 'experimental-api' } interface ActionConfig { deployment: DeploymentMode // ... } Rationale (from review:review-type-analyzer, confidence 82): the previous two-field shape made the (experimental-api: true, vercel-args: '--prod') pair typecheck even though it is illegal at runtime. The discriminated union makes that combination unrepresentable — the absence of vercelArgs on the 'experimental-api' variant is enforced by the type system. The runtime mutual-exclusion check still lives in config.ts, but it is now a pure input-parsing concern: it transforms raw action.yml inputs into one of the two valid type states, throwing if both inputs claim to be set. createVercelClient() switches on deployment.kind with an exhaustive match. VercelCliClient narrows config.deployment.kind === 'cli' before reading vercelArgs. Test fixtures across config/vercel/integration tests updated to the new shape; assertions on config.vercelArgs become assertions on config.deployment shape. All 243 unit + 19 integration tests pass; typecheck + lint clean; dist rebuilt. Refs #362. * chore(track): finalize cli-default-experimental-api-20260430 - Move active/ -> completed/ - Update metadata.json: status -> review, pr -> #363 - Sync tracks.jsonl section + status - Add product spec SPEC-001 (deployment) merged from track spec - Update product-specs index * fix: apply AI code review suggestions - config: trim vercel-args before mutual-exclusion check; whitespace-only values no longer trigger spurious errors and the trimmed value is what gets stored in the cli variant (copilot review thread) - config: resolveDeploymentEnvironment now takes the parsed DeploymentMode and the typed target; in experimental-api mode the GitHub deployment environment is inferred from target=production rather than from vercel-args (which is forbidden in that mode). Fixes a mislabel where API-mode production deploys appeared as preview in GitHub deployments. (copilot + cubic review threads) - vercel: add an exhaustive default branch with assertNever in createVercelClient() so unexpected deployment.kind values fail loud at runtime instead of silently returning undefined (copilot review) - README: close the scope row in the inputs table with a trailing | (cubic review) - plan.md: correct the historical 244 -> 243 unit test count (cubic review). 248 today after the new tests added in this commit. - tests: cover trimmed vercel-args, target-aware experimental-api environment resolution, and the new resolveDeploymentEnvironment signature in index.test.ts Refs #362.
1 parent 4076535 commit 2c02881

26 files changed

Lines changed: 772 additions & 123 deletions

.please/docs/knowledge/product.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ Provide the most reliable and feature-rich GitHub Action for automating Vercel d
1313
## Core Features
1414

1515
1. **Vercel Deployment** — Execute Vercel CLI deployments (preview and production) from GitHub Actions
16-
2. **PR & Commit Comments** — Automatically comment deployment URLs on pull requests and commits
17-
3. **GitHub Deployments** — Create GitHub Deployment records with environment tracking, status updates, and auto-deactivation
18-
4. **Alias Domains** — Assign custom domains to deployments with template variables (PR number, branch name)
19-
5. **Backward Compatibility** — Maintain support for legacy "zeit-" prefixed inputs
20-
6. **Flexible Configuration** — Support working directories, team scopes, custom CLI arguments, and project name overrides
16+
2. **Experimental API Mode** — Opt-in deployment via `@vercel/client` (gated by `experimental-api: true`) for projects that prefer typed action inputs over CLI passthrough; mutually exclusive with `vercel-args`
17+
3. **PR & Commit Comments** — Automatically comment deployment URLs on pull requests and commits
18+
4. **GitHub Deployments** — Create GitHub Deployment records with environment tracking, status updates, and auto-deactivation
19+
5. **Alias Domains** — Assign custom domains to deployments with template variables (PR number, branch name)
20+
6. **Backward Compatibility** — Maintain support for legacy "zeit-" prefixed inputs
21+
7. **Flexible Configuration** — Support working directories, team scopes, custom CLI arguments, and project name overrides
2122

2223
## Success Metrics
2324

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
---
2+
id: SPEC-002
3+
level: V_M
4+
domain: deployment
5+
feature: spec
6+
depends: []
7+
conflicts: []
8+
traces: []
9+
created_at: 2026-04-30T00:58:55Z
10+
updated_at: 2026-04-30T00:58:55Z
11+
source_tracks: ["cli-default-experimental-api-20260430"]
12+
---
13+
14+
# Deployment Mode Specification
15+
16+
## Purpose
17+
18+
Defines how the action selects and runs a Vercel deployment. The default path is the stable Vercel CLI; an opt-in experimental API path using `@vercel/client` is available for users who accept the risk of an internal Vercel package without semver guarantees.
19+
20+
## Requirements
21+
22+
### Requirement: experimental-api input
23+
24+
The system MUST expose an `experimental-api` boolean action input that defaults to `false`.
25+
26+
#### Scenario: experimental-api input
27+
28+
- GIVEN the action is invoked from a workflow
29+
- WHEN the user does not set `experimental-api`
30+
- THEN the action behaves as if `experimental-api: false` had been set explicitly
31+
32+
### Requirement: CLI is the default deployment client
33+
34+
The system MUST route to the Vercel CLI client (`VercelCliClient`) whenever `experimental-api` is `false` or unset, regardless of whether `vercel-args` is provided.
35+
36+
#### Scenario: CLI is the default deployment client
37+
38+
- GIVEN `experimental-api` is `false` or unset
39+
- WHEN the action selects a deployment client
40+
- THEN it returns `VercelCliClient` and logs `Using CLI-based deployment`
41+
42+
### Requirement: experimental API opt-in with warning
43+
44+
The system MUST route to the API client (`VercelApiClient`) only when `experimental-api` is `true`, and MUST emit a single `core.warning` per run stating that API mode is experimental and may break across `@vercel/client` updates.
45+
46+
#### Scenario: experimental API opt-in with warning
47+
48+
- GIVEN `experimental-api` is `true`
49+
- WHEN the action selects a deployment client
50+
- THEN it returns `VercelApiClient` and emits exactly one `core.warning` referencing `@vercel/client` and the experimental nature
51+
52+
### Requirement: mutual exclusion of experimental-api and vercel-args
53+
54+
The system MUST fail fast at config-parse time with a clear error naming both inputs when `experimental-api` is `true` and `vercel-args` is non-empty.
55+
56+
#### Scenario: mutual exclusion of experimental-api and vercel-args
57+
58+
- GIVEN `experimental-api: true` and `vercel-args: --prod` are both set
59+
- WHEN the action parses inputs
60+
- THEN it throws a configuration error before any deployment side effects, mentioning both `experimental-api` and `vercel-args`
61+
62+
### Requirement: legacy vercel-args passthrough preserved
63+
64+
The system MUST honor the existing `vercel-args` CLI passthrough when `experimental-api` is `false`, routing to `VercelCliClient` and forwarding the args verbatim.
65+
66+
#### Scenario: legacy vercel-args passthrough preserved
67+
68+
- GIVEN `experimental-api` is `false` and `vercel-args` contains a non-empty string
69+
- WHEN the action runs
70+
- THEN `VercelCliClient` is constructed and the provided args are passed to the underlying `vercel` CLI invocation
71+
72+
### Requirement: typed config carries the deployment mode
73+
74+
The system MUST surface the deployment mode on `ActionConfig` as a discriminated union (`{ kind: 'cli', vercelArgs }` or `{ kind: 'experimental-api' }`) so that the (`experimental-api`, `vercel-args`) mutual-exclusion is unrepresentable at the type level.
75+
76+
#### Scenario: typed config carries the deployment mode
77+
78+
- GIVEN `getActionConfig()` parses raw action inputs
79+
- WHEN the parsing succeeds
80+
- THEN the returned `ActionConfig.deployment` is exactly one variant of the union and the variant matches the user's input
81+
82+
### Requirement: action.yml input metadata is accurate
83+
84+
The system MUST keep `action.yml` input descriptions and deprecation messages aligned with the current routing semantics — `vercel-args` and `scope` are not deprecated under the CLI-default model, while legacy `zeit-*` / `now-*` inputs remain deprecated.
85+
86+
#### Scenario: action.yml input metadata is accurate
87+
88+
- GIVEN a user reads `action.yml` in their editor or browser
89+
- WHEN they look at `vercel-args`, `scope`, and `experimental-api`
90+
- THEN each has a meaningful description, no misleading deprecation copy on `vercel-args` / `scope`, and the `experimental-api` description names the mutual-exclusion rule
91+
92+
### Requirement: README documents the deployment mode
93+
94+
The system MUST provide README documentation that explains the CLI default, the `experimental-api` opt-in, the experimental warning, the mutual-exclusion rule, and a migration note for users coming from the previous API-default behavior.
95+
96+
#### Scenario: README documents the deployment mode
97+
98+
- GIVEN a user opens README.md to learn how to deploy
99+
- WHEN they read the "Deployment Mode" section
100+
- THEN they see CLI documented as the default, `experimental-api: true` documented as the opt-in, the warning text quoted, the mutual-exclusion rule stated, and a migration note for previous v42 users
101+
102+
## Non-functional Requirements
103+
104+
### Requirement: behavior parity for API mode outputs
105+
106+
The system SHOULD preserve deployment output parity (`preview-url`, `preview-name`, `deployment-id`) between API mode and the previous API-default behavior whenever `experimental-api: true` is set.
107+
108+
### Requirement: semver MINOR release for the routing change
109+
110+
The system SHOULD ship the routing-default change as a semver MINOR release. No public API contract is broken; only default behavior shifts. The migration is documented in release notes and the README.
111+
112+
### Requirement: test coverage for routing and config parsing
113+
114+
The system SHOULD maintain ≥80% test coverage for the routing factory and the new config-parsing logic, including the four-case routing matrix and the mutual-exclusion error.

.please/docs/product-specs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
| Spec | Domain | Feature | Created | Related Tracks |
66
|------|--------|---------|---------|----------------|
77
| SPEC-001 | deployment | vercel-build | 2026-04-30 | ["auto-vercel-build-20260430"] |
8+
| SPEC-002 | deployment | spec | 2026-04-30 | ["cli-default-experimental-api-20260430"] |

.please/docs/tracks.jsonl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
{"id":"build-exit-255-20260423","type":"bugfix","status":"review","phase":"finalize","issue":"#336","pr":"#350","created":"2026-04-23","section":"completed"}
44
{"id":"fix-vercel-validation-20260430","type":"bugfix","status":"review","phase":"finalize","issue":"#359","pr":"#364","created":"2026-04-30","section":"completed"}
55
{"id":"auto-vercel-build-20260430","type":"feature","status":"review","phase":"finalize","issue":"#360","pr":"#361","created":"2026-04-30","section":"completed"}
6+
{"id":"cli-default-experimental-api-20260430","type":"feature","status":"review","phase":"finalize","issue":"#362","pr":"#363","created":"2026-04-30","section":"completed"}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"track_id": "cli-default-experimental-api-20260430",
3+
"type": "feature",
4+
"status": "review",
5+
"created_at": "2026-04-30T00:00:00Z",
6+
"updated_at": "2026-04-30T01:00:00Z",
7+
"issue": "#362",
8+
"pr": "#363",
9+
"project": "",
10+
"project_item_id": ""
11+
}

0 commit comments

Comments
 (0)