Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
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":"auto-vercel-build-20260430","type":"feature","status":"in_progress","phase":"implement","issue":"#360","created":"2026-04-30","section":"active"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"track_id": "auto-vercel-build-20260430",
"type": "feature",
"status": "in_progress",
"created_at": "2026-04-29T18:55:48Z",
"updated_at": "2026-04-29T19:06:03Z",
"issue": "#360",
"pr": "",
"project": "",
"project_item_id": ""
}
142 changes: 142 additions & 0 deletions .please/docs/tracks/active/auto-vercel-build-20260430/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Plan: Local Vercel Build Step (`vercel-build` input)

> Track: auto-vercel-build-20260430
> Spec: [spec.md](./spec.md)

## Overview

- **Source**: /please:plan
- **Track**: auto-vercel-build-20260430
- **Issue**: TBD (created by /please:new-track)
- **Created**: 2026-04-30
- **Approach**: New `VercelBuildRunner` module + pre-deploy step in `run()`

## Purpose

Add an opt-in `vercel-build` action input that executes the official Vercel CLI workflow (`vercel pull` → `vercel build`) inside the GitHub Actions runner before deployment, then reuses the existing prebuilt deploy path to upload `.vercel/output`. Lets users build inside CI with their own secrets/runtime instead of relying on Vercel's remote build.

## Context

The action currently has two deploy paths: API-based (default, uploads source via `@vercel/client`) and CLI-based (when `vercel-args` is provided, runs `npx vercel ...`). The `prebuilt` input already exists and routes to the API path with prebuilt upload of `.vercel/output`.

This track adds a third orchestration: when `vercel-build: true`, run `vercel pull` + `vercel build` locally first, then funnel into the existing prebuilt API path. The change is additive and opt-in — existing pipelines see no behavior change.

## Architecture Decision

**Chosen: New `src/vercel-build.ts` module + pre-deploy orchestration in `src/index.ts`** (Approach A from architecture review).

Rejected: embedding build logic inside `VercelApiClient.deploy()` (Approach B). Reasons for rejection: couples subprocess execution to the API client, violates AGENTS.md "isolate side effects at boundary," and harder to unit-test in isolation.

Key design points:
- `@vercel/client` v17 does **not** expose `build`/`pull` primitives; the SDK fallback in spec FR-5 is therefore void. We invoke the bundled `vercel` package via `@actions/exec` (same pattern as `vercel-cli.ts`).
- Mutual exclusivity (`vercel-build: true` + `prebuilt: true`) is enforced in `getActionConfig()` so the action fails before any network/IO.
- After a successful local build, the runner mutates a copy of `ActionConfig` to set `prebuilt = true`, then the existing `VercelApiClient` deploys `.vercel/output` unchanged. No changes to `vercel-api.ts` are required.
- Build failure surfaces as a custom `BuildFailedError` carrying a truncated stderr tail; the `run()` catch block recognizes this error type and posts a build-failure comment (PR or commit) before rethrowing.

## Architecture Diagram

```
run() in src/index.ts
├─► getActionConfig() [config.ts]
│ └─ validates vercelBuild XOR prebuilt ← FR-3
├─► [if config.vercelBuild]
│ └─► runVercelBuild(config) [NEW: vercel-build.ts]
│ ├─ exec: npx vercel pull --yes --environment=<target>
│ └─ exec: npx vercel build [--prod]
Comment thread
amondnet marked this conversation as resolved.
Outdated
│ ├─ throws BuildFailedError on failure (with stderr tail)
│ └─ on success: returns { prebuilt: true, vercelOutputDir }
│ └─► config = { ...config, prebuilt: true, vercelOutputDir }
├─► createVercelClient(config) [vercel.ts] ← unchanged
│ └─ chooses API or CLI client
└─► client.deploy(config, ctx) [vercel-api.ts] ← unchanged
└─ uses existing prebuilt upload path

[catch BuildFailedError]
└─► createBuildFailureComment(...) [github-comments.ts]
```

## Tasks

- [ ] T001 Add `vercel-build` input to action.yml and `vercelBuild` field to `ActionConfig` type (file: action.yml, src/types.ts)
- [ ] T002 [P] Add unit tests for `vercel-build` input parsing in `getActionConfig()` (file: src/__tests__/config.test.ts) (depends on T001)
- [ ] T003 Parse `vercel-build` input in `getActionConfig()` and enforce mutual exclusivity with `prebuilt` (file: src/config.ts) (depends on T002)
- [ ] T004 [P] Add unit tests for `BuildFailedError` class (file: src/__tests__/vercel-build.test.ts)
- [ ] T005 Implement `BuildFailedError` class with stderr tail capture (file: src/vercel-build.ts) (depends on T004)
- [ ] T006 [P] Add unit tests for `runVercelPull()` and `runVercelBuild()` exec wrappers — covers happy path, scope propagation, build-env, target=production, exit-code failure (file: src/__tests__/vercel-build.test.ts) (depends on T005)
- [ ] T007 Implement `runVercelPull()` and `runVercelBuild()` using `@actions/exec` with streamed stdout/stderr listeners (file: src/vercel-build.ts) (depends on T006)
- [ ] T008 [P] Add unit tests for `runBuildStep()` orchestrator — verifies pull → build sequencing, returns `{ prebuilt, vercelOutputDir }`, propagates `BuildFailedError` (file: src/__tests__/vercel-build.test.ts) (depends on T007)
- [ ] T009 Implement `runBuildStep(config)` orchestrator that calls pull + build and returns updated config fragment (file: src/vercel-build.ts) (depends on T008)
- [ ] T010 [P] Add tests for build-failure comment helpers — verifies truncated tail formatting and PR/commit dispatch (file: src/__tests__/github-comments.test.ts) (depends on T009)
- [ ] T011 Add `createBuildFailureCommentOnPullRequest()` and `createBuildFailureCommentOnCommit()` helpers (file: src/github-comments.ts) (depends on T010)
- [ ] T012 [P] Add tests for `run()` integration — `vercel-build: true` invokes build runner before deploy and routes through prebuilt path; build failure posts comment and exits non-zero (file: src/__tests__/index.test.ts or new src/__tests__/run-build.test.ts) (depends on T011)
- [ ] T013 Wire `runBuildStep()` into `run()` in src/index.ts: call before `createVercelClient`, mutate config to `prebuilt = true`, catch `BuildFailedError` to post comment then rethrow (file: src/index.ts) (depends on T012)
- [ ] T014 [P] Add integration test using emulate.dev that verifies `vercel-build: true` flow end-to-end against a fixture project with `.vercel/output` (file: src/__integration__/vercel-build.test.ts) (depends on T013)
- [ ] T015 Update README.md with `vercel-build` input documentation, usage example, and the official Vercel KB workflow reference (file: README.md) (depends on T013)
- [ ] T016 Rebuild dist/ via `pnpm build` and commit the bundled output (file: dist/index.js) (depends on T015)

## Dependencies

```
T001 ─► T002 ─► T003
T004 ─► T005 ─────┤
T006 ─► T007 ─► T008 ─► T009 ─► T010 ─► T011 ─► T012 ─► T013 ─┬─► T014
├─► T015 ─► T016
```

`[P]` markers indicate test-first parallel-capable tasks within their dependency cluster (RED phase can be authored while previous GREEN is being verified, but the implementation task immediately following each `[P]` test must wait for the test to be in place).

## Key Files

| Path | Role |
|---|---|
| `action.yml` | Add `vercel-build` input definition |
| `src/types.ts` | Add `vercelBuild: boolean` to `ActionConfig` |
| `src/config.ts` | Parse input + enforce mutual exclusivity in `getActionConfig()` |
| `src/vercel-build.ts` | **NEW**: `BuildFailedError`, `runVercelPull()`, `runVercelBuild()`, `runBuildStep()` |
| `src/index.ts` | Wire `runBuildStep()` into `run()` orchestrator and handle `BuildFailedError` |
| `src/github-comments.ts` | Add build-failure comment helpers |
| `src/__tests__/config.test.ts` | Cover input parsing + mutual exclusivity |
| `src/__tests__/vercel-build.test.ts` | **NEW**: Unit tests for build runner |
| `src/__tests__/github-comments.test.ts` | Cover new comment helpers |
| `src/__tests__/index.test.ts` (or new `run-build.test.ts`) | Cover `run()` integration |
| `src/__integration__/vercel-build.test.ts` | **NEW**: Emulate-based end-to-end test |
| `README.md` | Document new input + example workflow |
| `dist/index.js` | Rebuilt bundle (committed) |

## Verification

Manual verification (run after T013):

1. **Happy path (preview)**: In `example/nextjs`, set `vercel-build: 'true'`, `prebuilt: 'false'`. Run the action against a real Vercel project (preview token). Expect: log shows `vercel pull` then `vercel build` invocations, `.vercel/output` is uploaded, deployment URL returned.
2. **Happy path (production)**: Set `target: production` + `vercel-build: 'true'`. Expect: `vercel pull --environment=production` and `vercel build --prod` invoked.
3. **Mutual exclusivity**: Set `vercel-build: 'true'` AND `prebuilt: 'true'`. Expect: action fails immediately with a clear conflict message; no API calls made.
4. **Build failure**: In a fixture project, introduce a syntax error so `vercel build` fails. Expect: action exits non-zero, GitHub Actions log shows the build error, PR comment is posted with truncated tail (when `github-comment: true`).
5. **Backward compatibility**: With `vercel-build` unset (or `false`), run an existing-style deployment. Expect: identical behavior to current main branch — no `vercel pull`/`vercel build` invocations.
6. **Token redaction**: Inspect logs from steps 1–4. The Vercel token MUST NOT appear in any log line, comment, or error message.

Automated:
- `pnpm test` — all unit tests green
- `pnpm test --coverage` — coverage for new code > 80%
- `pnpm run lint` — no errors
- `pnpm run build` — clean build, no warnings

## Progress

(populated by /please:implement during execution)

## Decision Log

- **2026-04-30 — Approach A (new module) chosen over embedding in VercelApiClient.** Reason: separation of concerns, AGENTS.md compliance (boundary isolation), reuse of existing prebuilt deploy path with zero modification.
- **2026-04-30 — `@actions/exec` chosen over SDK.** Reason: `@vercel/client` v17 does not expose `build`/`pull` primitives; the spec's "SDK if available, else exec" condition resolves to "exec" deterministically.
- **2026-04-30 — Mutual exclusivity validated in `getActionConfig()`.** Reason: fail-fast at the input boundary before any network or filesystem I/O.

## Surprises & Discoveries

(populated by /please:implement)
65 changes: 65 additions & 0 deletions .please/docs/tracks/active/auto-vercel-build-20260430/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
product_spec_domain: deployment/vercel-build
---

# Local Vercel Build Step (`vercel-build` input)

> Track: auto-vercel-build-20260430

## Overview

Add a new `vercel-build` action input that, when set to `true`, instructs the action to execute the official Vercel CLI workflow locally before deployment: `vercel pull --environment=<target>` → `vercel build` → upload `.vercel/output` via the existing prebuilt deploy path. This mirrors the recommended workflow in the [Vercel KB GitHub Actions guide](https://vercel.com/kb/guide/how-can-i-use-github-actions-with-vercel#configuring-github-actions-for-vercel) and lets users build inside CI (with their own secrets and runtime) instead of relying on Vercel's remote build.

The current default behavior (uploading source via `@vercel/client` for remote build) is preserved when `vercel-build` is `false` (the default), maintaining backward compatibility with existing pipelines.

## Requirements

### Functional Requirements

- [ ] FR-1: Add a new boolean action input `vercel-build` to `action.yml` (default: `false`).
- [ ] FR-2: When `vercel-build: true` and `prebuilt: false`, the action MUST execute, in order, in the configured `working-directory`:
1. `vercel pull --yes --environment=<target> --token=<vercel-token>` (where `<target>` is `production` or `preview` based on the existing `production`/`target` inputs)
2. `vercel build [--prod] --token=<vercel-token>` (the `--prod` flag is included only when targeting production)
Comment thread
amondnet marked this conversation as resolved.
Outdated
3. Treat the resulting `.vercel/output` directory as the deploy artifact, using the existing prebuilt deploy code path (`config.prebuilt = true`, `vercelOutputDir` defaulted to `<working-directory>/.vercel/output`).
Comment thread
amondnet marked this conversation as resolved.
Outdated
- [ ] FR-3: When `vercel-build: true` AND `prebuilt: true`, the action MUST fail fast with a clear error: the two flags are mutually exclusive (prebuilt means the user already built; `vercel-build: true` asks the action to build).
- [ ] FR-4: When `vercel-build: false` (default), preserve the current source-upload behavior — no behavior change for existing users.
- [ ] FR-5: The `vercel pull` and `vercel build` commands MUST be invoked programmatically through the `@vercel/client` SDK if it exposes a build/pull API; otherwise fall back to executing the bundled `vercel` package via `@actions/exec`. (Investigation of `@vercel/client` capability happens in the plan phase.)
- [ ] FR-6: If the team scope is configured (`vercel-org-id` / `--scope`), it MUST be propagated to both `vercel pull` and `vercel build` invocations.
- [ ] FR-7: Build environment variables (`build-env`) MUST be available to the local `vercel build` execution.
- [ ] FR-8: On `vercel build` failure, the action MUST:
- Fail the action with a non-zero exit code,
- Stream/capture build stdout+stderr into the GitHub Actions log,
- Post a comment on the PR/commit (when comments are enabled) summarizing the failure with a truncated tail of build output.

### Non-functional Requirements

- [ ] NFR-1: No breaking change to existing inputs or default behavior — existing workflows continue to work unchanged.
- [ ] NFR-2: Build output streaming MUST not buffer the entire log in memory (use streamed exec).
- [ ] NFR-3: Vercel token MUST never appear in logs, comments, or error messages (existing secret-masking guarantees apply).
- [ ] NFR-4: Test coverage for new code MUST exceed 80% (per `workflow.md`).

## Acceptance Criteria

- [ ] AC-1: Setting `vercel-build: true` with `prebuilt: false` runs `vercel pull` then `vercel build` in `working-directory`, then deploys `.vercel/output` as a prebuilt deployment, and the resulting deployment URL is returned via the `preview-url` output.
- [ ] AC-2: Setting `vercel-build: true` with `prebuilt: true` fails the action immediately with a message identifying the conflict, and does not call any Vercel API.
- [ ] AC-3: With `vercel-build` unset or `false`, deployments produce identical behavior, payloads, and outputs to the current implementation (verified via existing integration tests).
- [ ] AC-4: When `vercel build` fails, the action exits non-zero, the failure is visible in the GitHub Actions log, and a PR/commit comment containing the truncated tail of build output is posted (when comments are enabled).
- [ ] AC-5: Targeting production (`production: true`) passes `--prod` to `vercel build` and `--environment=production` to `vercel pull`; otherwise uses `preview`.
- [ ] AC-6: Build secrets (`build-env`) and team scope (`vercel-org-id`) are honored by the local `vercel build` execution.

## Out of Scope

- Auto-detection of when a build is needed (no inference; behavior is purely flag-driven).
- Caching `.vercel/output` between runs (tracked separately as a future optimization).
- Custom build commands beyond `vercel build` (users should configure `buildCommand` in `vercel.json`).
- Deprecating the current source-upload path (a future major version may revisit defaults; not part of this track).
- Running `vercel build` independently of deployment (no "build only" mode in this track).
- Changes to the legacy `vercel-args` CLI fallback path.

## Assumptions

- The `@vercel/client` SDK (v17.2.65, currently a dependency) is the preferred entry point. If it does not expose a build/pull primitive, the bundled `vercel` package (v50.0.0, already a dependency) will be invoked via `@actions/exec`. This decision is deferred to the plan phase.
- The `working-directory` input, after normalization in `parseWorkingDirectory()` (`src/config.ts`), is an absolute path — confirmed by existing gotcha #341. The new build step relies on this same guarantee.
- Existing prebuilt deploy code path (`vercel-api.ts:41-45`, `buildClientOptions`) correctly handles `.vercel/output` upload and only needs to be invoked after a successful local build.
- `production: true` is the existing convention for production deployments; the new build step infers `--prod` from this flag rather than introducing a new target input.
- Out-of-scope decisions in Section "Out of Scope" reflect the user's intent to keep this track tightly focused on the build-step feature; revisit in follow-up tracks if needed.
Loading