Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .please/docs/knowledge/product.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions .please/docs/tracks.jsonl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{"id":"prebuilt-project-id-bug-20260408","type":"bugfix","status":"review","phase":"finalize","issue":"#330","created":"2026-04-08","section":"completed"}
{"id":"relative-working-dir-20260423","type":"bugfix","status":"review","phase":"finalize","issue":"#341","pr":"#349","created":"2026-04-23","section":"completed"}
{"id":"build-exit-255-20260423","type":"bugfix","status":"review","phase":"finalize","issue":"#336","pr":"#350","created":"2026-04-23","section":"completed"}
{"id":"cli-default-experimental-api-20260430","type":"feature","status":"in_progress","phase":"implement","issue":"#362","created":"2026-04-30","section":"active"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"track_id": "cli-default-experimental-api-20260430",
"type": "feature",
"status": "ready_for_review",
"created_at": "2026-04-30T00:00:00Z",
"updated_at": "2026-04-30T09:14:31Z",
"issue": "#362",
"pr": "#363",
"project": "",
"project_item_id": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# 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 — 244 unit + 19 integration tests pass

## 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<ActionConfig>` 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.
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading