diff --git a/.please/docs/knowledge/product.md b/.please/docs/knowledge/product.md index 55c7dc59..4e1402d0 100644 --- a/.please/docs/knowledge/product.md +++ b/.please/docs/knowledge/product.md @@ -13,11 +13,12 @@ Provide the most reliable and feature-rich GitHub Action for automating Vercel d ## Core Features 1. **Vercel Deployment** — Execute Vercel CLI deployments (preview and production) from GitHub Actions -2. **PR & Commit Comments** — Automatically comment deployment URLs on pull requests and commits -3. **GitHub Deployments** — Create GitHub Deployment records with environment tracking, status updates, and auto-deactivation -4. **Alias Domains** — Assign custom domains to deployments with template variables (PR number, branch name) -5. **Backward Compatibility** — Maintain support for legacy "zeit-" prefixed inputs -6. **Flexible Configuration** — Support working directories, team scopes, custom CLI arguments, and project name overrides +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` +3. **PR & Commit Comments** — Automatically comment deployment URLs on pull requests and commits +4. **GitHub Deployments** — Create GitHub Deployment records with environment tracking, status updates, and auto-deactivation +5. **Alias Domains** — Assign custom domains to deployments with template variables (PR number, branch name) +6. **Backward Compatibility** — Maintain support for legacy "zeit-" prefixed inputs +7. **Flexible Configuration** — Support working directories, team scopes, custom CLI arguments, and project name overrides ## Success Metrics diff --git a/.please/docs/product-specs/deployment/spec.md b/.please/docs/product-specs/deployment/spec.md new file mode 100644 index 00000000..6f53692b --- /dev/null +++ b/.please/docs/product-specs/deployment/spec.md @@ -0,0 +1,114 @@ +--- +id: SPEC-002 +level: V_M +domain: deployment +feature: spec +depends: [] +conflicts: [] +traces: [] +created_at: 2026-04-30T00:58:55Z +updated_at: 2026-04-30T00:58:55Z +source_tracks: ["cli-default-experimental-api-20260430"] +--- + +# Deployment Mode Specification + +## Purpose + +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. + +## Requirements + +### Requirement: experimental-api input + +The system MUST expose an `experimental-api` boolean action input that defaults to `false`. + +#### Scenario: experimental-api input + +- GIVEN the action is invoked from a workflow +- WHEN the user does not set `experimental-api` +- THEN the action behaves as if `experimental-api: false` had been set explicitly + +### Requirement: CLI is the default deployment client + +The system MUST route to the Vercel CLI client (`VercelCliClient`) whenever `experimental-api` is `false` or unset, regardless of whether `vercel-args` is provided. + +#### Scenario: CLI is the default deployment client + +- GIVEN `experimental-api` is `false` or unset +- WHEN the action selects a deployment client +- THEN it returns `VercelCliClient` and logs `Using CLI-based deployment` + +### Requirement: experimental API opt-in with warning + +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. + +#### Scenario: experimental API opt-in with warning + +- GIVEN `experimental-api` is `true` +- WHEN the action selects a deployment client +- THEN it returns `VercelApiClient` and emits exactly one `core.warning` referencing `@vercel/client` and the experimental nature + +### Requirement: mutual exclusion of experimental-api and vercel-args + +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. + +#### Scenario: mutual exclusion of experimental-api and vercel-args + +- GIVEN `experimental-api: true` and `vercel-args: --prod` are both set +- WHEN the action parses inputs +- THEN it throws a configuration error before any deployment side effects, mentioning both `experimental-api` and `vercel-args` + +### Requirement: legacy vercel-args passthrough preserved + +The system MUST honor the existing `vercel-args` CLI passthrough when `experimental-api` is `false`, routing to `VercelCliClient` and forwarding the args verbatim. + +#### Scenario: legacy vercel-args passthrough preserved + +- GIVEN `experimental-api` is `false` and `vercel-args` contains a non-empty string +- WHEN the action runs +- THEN `VercelCliClient` is constructed and the provided args are passed to the underlying `vercel` CLI invocation + +### Requirement: typed config carries the deployment mode + +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. + +#### Scenario: typed config carries the deployment mode + +- GIVEN `getActionConfig()` parses raw action inputs +- WHEN the parsing succeeds +- THEN the returned `ActionConfig.deployment` is exactly one variant of the union and the variant matches the user's input + +### Requirement: action.yml input metadata is accurate + +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. + +#### Scenario: action.yml input metadata is accurate + +- GIVEN a user reads `action.yml` in their editor or browser +- WHEN they look at `vercel-args`, `scope`, and `experimental-api` +- THEN each has a meaningful description, no misleading deprecation copy on `vercel-args` / `scope`, and the `experimental-api` description names the mutual-exclusion rule + +### Requirement: README documents the deployment mode + +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. + +#### Scenario: README documents the deployment mode + +- GIVEN a user opens README.md to learn how to deploy +- WHEN they read the "Deployment Mode" section +- 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 + +## Non-functional Requirements + +### Requirement: behavior parity for API mode outputs + +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. + +### Requirement: semver MINOR release for the routing change + +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. + +### Requirement: test coverage for routing and config parsing + +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. diff --git a/.please/docs/product-specs/index.md b/.please/docs/product-specs/index.md index 25722afc..45dc33ab 100644 --- a/.please/docs/product-specs/index.md +++ b/.please/docs/product-specs/index.md @@ -5,3 +5,4 @@ | Spec | Domain | Feature | Created | Related Tracks | |------|--------|---------|---------|----------------| | SPEC-001 | deployment | vercel-build | 2026-04-30 | ["auto-vercel-build-20260430"] | +| SPEC-002 | deployment | spec | 2026-04-30 | ["cli-default-experimental-api-20260430"] | diff --git a/.please/docs/tracks.jsonl b/.please/docs/tracks.jsonl index 87464cdb..ec6a4a0e 100644 --- a/.please/docs/tracks.jsonl +++ b/.please/docs/tracks.jsonl @@ -3,3 +3,4 @@ {"id":"build-exit-255-20260423","type":"bugfix","status":"review","phase":"finalize","issue":"#336","pr":"#350","created":"2026-04-23","section":"completed"} {"id":"fix-vercel-validation-20260430","type":"bugfix","status":"review","phase":"finalize","issue":"#359","pr":"#364","created":"2026-04-30","section":"completed"} {"id":"auto-vercel-build-20260430","type":"feature","status":"review","phase":"finalize","issue":"#360","pr":"#361","created":"2026-04-30","section":"completed"} +{"id":"cli-default-experimental-api-20260430","type":"feature","status":"review","phase":"finalize","issue":"#362","pr":"#363","created":"2026-04-30","section":"completed"} diff --git a/.please/docs/tracks/completed/cli-default-experimental-api-20260430/metadata.json b/.please/docs/tracks/completed/cli-default-experimental-api-20260430/metadata.json new file mode 100644 index 00000000..8cd21f0a --- /dev/null +++ b/.please/docs/tracks/completed/cli-default-experimental-api-20260430/metadata.json @@ -0,0 +1,11 @@ +{ + "track_id": "cli-default-experimental-api-20260430", + "type": "feature", + "status": "review", + "created_at": "2026-04-30T00:00:00Z", + "updated_at": "2026-04-30T01:00:00Z", + "issue": "#362", + "pr": "#363", + "project": "", + "project_item_id": "" +} diff --git a/.please/docs/tracks/completed/cli-default-experimental-api-20260430/plan.md b/.please/docs/tracks/completed/cli-default-experimental-api-20260430/plan.md new file mode 100644 index 00000000..d343f99a --- /dev/null +++ b/.please/docs/tracks/completed/cli-default-experimental-api-20260430/plan.md @@ -0,0 +1,164 @@ +# Plan: CLI Default with Experimental API Mode + +> Track: cli-default-experimental-api-20260430 +> Spec: [spec.md](./spec.md) + +## Overview + +- **Source**: /please:plan +- **Track**: cli-default-experimental-api-20260430 +- **Created**: 2026-04-30 +- **Approach**: Add `experimental-api` boolean input. Validate mutual exclusion with `vercel-args` at config-parse time. Move routing default to CLI in `createVercelClient()` and gate the API path behind `experimentalApi`. Emit a `core.warning` when the API path is selected. + +## Purpose + +Make Vercel CLI the default deployment client and gate the `@vercel/client` API path behind an explicit opt-in. The API client depends on an internal Vercel package without semver guarantees, so users should opt in consciously. + +## Context + +- `createVercelClient()` in `src/vercel.ts:9-16` currently selects the API client when `vercelArgs` is empty. This plan inverts that default. +- `getActionConfig()` in `src/config.ts:81-117` parses all action inputs. The new `experimental-api` input is parsed here, and the mutual-exclusion check lives here too. +- `ActionConfig` in `src/types.ts:55-83` carries the parsed config across the codebase. +- Existing tests in `src/__tests__/vercel.test.ts:495-515` already cover the routing matrix and need to be updated to the new semantics. +- The previous track `api-based-deployment-20260329` is the immediate predecessor and added the typed inputs (`target`, `prebuilt`, etc.) that remain available regardless of routing default. + +## Architecture Decision + +**Validation lives at config-parse time; routing lives at the factory.** Splitting these two concerns keeps each file's responsibility narrow: + +- `getActionConfig()` is the single place that reads inputs and produces a typed config. Cross-input validation (mutual exclusion) belongs here so it fails fast before any side effects (env var setup, octokit creation, deployment context fetching) run. +- `createVercelClient()` only decides which client to instantiate. It assumes the config it receives is already valid, which keeps the routing logic boolean-simple and purely a function of `experimentalApi`. + +The factory does not re-check `vercelArgs` against `experimentalApi`; that invariant is enforced upstream. This avoids defensive duplication and surfaces the misconfiguration earlier in the run. + +The experimental warning is emitted in the factory (not the constructor) because the factory is the single point that proves "API path was actually chosen for this run." Constructors of `VercelApiClient` are also instantiated by tests and would emit noise. + +## Tasks + +### Phase 1 — Config foundation + +- [x] T001 Add `experimental-api` boolean input to `action.yml` with default `'false'` and a description explaining it gates opt-in API mode and is mutually exclusive with `vercel-args` (file: action.yml) +- [x] T002 Add `experimentalApi: boolean` field to `ActionConfig` interface (file: src/types.ts) (depends on T001) +- [x] T003 Parse the new input in `getActionConfig()` and add the mutual-exclusion error: throw a clear `Error` when both `experimental-api === 'true'` and `vercel-args` is non-empty (file: src/config.ts) (depends on T002) +- [x] T004 Add unit tests in `src/__tests__/config.test.ts` for: (a) `experimentalApi=false` default, (b) `experimentalApi=true` parses correctly, (c) mutual-exclusion error message and stack when both are set (file: src/__tests__/config.test.ts) (depends on T003) + +### Phase 2 — Routing change + +- [x] T005 Update `createVercelClient()` in `src/vercel.ts`: route to `VercelCliClient` by default; route to `VercelApiClient` only when `config.experimentalApi === true`; emit `core.warning` describing the experimental nature and how to opt out when API path is taken (file: src/vercel.ts) (depends on T003) +- [x] T006 Update existing routing tests in `src/__tests__/vercel.test.ts` (the `describe('createVercelClient', ...)` block): replace the two existing cases with the new four-case matrix from spec AC-1 — verify warning text in the API case, verify `core.info` text in the CLI case, verify both `vercelArgs=""` and `vercelArgs="--prod"` route to `VercelCliClient` when `experimentalApi=false` (file: src/__tests__/vercel.test.ts) (depends on T005) +- [x] T007 [P] Update `createConfig` test helper in `src/__tests__/vercel.test.ts` to include `experimentalApi: false` so the helper produces a valid `ActionConfig` after T002 (file: src/__tests__/vercel.test.ts) (depends on T002) + +### Phase 3 — action.yml cleanup + +- [x] T008 Replace the empty `description` of `vercel-args` in `action.yml` with a real description noting it is for ad-hoc CLI passthrough; reword the `deprecationMessage` of `scope` so it no longer claims scope is "only for CLI when vercel-args is provided" (since CLI is now the default regardless of vercel-args). Keep zeit-*/now-* deprecations untouched (file: action.yml) (depends on T001) + +### Phase 4 — Documentation + +- [x] T009 Add a new "Deployment Mode" section to README.md near the top of the inputs documentation: explain CLI is the default, document the `experimental-api` opt-in, surface the experimental warning, document the mutual-exclusion rule (file: README.md) (depends on T008) +- [x] T010 [P] Add a migration note to README.md (or a release-notes section) explicitly calling out that users who relied on the previous API-default must now set `experimental-api: true` to keep that behavior (file: README.md) (depends on T009) + +### Phase 5 — Integration verification + +- [x] T011 Update or add an integration test in `src/__integration__/` that runs the action twice against the emulator: once with default inputs (CLI path) and once with `experimental-api: true` (API path), verifying both produce a valid `preview-url` (file: src/__integration__/vercel-api.test.ts) (depends on T005) +- [x] T012 Run the full quality gate locally: `pnpm lint`, `pnpm typecheck`, `pnpm build`, `pnpm test`, `pnpm test:integration`. Fix any failures uncovered (depends on T006, T007, T011) + +## Dependencies + +``` +T001 ──► T002 ──► T003 ──► T004 + │ + └─► T005 ──► T006 ──► T012 + │ │ + │ └────► T011 ──► T012 + │ + └─► T007 [P, depends on T002] ──► T012 +T001 ──► T008 ──► T009 ──► T010 [P] +``` + +`[P]` tasks are parallelizable with their siblings under the same dependency root. + +## Key Files + +| File | Change | +|------|--------| +| `action.yml` | Add `experimental-api` input; add real description to `vercel-args`; reword `scope` deprecation | +| `src/types.ts` | Add `experimentalApi: boolean` to `ActionConfig` | +| `src/config.ts` | Parse `experimental-api`; add mutual-exclusion validation in `getActionConfig()` | +| `src/vercel.ts` | Invert routing default; emit experimental warning | +| `src/__tests__/config.test.ts` | Tests for new input parsing and mutual-exclusion error | +| `src/__tests__/vercel.test.ts` | Update test config helper; rewrite routing matrix tests | +| `src/__integration__/vercel-api.test.ts` | Add CLI-default and experimental-api opt-in cases | +| `README.md` | Deployment Mode section + migration note | + +## Verification + +1. `pnpm lint` — no warnings +2. `pnpm typecheck` — passes +3. `pnpm test` — all unit tests pass, including the new four-case routing matrix +4. `pnpm test:integration` — emulator tests pass for both default (CLI) and `experimental-api: true` (API) paths +5. `pnpm build` — `dist/index.js` regenerated successfully +6. Manual: invoke the action with default inputs and confirm `core.info('Using CLI-based deployment')` is logged (no warning) +7. Manual: invoke the action with `experimental-api: true` and confirm `core.warning(...)` is logged exactly once +8. 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 + +## Progress + +- 2026-04-30 — Phase 1 complete: T001 added `experimental-api` input; T002 added `experimentalApi: boolean` to `ActionConfig`; T003 added parsing + mutual-exclusion validation; T004 added 7 config tests (defaults, parsing, error message) +- 2026-04-30 — Phase 2 complete: T005 inverted routing default in `createVercelClient()` and emits `core.warning` when API path is taken; T006 rewrote `describe('createVercelClient', ...)` as the four-case AC-1 matrix; T007 updated `createConfig` test helper +- 2026-04-30 — Phase 3 complete: T008 replaced empty `vercel-args` description, reworded `scope` deprecation message +- 2026-04-30 — Phase 4 complete: T009 replaced "Migration to API-based Deployment" with "Deployment Mode" section in README; T010 added migration note for users on the previous v42 API default +- 2026-04-30 — Phase 5 complete: T011 added factory-routing integration tests + `experimentalApi` field to integration `createConfig`; T012 quality gate (lint, typecheck, build, full test suite + integration suite) all green — 243 unit + 19 integration tests pass (later raised to 248 after the discriminated-union refactor + post-review fixes) + +## Decision Log + +| Date | Decision | Rationale | +|------------|------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------| +| 2026-04-30 | Validate mutual exclusion at config-parse time (not in `createVercelClient`) | Fails fast before any side effects; one source of truth; easier to unit-test in isolation | +| 2026-04-30 | Emit experimental warning in `createVercelClient()`, not in `VercelApiClient` constructor | Co-located with routing decision; constructor is also called by tests and would produce warning noise | +| 2026-04-30 | Field naming: `experimentalApi` (camelCase) and `experimental-api` (kebab-case input) | Matches existing convention (`vercelOrgId` / `vercel-org-id`, `autoAssignCustomDomains` / `auto-assign-custom-domains`) | +| 2026-04-30 | Treat as semver MINOR (per user direction), not MAJOR | No public API contract changes; only default behavior shifts. Document the migration in README and release notes | +| 2026-04-30 | Hard-fail on mutual exclusion rather than silent precedence | Surfaces misconfiguration immediately; avoids surprising deployments where one input is silently ignored | + +## Surprises & Discoveries + +- The spec's FR-6 mentioned removing a `deprecationMessage` from `vercel-args`, but inspection of `action.yml:7-10` shows `vercel-args` has no `deprecationMessage` at all — only an empty description. The real cleanup target is the `scope` input (line 44), whose deprecation message references "CLI-based deployments (when vercel-args is provided)" — that conditional clause is now misleading because CLI is the default regardless of `vercel-args`. T008 captures this corrected scope. +- Existing tests in `src/__tests__/vercel.test.ts:495-515` already provide a routing test scaffold; this plan rewrites them rather than creating new files. +- `src/__tests__/vercel.test.ts:27-55` `createConfig` helper does not currently include `githubDeployment` / `githubDeploymentEnvironment` fields — it produces a `Partial` cast as full. T007 will update it for the new `experimentalApi` field; whether to also fix the missing legacy fields is out of scope for this track. +- Initial implementation modeled the routing as `experimentalApi: boolean` + `vercelArgs: string`. During code review, the type-analyzer reviewer (confidence 82) flagged that the mutual-exclusion invariant only existed at runtime; the type allowed `{ experimentalApi: true, vercelArgs: '--prod' }` to typecheck. A post-implementation refactor (commit `a08587e`) replaced both fields with a discriminated union `DeploymentMode = { kind: 'cli', vercelArgs: string } | { kind: 'experimental-api' }` and an exhaustive `switch` in `createVercelClient()`. The runtime mutual-exclusion check stayed but is now scoped to input parsing only. + +## Outcomes & Retrospective + +### What Was Shipped + +- New `experimental-api` boolean action input (default `false`). +- `ActionConfig.deployment: DeploymentMode` discriminated union encoding the (CLI ↔ experimental-API) mutual-exclusion at the type level. +- `createVercelClient()` switches on `deployment.kind`; CLI is the default path with `core.info`, API path emits a single `core.warning`. +- Mutual-exclusion error thrown at config-parse time when both `experimental-api: true` and `vercel-args` are set. +- README "Deployment Mode" section documenting CLI default, experimental-api opt-in, mutual exclusion, CLI ↔ API input mapping, and a migration note for users who relied on the previous v42 API default. +- 7 new config tests + 3 new routing matrix tests + 2 new factory routing integration tests. +- `action.yml` polished: real description for `vercel-args`; `scope` `deprecationMessage` removed (input remains, with description preferring `vercel-org-id`). + +### What Went Well + +- TDD cycle (RED → GREEN) caught the routing matrix end-to-end before the implementation switched defaults. +- Spec compliance audit during /please:review caught FR-6 (the `scope` `deprecationMessage` was rewritten rather than removed) immediately. +- The type-system refactor was suggested *after* a working implementation and validated by a re-review iteration; the change was small, self-contained, and improved the design without expanding scope. +- All gates (lint, typecheck, build, 243 unit + 19 integration tests) stayed green throughout multiple commits. + +### What Could Improve + +- The original spec FR-6 was based on an assumption that turned out to be wrong (`vercel-args` had no `deprecationMessage`). A short codebase scan during spec authoring would have caught this; future bug/refactor specs should ground claims in `git grep` / current state before locking the wording. +- The type-system encoding (discriminated union) was a cleaner design from day one, but only emerged through an external review. Future specs that involve mutual-exclusion of inputs should consider tagged-union shapes upfront. + +### Tech Debt Created + +- None directly from this track. The `dist/` regeneration adds noise to the diff but is required for GitHub Actions consumption per `CLAUDE.md`. +- Pre-existing warnings (`ts/no-explicit-any` in test files) remain — out of scope for this track, but a candidate for a future sweep. + +### Stats + +- Tasks: 12/12 complete +- Tests: 243 unit + 19 integration +- Commits: 9 (track docs, setup, 4 implementation phases, doc sync, FR-6 fix, type refactor) +- Code review iterations: 2 (1 issue → fix → 0 issues) +- Spec compliance: 17/17 IMPLEMENTED diff --git a/.please/docs/tracks/completed/cli-default-experimental-api-20260430/spec.md b/.please/docs/tracks/completed/cli-default-experimental-api-20260430/spec.md new file mode 100644 index 00000000..d7ee023e --- /dev/null +++ b/.please/docs/tracks/completed/cli-default-experimental-api-20260430/spec.md @@ -0,0 +1,61 @@ +--- +product_spec_domain: deployment +--- + +# CLI Default with Experimental API Mode + +> Track: cli-default-experimental-api-20260430 + +## Overview + +Restore the Vercel CLI as the default deployment client, and provide the `@vercel/client` API-based deployment as an opt-in experimental feature gated by a new `experimental-api` action input. + +The previous track (`api-based-deployment-20260329`) made API the default when `vercel-args` was empty. However, `@vercel/client` is an internal Vercel package without semver guarantees, so users should explicitly opt in to the API path rather than have it imposed as the default. + +## Requirements + +### Functional Requirements + +- [ ] FR-1: Add a new `experimental-api` boolean input to `action.yml` (default: `false`). +- [ ] FR-2: Default routing in `createVercelClient()` returns `VercelCliClient` when `experimental-api` is `false` (or unset). +- [ ] FR-3: When `experimental-api` is `true`, `createVercelClient()` returns `VercelApiClient` and emits a `core.warning` line stating that API mode is experimental and may break across `@vercel/client` updates. +- [ ] FR-4: When `experimental-api` is `true` AND `vercel-args` is non-empty, the action MUST fail fast with a clear configuration error explaining that the two inputs are mutually exclusive, and instruct the user to choose one. +- [ ] FR-5: When `vercel-args` is non-empty and `experimental-api` is `false`, route to `VercelCliClient` (legacy passthrough preserved). +- [ ] FR-6: Remove the `deprecationMessage` block from `vercel-args` and `scope` inputs in `action.yml` — CLI is now the standard mode and these inputs are no longer deprecated. +- [ ] FR-7: Update `ActionConfig` type and `getActionConfig()` parser in `src/config.ts` to surface the new `experimentalApi: boolean` field. +- [ ] FR-8: Update `README.md` with: (a) a new "Deployment Mode" section explaining CLI is default and `experimental-api` opt-in, (b) the warning that API mode is experimental, (c) the mutual exclusion rule with `vercel-args`. + +### Non-functional Requirements + +- [ ] NFR-1: Behavior parity — when API mode is enabled via `experimental-api`, deployment outputs (`preview-url`, `preview-name`, `deployment-id`) must match the previous API-default behavior. +- [ ] NFR-2: Released as a semver MINOR bump (no user code change is required to keep working — workflows that set `vercel-args` continue unchanged; users who relied on API as default must opt in, but this is a behavior change documented in release notes, not an API contract change). +- [ ] NFR-3: Test coverage ≥80% for new routing logic and config parsing changes. + +## Acceptance Criteria + +- [ ] AC-1: `pnpm test` — new unit tests cover all four routing matrix cases: + - `experimental-api=false`, `vercel-args=""` → `VercelCliClient` + - `experimental-api=false`, `vercel-args="--prod"` → `VercelCliClient` + - `experimental-api=true`, `vercel-args=""` → `VercelApiClient` (with warning) + - `experimental-api=true`, `vercel-args="--prod"` → throws config error before client construction +- [ ] AC-2: `pnpm test:integration` — emulator-backed test confirms `experimental-api=true` end-to-end deployment still produces the expected preview URL. +- [ ] AC-3: `pnpm build` succeeds and `dist/index.js` rebuilds cleanly. +- [ ] AC-4: `pnpm lint` passes with no new warnings. +- [ ] AC-5: Running the action with default inputs (no `experimental-api`, no `vercel-args`) on a sample workflow uses CLI mode (verifiable via `core.info` log line). +- [ ] AC-6: The `core.warning` is emitted exactly once per action run when `experimental-api=true`. + +## Out of Scope + +- Removing the `vercel` CLI npm dependency or the `@actions/exec` dependency. +- Removing or migrating `@vercel/client` away from internal-package status. +- Removing `zeit-*` / `now-*` deprecated inputs. +- Stabilizing the API mode (graduating from experimental → stable). That decision belongs to a future track once `@vercel/client` stability improves. +- Changes to `inspect()` or `assignAlias()` — these continue to use their current implementations on both clients. +- Reverting the new typed inputs (`target`, `prebuilt`, `force`, `env`, `build-env`, `regions`, `archive`, `root-directory`, `auto-assign-custom-domains`, `custom-environment`, `public`, `with-cache`) added by `api-based-deployment-20260329`. These remain available; only the routing default changes. + +## Assumptions + +- The existing `VercelClient` strategy interface and the two implementations (`VercelCliClient`, `VercelApiClient`) remain unchanged in shape — only the factory `createVercelClient()` selection logic and the config schema change. +- The typed inputs added by the previous track (`target`, `prebuilt`, `force`, etc.) are honored by `VercelCliClient` already. If they are not, that is tracked separately and not in scope here. +- Users who currently rely on the API default (no `vercel-args`, no opt-in) will receive CLI behavior after upgrade. Release notes will explicitly call out the change and the `experimental-api: true` opt-in. +- The mutual-exclusion error in FR-4 is preferable to silent precedence rules because it surfaces the misconfiguration immediately and avoids surprising deployments. diff --git a/README.md b/README.md index 54056583..4f121f71 100644 --- a/README.md +++ b/README.md @@ -50,18 +50,19 @@ This action make a Vercel deployment with github actions. | github-deployment-environment |