Skip to content

feat: make CLI deployment default, gate API behind experimental-api flag#363

Merged
amondnet merged 12 commits into
masterfrom
amondnet/cli-default-api-exp
Apr 30, 2026
Merged

feat: make CLI deployment default, gate API behind experimental-api flag#363
amondnet merged 12 commits into
masterfrom
amondnet/cli-default-api-exp

Conversation

@amondnet

@amondnet amondnet commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

Restore the Vercel CLI as the default deployment client and gate the @vercel/client API path behind a new experimental-api opt-in input. The API client depends on an internal Vercel package without semver guarantees, so users should opt in consciously.

Track

Routing matrix

experimental-api vercel-args Result
false (default) empty VercelCliClient
false (default) --prod VercelCliClient
true empty VercelApiClient (with experimental warning)
true --prod Config error (mutually exclusive, thrown at config-parse time)

Type-level invariant

After the review-driven refactor, the mutual-exclusion invariant is encoded in the type system:

type DeploymentMode =
  | { kind: 'cli', vercelArgs: string }
  | { kind: 'experimental-api' }

interface ActionConfig {
  deployment: DeploymentMode
  // ...
}

The (experimental-api: true, vercel-args: '...') combination is unrepresentable. The runtime check in parseDeploymentMode() exists only to validate raw action.yml inputs at the boundary.

Implementation Tasks

  • T001 Add experimental-api input
  • T002 ActionConfig type (later refactored into DeploymentMode union)
  • T003 Parse + mutual exclusion
  • T004 Config tests
  • T005 Routing change + warning
  • T006 Routing matrix tests
  • T007 Test helper update
  • T008 action.yml cleanup
  • T009 README Deployment Mode section
  • T010 README migration note
  • T011 Integration tests
  • T012 Quality gate

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) and experimental-api: true (API) paths (19/19 passing)
  • pnpm builddist/index.js regenerated successfully (committed)
  • Manual: invoke the action with default inputs and confirm core.info('Using CLI-based deployment') is logged (no warning)
  • Manual: invoke the action with experimental-api: true and confirm core.warning(...) is logged exactly once
  • Manual: invoke the action with both experimental-api: true and vercel-args: --prod and confirm a clear config error is thrown before any deployment side effects

Review

  • Spec compliance: 17/17 IMPLEMENTED
  • Code review: 0 issues at confidence ≥80 (iteration 2 clean)
  • Type review: original suggestion applied via discriminated union refactor
  • Test review: AC-1 four-case matrix and AC-6 single-warning assertion verified

🤖 Created via /please:implement, refined via /please:review

Plan to make CLI deployment the default and gate API behind an
opt-in experimental-api input. Refs #362.
@github-actions

github-actions Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for file ready!

Project:file
Status: ✅  Deploy successful!
Preview URL:https://team-scope-test-mg9fe3cou-dietfriends.vercel.app
Latest Commit:75dde36
Inspect:View deployment

Deployed with vercel-action

@github-actions

github-actions Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for express-basic-auth ready!

Project:express-basic-auth
Status: ✅  Deploy successful!
Preview URL:https://express-basic-auth-6u1jse22t-minsu-lees-projects-b1e388b7.vercel.app
Latest Commit:75dde36
Inspect:View deployment

Deployed with vercel-action

@github-actions

github-actions Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for static ready!

Project:static
Status: ✅  Deploy successful!
Preview URL:https://zeit-now-deployment-action-example-angular-gegymjs3q.vercel.app
Latest Commit:75dde36
Alias:https://staging.static.vercel-action.amond.dev
Alias:https://pr-363.static.vercel-action.amond.dev
Inspect:View deployment

Deployed with vercel-action

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .please/docs/tracks/active/cli-default-experimental-api-20260430/plan.md Outdated
…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.
@github-actions

github-actions Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for angular ready!

Project:angular
Status: ✅  Deploy successful!
Preview URL:https://zeit-now-deployment-action-example-angular-r6ho4xsdn.vercel.app
Latest Commit:75dde36
Alias:https://staging.angular.vercel-action.amond.dev
Alias:https://pr-363.angular.vercel-action.amond.dev
Inspect:View deployment

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.
@amondnet amondnet marked this pull request as ready for review April 30, 2026 00:56
Copilot AI review requested due to automatic review settings April 30, 2026 00:56
- 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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Loading

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 deployment mode in ActionConfig and routes client construction off deployment.kind.
  • Adds experimental-api input, validates experimental-api × vercel-args mutual 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.

Comment thread src/config.ts
Comment thread src/vercel.ts
Comment thread src/config.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/config.ts
Comment thread .please/docs/tracks/completed/cli-default-experimental-api-20260430/plan.md Outdated
Comment thread README.md Outdated
- 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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 9 files (changes from recent commits).

Requires human review: This PR changes the default deployment client from API to CLI and refactors core configuration logic. This is a high-impact behavioral change requiring human review.

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).
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
37.7% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@amondnet amondnet merged commit 2c02881 into master Apr 30, 2026
13 of 15 checks passed
@amondnet amondnet deleted the amondnet/cli-default-api-exp branch April 30, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants