feat: make CLI deployment default, gate API behind experimental-api flag#363
Conversation
Plan to make CLI deployment the default and gate API behind an opt-in experimental-api input. Refs #362.
|
Deploy preview for file ready!
Deployed with vercel-action |
|
Deploy preview for express-basic-auth ready!
Deployed with vercel-action |
|
Deploy preview for static ready!
Deployed with vercel-action |
There was a problem hiding this comment.
Code Review
This pull request introduces a new 'experimental-api' input to gate the use of the Vercel API client, reverting the default deployment behavior to the Vercel CLI. The changes include updating the configuration parser, adding mutual-exclusion validation between 'experimental-api' and 'vercel-args', and updating documentation and test suites. I have provided feedback to refine the requirements in the specification and the implementation plan to ensure log messages and deprecation notices accurately reflect the new default behavior.
…tion - 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.
… 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.
- 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.
- 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.
|
Deploy preview for angular ready!
Deployed with vercel-action |
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.
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.
- 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
There was a problem hiding this comment.
No issues found across 22 files
Requires human review: This PR modifies core deployment routing logic, refactors the configuration schema into a discriminated union, and changes the default execution mode, which warrants human verification.
Architecture diagram
sequenceDiagram
participant Runner as GitHub Action Runner
participant Parser as getActionConfig()
participant Factory as createVercelClient()
participant CLI as VercelCliClient
participant API as VercelApiClient
participant Vercel as Vercel Infrastructure
Note over Runner,Parser: Configuration & Validation Phase
Runner->>Parser: Load inputs (action.yml)
Parser->>Parser: NEW: parseDeploymentMode(args, experimentalFlag)
alt NEW: experimental-api: true AND vercel-args is provided
Parser-->>Runner: THROW: Config Error (Mutually Exclusive)
else experimental-api: true
Parser-->>Runner: Return ActionConfig { kind: 'experimental-api' }
else Default / vercel-args provided
Parser-->>Runner: Return ActionConfig { kind: 'cli', vercelArgs: '...' }
end
Note over Runner,API: Routing & Strategy Selection
Runner->>Factory: createVercelClient(config)
alt NEW: kind == 'experimental-api'
Factory->>Factory: core.warning("API mode is experimental")
Factory-->>Runner: return new VercelApiClient()
else CHANGED: kind == 'cli' (DEFAULT)
Factory->>Factory: core.info("Using CLI-based deployment")
Factory-->>Runner: return new VercelCliClient()
end
Note over Runner,Vercel: Execution Phase
Runner->>CLI: vercelDeploy()
alt kind == 'cli'
CLI->>CLI: CHANGED: Extract args from config.deployment
CLI->>Vercel: Spawn 'vercel' process via @actions/exec
else kind == 'experimental-api'
API->>Vercel: Direct programmatic call via @vercel/client
end
Vercel-->>Runner: Deployment Metadata (URL, ID)
There was a problem hiding this comment.
Pull request overview
This PR restores the Vercel CLI as the default deployment path and gates the @vercel/client API-based deployment behind a new opt-in experimental-api input, including mutual-exclusion validation with vercel-args.
Changes:
- Introduces a discriminated
deploymentmode inActionConfigand routes client construction offdeployment.kind. - Adds
experimental-apiinput, validatesexperimental-api×vercel-argsmutual exclusion at config-parse time, and emits an experimental warning when API mode is selected. - Updates unit/integration tests and documentation to reflect the new routing matrix and migration guidance.
Reviewed changes
Copilot reviewed 21 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/vercel.ts | Routes between CLI and API clients via config.deployment.kind and warns on experimental API mode. |
| src/vercel-cli.ts | Reads CLI args from deployment.vercelArgs instead of a top-level vercelArgs. |
| src/types.ts | Replaces vercelArgs with deployment: DeploymentMode on ActionConfig. |
| src/config.ts | Parses experimental-api, constructs deployment mode, enforces mutual exclusion with vercel-args. |
| src/tests/vercel.test.ts | Updates factory-routing unit tests to match the new routing matrix and log expectations. |
| src/tests/vercel-api.test.ts | Updates test config helper to the new deployment shape. |
| src/tests/project-config.test.ts | Updates test config helper to the new deployment shape. |
| src/tests/github-comments.test.ts | Updates test config helper to the new deployment shape. |
| src/tests/config.test.ts | Updates assertions and adds coverage for deployment-mode parsing and mutual exclusion. |
| src/integration/vercel-api.test.ts | Adds integration coverage for factory routing (default CLI vs opted-in experimental API). |
| src/integration/github-pr-comments.test.ts | Updates integration test config helper to the new deployment shape. |
| src/integration/github-commit-comments.test.ts | Updates integration test config helper to the new deployment shape. |
| dist/types.d.ts | Regenerates published types with DeploymentMode and deployment field. |
| dist/index.js | Regenerates bundled action output with new routing + parsing logic. |
| action.yml | Adds experimental-api input and updates vercel-args/scope descriptions to remove deprecation framing. |
| README.md | Reworks docs to describe CLI default, experimental API opt-in, mutual exclusion, and migration. |
| .please/docs/tracks/active/cli-default-experimental-api-20260430/spec.md | Adds product spec for the track (needs alignment with implemented config shape). |
| .please/docs/tracks/active/cli-default-experimental-api-20260430/plan.md | Adds implementation plan for the track (needs alignment with implemented config shape). |
| .please/docs/tracks/active/cli-default-experimental-api-20260430/metadata.json | Adds track metadata (status value appears inconsistent with existing conventions). |
| .please/docs/tracks.jsonl | Registers the new active track. |
| .please/docs/knowledge/product.md | Updates product knowledge to include experimental API mode as a core feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
3 issues found across 24 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/config.ts">
<violation number="1" location="src/config.ts:98">
P2: GitHub deployment environment inference still depends on `vercel-args`, but API mode now forbids `vercel-args`, so production API deploys can be mislabeled as `preview` unless users set `github-deployment-environment` manually.</violation>
</file>
<file name=".please/docs/tracks/completed/cli-default-experimental-api-20260430/plan.md">
<violation number="1" location=".please/docs/tracks/completed/cli-default-experimental-api-20260430/plan.md:110">
P3: The documented unit test count is inconsistent (244 vs 243), so verification results are unreliable as written.</violation>
</file>
<file name="README.md">
<violation number="1" location="README.md:55">
P3: Close the `scope` table row with a trailing `|` to keep the Markdown table valid.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Runner as GitHub Action Runner
participant Config as config.ts (Parser)
participant Factory as vercel.ts (Factory)
participant CLI as VercelCliClient (Default)
participant API as VercelApiClient (Experimental)
participant Vercel as Vercel Platform
Note over Runner,Vercel: Deployment Initialization Flow
Runner->>Config: getActionConfig()
Config->>Config: NEW: parseDeploymentMode()
alt NEW: Both 'experimental-api' AND 'vercel-args' provided
Config-->>Runner: Throw Error: Mutually Exclusive
else Valid Inputs
Config-->>Runner: Return ActionConfig (with DeploymentMode union)
end
Runner->>Factory: createVercelClient(config)
alt NEW: config.deployment.kind == 'experimental-api'
Factory->>Runner: core.warning("Experimental mode enabled...")
Factory->>API: Instantiate with typed options
Factory-->>Runner: Return VercelApiClient
else CHANGED: Default / Kind == 'cli'
Factory->>Runner: core.info("Using CLI-based deployment")
Factory->>CLI: Instantiate with vercelArgs
Factory-->>Runner: Return VercelCliClient
end
Note over Runner,Vercel: Execution Phase
Runner->>CLI: deploy() (if CLI selected)
CLI->>Vercel: Execute 'vercel deploy' with passthrough args
Runner->>API: deploy() (if API selected)
API->>Vercel: Programmatic deployment via @vercel/client
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- 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.
Resolve conflicts: - src/config.ts: keep our extracted target const, integrate master's prebuilt and vercelBuild const extraction (used by mutual-exclusion checks for vercel-build vs prebuilt and vercel-build vs vercel-args) - .please/docs/product-specs/index.md: master's vercel-build was committed first as SPEC-001; renumber our deployment/spec.md to SPEC-002 - .please/docs/product-specs/deployment/spec.md: bump frontmatter id to SPEC-002 to match the renumbered index entry - .please/docs/tracks.jsonl: keep all three new track entries - dist/index.js + dist/index.js.map: rebuilt from merged source via ncc Auto-merged cleanly: - action.yml (new vercel-build input + our experimental-api input coexist alphabetically) - src/types.ts (new vercelBuild field added next to prebuilt) - src/__tests__/* and src/__integration__/* (deployment discriminated union shape preserved) - README.md (sections compose without overlap) Quality gate: pnpm lint (0 errors), pnpm typecheck (clean), pnpm test (319/319 passing), pnpm test:integration (23/23 passing).
|


Summary
Restore the Vercel CLI as the default deployment client and gate the
@vercel/clientAPI path behind a newexperimental-apiopt-in input. The API client depends on an internal Vercel package without semver guarantees, so users should opt in consciously.Track
cli-default-experimental-api-20260430Routing matrix
experimental-apivercel-argsVercelCliClient--prodVercelCliClientVercelApiClient(with experimental warning)--prodType-level invariant
After the review-driven refactor, the mutual-exclusion invariant is encoded in the type system:
The
(experimental-api: true, vercel-args: '...')combination is unrepresentable. The runtime check inparseDeploymentMode()exists only to validate raw action.yml inputs at the boundary.Implementation Tasks
Verification Checklist
pnpm lint— no warnings (verified locally: 0 errors, 20 pre-existing warnings)pnpm typecheck— passes (verified locally)pnpm test— all unit tests pass, including the four-case routing matrix (243/243 passing)pnpm test:integration— emulator tests pass for both default (CLI) andexperimental-api: true(API) paths (19/19 passing)pnpm build—dist/index.jsregenerated successfully (committed)core.info('Using CLI-based deployment')is logged (no warning)experimental-api: trueand confirmcore.warning(...)is logged exactly onceexperimental-api: trueandvercel-args: --prodand confirm a clear config error is thrown before any deployment side effectsReview
🤖 Created via /please:implement, refined via /please:review