open-design/packages
Nagendhra Madishetti 8897cb85be
feat(deploy): add /api/projects/:id/deploy/preflight for pre-upload inspection (#320)
* feat(deploy): add /api/projects/:id/deploy/preflight for pre-upload inspection

Today the deploy flow is a single POST that builds the file set, ships
it to Vercel, and waits for the public URL to come up. The user has no
visibility into what is actually being uploaded until the operation
either succeeds or fails with a generic 400. There is no way to tell
"my background image is missing" from "my deploy will exceed Vercel
quotas" without rolling the dice and waiting up to ~110 seconds.

Add a preflight endpoint that builds the plan, runs an analyzer, and
returns a typed report without touching Vercel. The endpoint is purely
additive, costs no network round-trips, and gives the UI everything
needed to render a confirm-before-deploy summary.

Implementation:

- packages/contracts/src/api/projects.ts: new public types
  DeployPreflightRequest, DeployPreflightResponse, DeployPreflightFile,
  DeployPreflightWarning, and the closed enum DeployPreflightWarningCode
  with values broken-reference, invalid-reference, large-asset,
  large-bundle, large-html, external-script, external-stylesheet,
  no-doctype, no-viewport.

- apps/daemon/src/deploy.ts: refactor buildDeployFileSet so the
  walk-and-collect logic lives in a non-throwing buildDeployFilePlan
  that returns { entryPath, html, files, missing, invalid }.
  buildDeployFileSet keeps its existing semantics by delegating to the
  plan and throwing when missing or invalid is non-empty, so every
  current caller and test is unchanged. Add analyzeDeployPlan that
  walks the entry HTML once via parseHtmlTags + parseHtmlAttributes
  (no new parser) and emits the warning vocabulary above. Add
  prepareDeployPreflight that combines the plan and the analyzer and
  returns the public DeployPreflightResponse shape. Soft thresholds
  (4 MiB per asset, 75 MiB bundle, 1 MiB entry HTML) are exported as
  DEPLOY_PREFLIGHT_LARGE_*_BYTES constants so tests and future tuning
  use the same numbers.

- apps/daemon/src/server.ts: new route POST /api/projects/:id/deploy/
  preflight. Same body shape as POST /deploy ({ fileName, providerId? }).
  Returns DeployPreflightResponse on success. Validation errors map to
  BAD_REQUEST, missing entry file maps to FILE_NOT_FOUND, exactly like
  the existing deploy POST so the client error-handling stays uniform.

Tests (apps/daemon/tests/deploy.test.ts, 14 new cases under a new
"deploy plan and analyzer" describe block):

- buildDeployFilePlan returns files plus missing and invalid lists
  without throwing.
- buildDeployFileSet still throws on missing/invalid, preserving the
  pre-refactor contract.
- Analyzer unit tests for broken-reference, invalid-reference,
  no-doctype, no-viewport, external-script, external-stylesheet,
  protocol-relative external script, large-asset (per-file but not
  entry HTML), large-html (entry HTML threshold), and a healthy-input
  case that produces zero warnings.
- Preflight integration tests: full payload shape (provider, entry,
  files, totals, warnings) on a healthy project, broken-reference path
  on a missing asset, and a non-HTML entry rejection.

Net diff: +439 / -8 across 4 files. No new dependencies, no public API
removals. The existing POST /deploy flow is byte-for-byte unchanged
because buildDeployFileSet is a thin wrapper around the new plan.

* fix(deploy): preflight review fixes (P2 doctype anchoring, P2 large-html source path, P3 logging, P3 jsdoc)

Address review feedback on PR #320:

P2 (Codex bot): the no-doctype check used /<!doctype\s+html/i with no
anchor, so a `<!doctype html>` substring inside a <script> template
literal or HTML comment would mask a missing real declaration. Switch
to `new RegExp('^\uFEFF?\s*(?:<!--[\s\S]*?-->\s*)*<!doctype\s+html', 'i')`
which anchors to the document prolog and accepts the HTML5-permitted
optional BOM, comments, and whitespace before the doctype.

P2 (Codex bot): the large-html warning hardcoded `path: 'index.html'`,
but `entry` in the preflight payload is the source project file (e.g.
`pages/landing.html`) and other warnings already use source paths.
Switch the warning path to `entryPath` so file-targeted UI actions
deep-link back to a real project file.

P3 (lefarcen): add a JSDoc block alongside the inline TS annotation
on analyzeDeployPlan so the function follows the JSDoc-first style
of the rest of the file. The TS inline annotation stays because
deploy.ts uses `// @ts-nocheck`, which makes JSDoc `@param`/`@returns`
non-authoritative for callers; both forms together give docs and
correct caller-side types.

P3 (lefarcen): the preflight route catch block now logs non-DeployError
exceptions via console.error before sending the generic 400, so
unexpected failures show up in the daemon log without leaking
internals to the client.

Tests:
- analyzeDeployPlan flags missing doctype even when a fake `<!doctype>`
  lives inside a <script> string literal.
- analyzeDeployPlan accepts a doctype that follows a leading HTML
  comment and BOM (HTML5 prolog forms).
- analyzeDeployPlan reports large-html against the source entry path
  (e.g. `pages/landing.html`), not the deploy-renamed `index.html`.

38 deploy tests pass, 264 across the daemon package, typecheck clean.

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-03 09:00:46 +08:00
..
contracts feat(deploy): add /api/projects/:id/deploy/preflight for pre-upload inspection (#320) 2026-05-03 09:00:46 +08:00
platform release: Open Design 0.2.0 (#297) 2026-05-02 22:28:59 +08:00
sidecar release: Open Design 0.2.0 (#297) 2026-05-02 22:28:59 +08:00
sidecar-proto release: Open Design 0.2.0 (#297) 2026-05-02 22:28:59 +08:00
AGENTS.md Refresh desktop integration control plane (#123) 2026-04-30 14:23:53 +08:00