Skip to content

feat(cloudflare): move deploy command to cloudflare package - #2405

Merged
james-elicx merged 2 commits into
mainfrom
codex/cloudflare-deploy-command
Jun 29, 2026
Merged

james-elicx merged 2 commits into
mainfrom
codex/cloudflare-deploy-command

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • move the Cloudflare deploy implementation and CLI into @vinext/cloudflare
  • keep vinext deploy as a deprecated compatibility shim that warns and delegates
  • move shared project detection/config helpers into core vinext/utils/project
  • keep Cloudflare deploy internals exported only under @vinext/cloudflare/internal/*

Validation

  • vp check vite.config.ts packages/cloudflare/package.json packages/vinext/package.json packages/vinext/vite.config.ts packages/vinext/src/cli.ts packages/vinext/src/index.ts packages/vinext/src/init.ts packages/vinext/src/init-cloudflare.ts packages/vinext/src/cloudflare/index.ts packages/vinext/src/utils/project.ts tsconfig.json packages/cloudflare/src/cli.ts packages/cloudflare/src/deploy.ts packages/cloudflare/src/deploy-config.ts packages/cloudflare/src/tpr.ts tests/deploy.test.ts tests/tpr-kv-keys.test.ts
  • vp test run tests/deploy.test.ts tests/tpr-kv-keys.test.ts
  • vp run @vinext/cloudflare#build
  • vp run vinext#build
  • node packages/cloudflare/dist/cli.js deploy --help
  • node packages/vinext/dist/cli.js deploy --help
  • npx --yes ./packages/cloudflare deploy --help
  • manifest/import sanity checks

Note: vp run vinext#build still emits the existing virtual-module externalization warnings, but exits successfully.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2405
npm i https://pkg.pr.new/vinext@2405

commit: 4c45345

@github-actions

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@james-elicx james-elicx changed the title Move deploy command to @vinext/cloudflare feat(cloudflare): move deploy command to cloudflare package Jun 29, 2026
@github-actions

github-actions Bot commented Jun 29, 2026 •

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 49989fb against base 23ed801 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 124.0 KB 124.0 KB ⚫ +0.0%
Client entry size (gzip) vinext 118.8 KB 118.8 KB ⚫ -0.0%
Dev server cold start vinext 2.49 s 2.52 s ⚫ +1.4%
Production build time vinext 3.00 s 3.00 s ⚫ -0.2%
RSC entry closure size (gzip) vinext 95.2 KB 95.2 KB ⚫ -0.0%
Server bundle size (gzip) vinext 161.5 KB 161.5 KB ⚫ -0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review: Move deploy command to @vinext/cloudflare

Reviewed the full diff, ran the targeted tests, type-checked the changed files, built both packages, and exercised both CLIs from dist. This is a clean, well-executed refactor. No blocking issues found.

What I verified

Bundling / dependency graph (the main risk in this PR)

  • packages/vinext/src/cli.ts:31 now does a static, top-level import of @vinext/cloudflare/internal/deploy, and @vinext/cloudflare is only a devDependency of vinext. On its own that would break vinext deploy for consumers — but packages/vinext/vite.config.ts aliases @vinext/cloudflare/internal (and the vinext/* subpaths the cloudflare source imports) to local source, so the deploy/tpr/deploy-config code is bundled into vinext's dist. Confirmed:
    • dist/cli.js has no residual @vinext/cloudflare import (only doc/warning strings).
    • The deploy code is emitted as a co-located chunk at dist/packages/cloudflare/src/deploy.js, imported via a relative path, and its transitive imports (../../../utils/project.js, ./cache/kv-data-adapter.runtime.js, etc.) all resolve inside dist.
    • node packages/vinext/dist/cli.js deploy --help → exit 0.
  • The reverse direction is correct too: @vinext/cloudflare builds with skipNodeModulesBundle: true, so its dist/deploy.js keeps bare vinext/build/run-prerender etc. as externals, resolved via the vinext peer dependency. node packages/cloudflare/dist/cli.js deploy --help and --version → exit 0.

Export surface

  • Every vinext/* subpath imported by the relocated cloudflare source (server/headers, server/isr-cache, server/app-page-cache, build/run-prerender, config/dotenv, config/next-config, utils/project) was added to packages/vinext/package.json exports. ✅
  • @vinext/cloudflare adds ./internal/{deploy,deploy-config,tpr} exports and a bin. ✅

utils/project.ts merge

  • The deleted cloudflare/project.ts was merged into utils/project.ts alongside the pre-existing helpers. Diffed old vs. new exports — all prior utils/project.ts exports are preserved, plus the moved detectProject/getMissingDeps/formatMissingCloudflarePluginError/hasWranglerConfig/ProjectInfo/isPackageResolvable. No dropped symbols.

Duplicated helper

  • parsePositiveIntegerArg was copied into packages/cloudflare/src/deploy.ts (previously imported from vinext's cli-args.ts). The copy is byte-for-byte identical to the original, so no behavior drift. Minor maintenance smell (two copies that can diverge), but acceptable given it avoids exporting another vinext internal.

Backward compat

  • vinext/cloudflare entry still re-exports KVCacheHandler / runTPR (now via the bundled @vinext/cloudflare/internal/tpr); built dist/cloudflare/index.js resolves to local chunks. ✅
  • vinext deploy retained as a deprecated shim that warns and delegates. Help text updated to reflect validation (not generation) behavior. ✅

No stale references to the old paths (cloudflare/project, cloudflare/tpr, cloudflare/deploy-config, src/deploy.js) anywhere outside dist.

Tests / checks run

  • vp test run tests/deploy.test.ts tests/tpr-kv-keys.test.ts → 270 passed
  • vp check on all changed source files → format + lint + types clean
  • vp run @vinext/cloudflare#build and vp run vinext#build → both succeed (vinext build still emits the pre-existing virtual-module externalization warnings, unrelated to this PR)

Non-blocking suggestions

  1. parsePositiveIntegerArg duplication — consider exporting it from a shared vinext/utils/* subpath instead of copying, so the two implementations can't drift. Low priority.
  2. npx @vinext/cloudflare deploy relies on npx's single-bin fallback since the bin is named vinext-cloudflare (not matching the scoped package name). This works today and the PR validated npx --yes ./packages/cloudflare deploy --help, but worth a smoke check against the published tarball at release time.

LGTM.

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Posted the review to PR #2405. Summary of findings:

Review outcome: LGTM, no blocking issues

This is a clean refactor moving the Cloudflare deploy implementation/CLI into @vinext/cloudflare, keeping vinext deploy as a deprecated delegating shim, and consolidating shared project helpers into vinext/utils/project.

The one risk worth scrutiny — vinext/src/cli.ts statically imports @vinext/cloudflare/internal/deploy, while @vinext/cloudflare is only a devDependency of vinext. I confirmed this is safe: the vite config aliases @vinext/cloudflare/internal (and the cloudflare source's vinext/* imports) to local source, so the deploy code is bundled into vinext's dist rather than externalized. Verified by building and running node dist/cli.js deploy --help.

Verified end-to-end:

  • Both packages build successfully
  • Both CLIs run from dist (exit 0)
  • All bundled/external import paths resolve correctly in both directions (vinext bundles cloudflare; cloudflare externalizes vinext via peer dep)
  • All required export subpaths added to both package.jsons
  • utils/project.ts merge preserves every prior export
  • deploy.test.ts + tpr-kv-keys.test.ts: 270 passed
  • vp check clean on all changed files
  • No stale references to old paths

Two non-blocking notes: parsePositiveIntegerArg is now duplicated (identical copy, but can drift), and npx @vinext/cloudflare deploy relies on npx's single-bin fallback (works, worth a release-time smoke check).

github run

@james-elicx
james-elicx force-pushed the codex/cloudflare-deploy-command branch 4 times, most recently from 5f115d0 to 0e90a1c Compare June 29, 2026 15:44
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review: Move deploy command to @vinext/cloudflare

Reviewed the full diff, rebuilt both packages, ran the targeted tests, type-checked the changed source and test files, and exercised both CLIs from dist (including npx --yes ./packages/cloudflare). This is a clean, well-scoped refactor. No blocking issues found.

Verified

Bundling / dependency graph (the main risk)

  • vinext/src/cli.ts now statically imports @vinext/cloudflare/internal/deploy and /internal/deploy-help, while @vinext/cloudflare is only a devDependency of vinext. This is safe because packages/vinext/vite.config.ts aliases @vinext/cloudflare/internal and the vinext/* subpaths the cloudflare source imports back to local source, so the deploy/tpr/deploy-config code is bundled into vinext's dist. Confirmed dist/cli.js has no residual @vinext/cloudflare import (only the deprecation/help strings), the deploy code is emitted at dist/packages/cloudflare/src/deploy.js with all transitive imports rewritten to relative paths that exist in dist, and node dist/cli.js deploy --help → exit 0 with the deprecation warning.
  • Reverse direction is correct: @vinext/cloudflare builds unbundle + skipNodeModulesBundle, so dist/deploy.js/tpr.js keep vinext/build/run-prerender, vinext/server/headers, etc. as bare externals resolved via the vinext peer dependency. node packages/cloudflare/dist/cli.js deploy --help and --help/--version → exit 0. npx --yes ./packages/cloudflare deploy --help → exit 0.

Export surface — every vinext/* subpath the relocated cloudflare source imports (server/headers, server/isr-cache, server/app-page-cache, build/run-prerender, config/dotenv, config/next-config, utils/project) is added to packages/vinext/package.json exports, and @vinext/cloudflare adds ./internal/{deploy,deploy-help,deploy-config,tpr} plus the vinext-cloudflare bin. The tpr.ts import of @vinext/cloudflare/cache/kv-data-adapter.runtime is covered by the existing ./cache/* wildcard export.

utils/project.ts merge — the deleted cloudflare/project.ts was merged in; formatMissingCloudflarePluginError was relocated here from deploy-config.ts, and index.ts/deploy.ts both import it from the new home. findViteConfigPath (used by deploy-config.ts) is exported. No prior utils/project.ts exports dropped.

No stale references to old paths (cloudflare/project, cloudflare/tpr, cloudflare/deploy-config, src/deploy.js) anywhere outside the test that correctly points at the new location.

parsePositiveIntegerArg — correctly de-exported in cli-args.ts (internal use intact, no other importers) and copied byte-for-byte into cloudflare/src/deploy.ts.

scripts/e2e-deploy.sh — changes are cosmetic log-string renames only; the harness uses vinext build + vinext start, not vinext deploy, so the new deprecation warning doesn't affect it. The matching assertion in e2e-deploy-script.test.ts was updated.

Checks run

  • vp test run tests/deploy.test.ts tests/tpr-kv-keys.test.ts → 270 passed
  • vp check on all changed source + test files → format + lint + types clean
  • vp run @vinext/cloudflare#build and vp run vinext#build → both succeed

One thing to watch (non-blocking)

  • Self-referential bare imports inside vinext. Several vinext source files were switched from relative imports (./utils/project.js) to bare self-imports (vinext/utils/project) — e.g. build/report.ts, build/run-prerender.ts, check.ts, typegen.ts, init.ts, init-cloudflare.ts, index.ts. These only resolve because of the new vite.config alias + tsconfig path + package.json export triple. It works today and is verified by the build, but it couples ordinary intra-package imports to the alias config; a missing alias in a future tooling context (or a consumer's bundler) would break them where a relative import would not. Consider keeping intra-package imports relative and reserving the bare vinext/utils/project specifier for the cloudflare package that genuinely crosses the package boundary.
  • npx @vinext/cloudflare deploy relies on npx's single-bin fallback since the bin is vinext-cloudflare (not matching the scoped package name). Works against the local package; worth a smoke check against the published tarball at release time.

LGTM.

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Posted the review to PR #2405.

Review outcome: LGTM, no blocking issues

Clean refactor moving the Cloudflare deploy implementation/CLI into @vinext/cloudflare, keeping vinext deploy as a deprecated delegating shim, and consolidating shared project helpers into vinext/utils/project.

Verified end-to-end (rebuilt both packages first — initial node packages/cloudflare/dist/cli.js deploy --help failed only due to a stale vinext/dist; after vp run vinext#build it works):

  • Both packages build; both CLIs run from dist (exit 0), including npx --yes ./packages/cloudflare deploy --help
  • vinext's dist/cli.js bundles the cloudflare deploy code (no residual @vinext/cloudflare imports, only doc strings); all rewritten relative imports resolve in dist
  • Reverse direction externalizes vinext/* via the peer dep
  • All required export subpaths added to both package.jsons; tpr.ts's cache/kv-data-adapter.runtime covered by the ./cache/* wildcard
  • utils/project.ts merge preserves all prior exports; formatMissingCloudflarePluginError correctly relocated
  • deploy.test.ts + tpr-kv-keys.test.ts: 270 passed; vp check clean on all changed source + test files
  • No stale path references; e2e-deploy.sh changes are cosmetic and don't hit the deprecated shim

Two non-blocking notes: several vinext files switched to self-referential bare imports (vinext/utils/project) that only resolve via the alias/tsconfig/export triple — fine today, but consider keeping intra-package imports relative; and npx @vinext/cloudflare deploy relies on npx's single-bin fallback (bin is vinext-cloudflare), worth a release-time smoke check.

github run

@james-elicx
james-elicx force-pushed the codex/cloudflare-deploy-command branch from 0e90a1c to 5cc3ee7 Compare June 29, 2026 15:58
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review: Move deploy command to @vinext/cloudflare

Reviewed the full diff, rebuilt @vinext/cloudflare, and traced runtime path resolution. The refactor is clean overall, but there is one blocking bug in the relocated TPR code.

🔴 Blocking: TPR startLocalServer resolves prod-server.js to a non-existent path

packages/cloudflare/src/tpr.ts:616:

const prodServerPath = path.resolve(import.meta.dirname, "..", "server", "prod-server.js");

This path was correct when tpr.ts lived in vinext at src/cloudflare/tpr.ts (built to dist/cloudflare/tpr.js, so ../server/prod-server.js → vinext/dist/server/prod-server.js ✅). After the move into @vinext/cloudflare, the path was not updated.

@vinext/cloudflare builds with unbundle: true, so src/tpr.ts → dist/tpr.js. At runtime import.meta.dirname is <cloudflare>/dist, and the expression resolves to:

<cloudflare>/server/prod-server.js   ← does not exist

There is no server/ directory in @vinext/cloudflare's dist — prod-server.js lives in the vinext package (vinext/server/prod-server, a declared peer dependency). I verified against the actual build:

$ vp run @vinext/cloudflare#build
$ ls packages/cloudflare/dist/server
ls: cannot access 'packages/cloudflare/dist/server': No such file or directory

# built dist/tpr.js still has:
const prodServerPath = path.resolve(import.meta.dirname, "..", "server", "prod-server.js");
# → resolves to packages/cloudflare/server/prod-server.js (Exists: false)

Impact: when --experimental-tpr runs with all prerequisites met (CLOUDFLARE_API_TOKEN, custom domain, KV namespace, traffic data), startLocalServer spawns a subprocess that imports a non-existent file. The subprocess exits 1, waitForServer times out after 30s, every page fails to render, and TPR silently reports "all pages failed to pre-render (request-dependent?)" — masking the real cause. The feature is effectively broken end-to-end on this branch.

Why tests didn't catch it: deploy.test.ts / tpr-kv-keys.test.ts only exercise pure functions (buildTprKVPairs, domainCandidates, parseWranglerConfig). Nothing covers startLocalServer / prerenderRoutes, so the broken spawn path has no coverage.

Suggested fix: resolve vinext/server/prod-server from the project root via the peer dependency, mirroring how deploy.ts already resolves vite/wrangler (packages/cloudflare/src/deploy.ts:170, :229):

import { createRequire } from "node:module";
// inside startLocalServer (root is available):
const req = createRequire(path.join(root, "package.json"));
const prodServerPath = req.resolve("vinext/server/prod-server");

vinext exports ./server/prod-server (packages/vinext/package.json:32), and @vinext/cloudflare already declares vinext as a peer dependency, so this resolves correctly in a real install. A focused test that asserts the resolved path exists (or mocks the spawn target) would prevent regressions.

Non-blocking

  • parsePositiveIntegerArg duplication — copied byte-for-byte into cloudflare/src/deploy.ts and de-exported from vinext's cli-args.ts. Fine for now, but the two copies can drift. Note parseDeployArgs uses parsePositiveIntegerArg only for --prerender-concurrency while --tpr-* flags use the looser local parseIntArg (allows non-positive / calls process.exit); the inconsistency predates this PR but is now duplicated.
  • npx @vinext/cloudflare deploy relies on npx's single-bin fallback since the bin is vinext-cloudflare, not matching the scoped package name. Works against the local package; worth a smoke check against the published tarball at release time.

Everything else (export surface, utils/project.ts merge, vinext deploy deprecation shim, alias/tsconfig/path triple for the bundled-into-vinext direction, deploy-config relocation) checks out. Once the prod-server.js resolution is fixed, this is good to go.

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Posted the review to PR #2405.

Review outcome: one blocking bug found

🔴 Blocking — TPR startLocalServer resolves prod-server.js to a non-existent path (packages/cloudflare/src/tpr.ts:616)

When tpr.ts moved from vinext/src/cloudflare/ into @vinext/cloudflare/src/, this line was left unchanged:

const prodServerPath = path.resolve(import.meta.dirname, "..", "server", "prod-server.js");

@vinext/cloudflare builds unbundle: true, so dist/tpr.js resolves this to packages/cloudflare/server/prod-server.js — which doesn't exist. prod-server.js lives in the vinext package (vinext/server/prod-server, a peer dep). I verified against the actual build: there's no server/ dir in cloudflare's dist, and the resolved path returns Exists: false.

Impact: --experimental-tpr is broken end-to-end. When prerequisites are met, the spawned subprocess imports a missing file, waitForServer times out after 30s, and TPR silently reports "all pages failed to pre-render," masking the real cause. No test covers startLocalServer/prerenderRoutes, so the existing 270 passing tests don't catch it.

Suggested fix: resolve via the peer dependency, mirroring how deploy.ts already resolves vite/wrangler:

const req = createRequire(path.join(root, "package.json"));
const prodServerPath = req.resolve("vinext/server/prod-server");

Two non-blocking notes (parsePositiveIntegerArg duplication + inconsistent --tpr-* int parsing; npx single-bin fallback). Everything else — export surface, utils/project.ts merge, deprecation shim, alias/tsconfig/path triple — checks out.

Note: the two prior ask-bonk reviews on this PR marked it LGTM and missed this; the bug only manifests at TPR deploy time, which isn't exercised by the build or test suite.

github run

@james-elicx
james-elicx force-pushed the codex/cloudflare-deploy-command branch from 5cc3ee7 to f1d2316 Compare June 29, 2026 16:07
@james-elicx

Copy link
Copy Markdown
Member Author

Fixed the TPR blocker: startLocalServer now resolves vinext/server/prod-server through the app root's installed vinext peer with createRequire, and the subprocess imports it via a proper file URL. Added a regression test covering peer dependency resolution.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk 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.

Review: Move deploy command to @vinext/cloudflare

Reviewed the full diff at the current HEAD (which includes the TPR fix from the last comment), rebuilt both packages, ran the targeted tests, type-checked all changed source + test files, and exercised both CLIs from dist. The refactor is clean and the previously-reported blocking bug is now fixed. No blocking issues found.

The previously-blocking TPR bug is fixed ✅

packages/cloudflare/src/tpr.ts:642 now resolves prod-server through the app's installed vinext peer instead of a hardcoded relative path:

export function resolveVinextProdServerPath(root: string): string {
  return createRequire(path.join(root, "package.json")).resolve("vinext/server/prod-server");
}

I verified this against both built outputs:

  • packages/cloudflare/dist/tpr.js:395 keeps the createRequire(...).resolve("vinext/server/prod-server") form (resolves via the peer dep).
  • The bundled-into-vinext copy at packages/vinext/dist/packages/cloudflare/src/tpr.js:395 is identical, so vinext deploy --experimental-tpr also resolves correctly.
  • vinext exports ./server/prod-server (packages/vinext/package.json:32), and the new regression test (tests/tpr-kv-keys.test.ts:95) builds a fake app root with a vinext peer and asserts the resolved path — closing the coverage gap that let the bug ship.

Verified

Bundling / dependency graph (the main risk) — vinext/src/cli.ts:31-32 statically imports @vinext/cloudflare/internal/{deploy,deploy-help} while @vinext/cloudflare is only a devDependency of vinext. This is safe because packages/vinext/vite.config.ts:84-87 aliases @vinext/cloudflare/internal and @vinext/cloudflare/cache to local source, so the deploy/tpr/deploy-config code is bundled into vinext's dist. Confirmed:

  • dist/cli.js has no residual @vinext/cloudflare import (only deprecation/help strings).
  • The deploy code emits at dist/packages/cloudflare/src/{deploy,tpr,deploy-config,deploy-help}.js, and the vinext/internal/* imports are rewritten to relative paths (../../../server/headers.js, etc.) that all exist in dist.
  • node dist/cli.js deploy --help → exit 0 with the deprecation warning.
  • Reverse direction: @vinext/cloudflare builds unbundle, so dist/tpr.js/deploy.js keep vinext/internal/* and vinext/server/prod-server as bare externals resolved via the vinext peer dep; node packages/cloudflare/dist/cli.js deploy --help → exit 0.

Export surface — every vinext/internal/* subpath the relocated cloudflare source imports (server/headers, server/isr-cache, server/app-page-cache, build/run-prerender, config/dotenv, config/next-config, utils/project) is added to packages/vinext/package.json exports, and @vinext/cloudflare adds ./internal/{deploy,deploy-help,deploy-config,tpr} plus the vinext-cloudflare bin. The tpr.ts self-import of @vinext/cloudflare/cache/kv-data-adapter.runtime is covered by the ./cache/* wildcard.

utils/project.ts merge — all prior exports preserved; the moved helpers (detectProject, getMissingDeps, formatMissingCloudflarePluginError, findViteConfigPath, findInNodeModules, hasWranglerConfig, isPackageResolvable, ProjectInfo) are present and consumed correctly by deploy.ts/deploy-config.ts.

Backward compat — vinext/cloudflare still re-exports KVCacheHandler/runTPR (packages/vinext/dist/cloudflare/index.js resolves to bundled local chunks). The vinext deploy deprecated shim warns and delegates.

No stale references to old paths (cloudflare/project, cloudflare/tpr, cloudflare/deploy-config, src/deploy.js) anywhere outside the relocated source.

Checks run

  • vp test run tests/deploy.test.ts tests/tpr-kv-keys.test.ts → 271 passed (was 270; +1 from the new prod-server resolution test)
  • vp check on all changed source + test files → format + lint + types clean
  • vp run @vinext/cloudflare#build and vp run vinext#build → both succeed

Non-blocking notes

  1. parsePositiveIntegerArg duplication — copied byte-for-byte into cloudflare/src/deploy.ts:66 and de-exported from vinext's cli-args.ts (no remaining cross-importers). Fine for now, but the two copies can drift. Also note parseDeployArgs uses the strict parsePositiveIntegerArg only for --prerender-concurrency, while --tpr-* flags use the looser local parseIntArg (allows non-positive, calls process.exit) — pre-existing inconsistency, now duplicated.
  2. npx @vinext/cloudflare deploy relies on npx's single-bin fallback since the bin is named vinext-cloudflare, not matching the scoped package name. Works against the local package; worth a smoke check against the published tarball at release time.
  3. Self-referential bare imports inside vinext (vinext/internal/utils/project, etc.) only resolve via the vite alias + tsconfig path + package.json export triple. Verified by the build, but consider keeping intra-package imports relative and reserving the bare specifier for genuine cross-package boundaries.

LGTM.

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Posted the review to PR #2405.

Review outcome: LGTM, no blocking issues

I reviewed the full diff at current HEAD (which includes the TPR fix from the last comment), rebuilt both packages, ran targeted tests, type-checked all changed files, and exercised both CLIs from dist.

The previously-blocking TPR bug is fixed. packages/cloudflare/src/tpr.ts:642 now resolves prod-server via createRequire(...).resolve("vinext/server/prod-server") through the app's vinext peer dependency, mirroring how deploy.ts resolves vite/wrangler. Verified in both build outputs (the standalone cloudflare dist and the bundled-into-vinext copy), and the new regression test (tests/tpr-kv-keys.test.ts:95) covers it by building a fake app root and asserting the resolved path.

Verified end-to-end:

  • Both packages build; both CLIs run from dist (exit 0)
  • vinext bundles the cloudflare deploy code (no residual @vinext/cloudflare imports, only doc strings); all rewritten relative imports resolve in dist
  • Reverse direction externalizes vinext/internal/* and vinext/server/prod-server via the peer dep
  • All required export subpaths added to both package.jsons; utils/project.ts merge preserves every prior export
  • Backward-compat vinext/cloudflare re-exports resolve to bundled chunks
  • deploy.test.ts + tpr-kv-keys.test.ts: 271 passed (+1 from new test); vp check clean
  • No stale path references

Three non-blocking notes: parsePositiveIntegerArg duplication + inconsistent --tpr-* int parsing; npx @vinext/cloudflare deploy relies on npx's single-bin fallback; and the self-referential bare vinext/internal/* imports only resolve via the alias/tsconfig/export triple.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk 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.

Review: Move deploy command to @vinext/cloudflare

Reviewed at current HEAD (982b0e1, which adds the new fix(cloudflare): expose scoped deploy package bin commit on top of the previously-reviewed state). Rebuilt both packages, ran the targeted tests, and traced bin/import resolution. The TPR bug from the earlier round is fixed and the core refactor is sound, but the new bin-rename commit does not do what it intends and is a minor regression.

🟡 The @vinext/cloudflare bin key does not produce a @vinext/cloudflare executable

packages/cloudflare/package.json:11-13:

"bin": {
  "@vinext/cloudflare": "dist/cli.js"
},

Commit 982b0e1 changed the bin key from vinext-cloudflare → @vinext/cloudflare to "expose scoped deploy package bin." But npm and pnpm strip the scope from a scoped bin key, so the installed executable is named cloudflare, not @vinext/cloudflare. Verified against real installs of a package with exactly this bin block:

# npm
$ ls node_modules/.bin/
cloudflare
# pnpm
$ ls node_modules/.bin/
cloudflare

Implications:

  1. The intent isn't achieved. There is no @vinext/cloudflare binary on disk. npx @vinext/cloudflare deploy (the form used everywhere in the README) still works — but only via npx's single-bin fallback, exactly as it did with the old vinext-cloudflare name. So this commit changes nothing about the documented invocation.
  2. It's a net regression on the local-bin name. The resulting node_modules/.bin/cloudflare is a generic, collision-prone name (anything that wants a cloudflare command in .bin now conflicts), whereas the prior vinext-cloudflare was clear and namespaced. The previous review's non-blocking note about the single-bin fallback is unchanged by this commit — both before and after, npx @vinext/cloudflare deploy relies on the same fallback.

Suggested fix: revert the bin key to a real, scope-free command name (vinext-cloudflare, or vinext-deploy). A scoped key can't ever produce a scoped executable, so it only obscures the actual installed bin name. Keep the README's npx @vinext/cloudflare deploy guidance — that resolves the package and runs its single bin regardless of the bin's name.

Previously-blocking TPR bug is fixed ✅

packages/cloudflare/src/tpr.ts:642 now resolves prod-server through the app's vinext peer:

export function resolveVinextProdServerPath(root: string): string {
  return createRequire(path.join(root, "package.json")).resolve("vinext/server/prod-server");
}

Verified in both build outputs — the standalone packages/cloudflare/dist/tpr.js:395 and the bundled-into-vinext copy at packages/vinext/dist/packages/cloudflare/src/tpr.js:395 both keep the createRequire(...).resolve(...) form. The new regression test (tests/tpr-kv-keys.test.ts:95) builds a fake app root with a vinext peer and asserts the resolved path, closing the coverage gap.

Everything else checks out

  • Bundling / dependency graph. vinext/src/cli.ts:31-32 statically imports @vinext/cloudflare/internal/{deploy,deploy-help} while @vinext/cloudflare is only a devDependency of vinext. Safe: packages/vinext/vite.config.ts:85-86 aliases @vinext/cloudflare/internal + /cache to local source, so the deploy/tpr/deploy-config code is bundled into vinext's dist with vinext/internal/* imports rewritten to relative paths (../../../utils/project.js, ../../../build/run-prerender.js). Reverse direction externalizes vinext/internal/* and vinext/server/prod-server via the peer dep. node packages/vinext/dist/cli.js deploy --help and node packages/cloudflare/dist/cli.js deploy --help both → exit 0.
  • Export surface — every vinext/internal/* subpath the cloudflare source imports is added to packages/vinext/package.json exports; @vinext/cloudflare adds ./internal/{deploy,deploy-help,deploy-config,tpr}.
  • utils/project.ts merge — all prior exports preserved; the moved helpers (detectProject, getMissingDeps, formatMissingCloudflarePluginError, hasWranglerConfig, isPackageResolvable, findInNodeModules, ProjectInfo) present and consumed by deploy.ts/deploy-config.ts.
  • No stale references to old paths (cloudflare/project, cloudflare/tpr, vinext-cloudflare) anywhere in source/scripts/docs.

Checks run

  • vp test run tests/deploy.test.ts tests/tpr-kv-keys.test.ts → 271 passed
  • vp run @vinext/cloudflare#build and vp run vinext#build → both succeed

Non-blocking (carried from prior rounds)

  • parsePositiveIntegerArg duplicated byte-for-byte into cloudflare/src/deploy.ts:66; --prerender-concurrency uses the strict parser while --tpr-* flags use the looser local parseIntArg — pre-existing inconsistency, now duplicated.
  • Self-referential bare vinext/internal/* imports only resolve via the alias + tsconfig path + package.json export triple.

The only thing I'd act on before merge is the bin key — it can't produce the scoped name it's named for, and trades a clear vinext-cloudflare for a generic cloudflare.

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2405.

Review outcome: one minor regression found

This round adds a new commit (982b0e1 — "expose scoped deploy package bin") on top of the previously-reviewed state. The earlier TPR blocker is confirmed fixed, but the new bin-rename commit doesn't achieve its goal.

🟡 The @vinext/cloudflare bin key doesn't produce a @vinext/cloudflare executable (packages/cloudflare/package.json:11)

The commit changed the bin key from vinext-cloudflare → @vinext/cloudflare, but npm and pnpm strip the scope from scoped bin keys — I verified against real installs with both package managers that the installed binary is named cloudflare, not @vinext/cloudflare. So:

  • The intent (a scoped executable) is impossible; npx @vinext/cloudflare deploy still works only via npx's single-bin fallback, exactly as it did before.
  • It's a net regression: the local bin went from a clear vinext-cloudflare to a generic, collision-prone cloudflare.

Suggested fix: revert to a real scope-free command name (vinext-cloudflare), keep the README's npx @vinext/cloudflare deploy guidance.

Verified everything else:

  • TPR prod-server resolution fixed via createRequire(...).resolve("vinext/server/prod-server") in both build outputs; new regression test covers it
  • Bundling direction sound (vinext bundles cloudflare deploy code; reverse externalizes vinext/internal/* via peer dep); both CLIs run from dist (exit 0)
  • Export surface, utils/project.ts merge, no stale references
  • vp test run tests/deploy.test.ts tests/tpr-kv-keys.test.ts → 271 passed; both builds succeed

The bin key is the one thing worth acting on before merge.

github run

@james-elicx
james-elicx force-pushed the codex/cloudflare-deploy-command branch from 982b0e1 to 49989fb Compare June 29, 2026 16:33
@james-elicx

Copy link
Copy Markdown
Member Author

Fixed the bin regression from the last review: @vinext/cloudflare now keeps the package-runner bin and also exposes vinext-cloudflare for local .bin/vp exec usage. Updated CLI help, vinext deploy deprecation output, README/docs, and the TPR demo to use vp exec vinext-cloudflare deploy for Vite+ local execution.

/bigbonk review for issues

@ask-bonk ask-bonk 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.

Review: bin regression fix + deploy command rename

Reviewed the new fix(cloudflare): expose deploy cli bin commit (49989fb) at current HEAD. Rebuilt @vinext/cloudflare, ran the targeted tests, type-checked all changed files, and empirically verified bin resolution across npm/pnpm/vpx/vp exec. The previously-flagged bin regression is resolved. No blocking issues.

The bin regression is fixed ✅

packages/cloudflare/package.json:11-14 now ships both keys:

"bin": {
  "@vinext/cloudflare": "dist/cli.js",
  "vinext-cloudflare": "dist/cli.js"
}

I replicated the exact bin block in a throwaway package and installed it with both npm and pnpm. As the prior review noted, the scoped key @vinext/cloudflare is installed scope-stripped as a cloudflare binary — but because the vinext-cloudflare key is also present, the package-runner direction and the local-bin direction both work:

installed .bin/: cloudflare, vinext-cloudflare
vpx @vinext/cloudflare deploy   → runs (resolves by package name)
vpx vinext-cloudflare deploy    → runs
vp exec vinext-cloudflare deploy → runs (resolves the local bin)

So every documented invocation in this PR is accurate:

  • npx @vinext/cloudflare deploy — resolves by package name (npx single-bin/name fallback)
  • vpx @vinext/cloudflare deploy — same, verified against the real vpx
  • vp exec vinext-cloudflare deploy — runs the vinext-cloudflare local bin

vpx (Execute a command from a local or remote npm package) is the correct Vite+ analog of npx, and vp exec (Execute a command from local node_modules/.bin) is the correct analog for the locally-installed bin. The README/help wording matches the tools' actual semantics.

Verified

  • Built CLI — node packages/cloudflare/dist/cli.js --version → vinext-cloudflare v0.1.2; --help prints the new vinext-cloudflare name plus the Vite+ block. All help/usage strings consistently renamed from @vinext/cloudflare → vinext-cloudflare.
  • tpr-demo dependency wiring — examples/tpr-demo/package.json adds @vinext/cloudflare: workspace:* as a dependency (required for the vinext-cloudflare bin to land in node_modules/.bin for the "deploy": "vinext-cloudflare deploy" script). Lockfile change is the single matching link:../../packages/cloudflare entry; pnpm install --frozen-lockfile passes.
  • CI unaffected — create-next-app-cloudflare-deploy.yml and ci.yml deploy via vp exec wrangler deploy, not the renamed bin, so the rename doesn't touch them.
  • Tests — vp test run tests/deploy.test.ts tests/tpr-kv-keys.test.ts → 271 passed; vp check on all changed source files → format + lint + types clean; vp run @vinext/cloudflare#build succeeds.
  • Doc updates across README, apps/web, tpr-demo pages/scripts, and vinext deploy deprecation text are consistent and point at valid commands.

Non-blocking

  1. Deprecation warning moved to stdout — packages/vinext/src/cli.ts:807 changed printDeployDeprecationWarning from console.warn → console.log, so the vinext deploy deprecation notice now prints to stdout instead of stderr. Deprecation/diagnostic notices conventionally go to stderr so they don't pollute machine-parseable stdout when deploy output is piped. No test asserts the stream. Minor; consider keeping it on console.warn.
  2. Scope-stripped cloudflare bin — the @vinext/cloudflare bin key installs a generic cloudflare executable into .bin (collision-prone with anything else wanting a cloudflare command). It's harmless given vinext-cloudflare is the documented local name and vpx @vinext/cloudflare resolves by package name regardless, but the scoped key only ever yields cloudflare, never @vinext/cloudflare. If the goal is solely to support vpx @vinext/cloudflare, that already works via package-name resolution without the scoped bin key — the key is essentially redundant.
  3. Carried from prior rounds: parsePositiveIntegerArg duplication in cloudflare/src/deploy.ts; self-referential bare vinext/internal/* imports resolve only via the alias/tsconfig/export triple.

LGTM.

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2405.

Review outcome: LGTM, no blocking issues

The previously-flagged bin regression is resolved. The PR now ships both bin keys:

"bin": {
  "@vinext/cloudflare": "dist/cli.js",
  "vinext-cloudflare": "dist/cli.js"
}

Empirically verified (replicated the exact bin block, installed with npm + pnpm, ran the real vpx/vp exec):

  • The scoped key still installs scope-stripped as cloudflare, but because vinext-cloudflare is also present, all three documented invocations work: npx @vinext/cloudflare deploy, vpx @vinext/cloudflare deploy, and vp exec vinext-cloudflare deploy.
  • vpx (run local/remote package) and vp exec (run local .bin) are the correct Vite+ analogs — the README/help wording matches their actual semantics.
  • Built CLI prints vinext-cloudflare v0.1.2; all help/usage strings consistently renamed.
  • tpr-demo correctly adds @vinext/cloudflare as a dependency (needed for the local bin); frozen-lockfile install passes; CI deploys via wrangler so it's unaffected.
  • 271 tests pass; vp check clean; both builds succeed.

Two non-blocking notes worth acting on:

  1. The deprecation warning was switched from console.warn → console.log (packages/vinext/src/cli.ts:807), moving it from stderr to stdout. Deprecation notices conventionally belong on stderr.
  2. The scoped @vinext/cloudflare bin key only ever produces a generic, collision-prone cloudflare binary — and since vpx @vinext/cloudflare already resolves by package name without it, that key is essentially redundant.

github run

@james-elicx
james-elicx force-pushed the codex/cloudflare-deploy-command branch from 49989fb to 97f5087 Compare June 29, 2026 16:42
@james-elicx

Copy link
Copy Markdown
Member Author

Adjusted public docs and website examples back to npx @vinext/cloudflare deploy, while keeping vp exec vinext-cloudflare deploy documented only as the Vite+ local-bin form.

/bigbonk review for issues

@james-elicx
james-elicx marked this pull request as ready for review June 29, 2026 16:42
@james-elicx
james-elicx force-pushed the codex/cloudflare-deploy-command branch from 97f5087 to 4c45345 Compare June 29, 2026 16:43
@james-elicx
james-elicx enabled auto-merge (squash) June 29, 2026 16:45
@james-elicx
james-elicx merged commit 0a48f0f into main Jun 29, 2026
60 checks passed
@james-elicx
james-elicx deleted the codex/cloudflare-deploy-command branch June 29, 2026 16:47

@ask-bonk ask-bonk 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.

Review: deploy command move + doc adjustments

Reviewed the current HEAD (4c45345, the force-pushed update that adjusts the public docs/website examples back to npx @vinext/cloudflare deploy while keeping vp exec vinext-cloudflare deploy as the Vite+ local-bin form). Rebuilt both packages, ran the targeted tests, type-checked the changed files, ran a frozen-lockfile install, and exercised the bins end-to-end.

🟡 Stale (and now incorrect) docs in the migrate-to-vinext skill

The PR's stated goal is to fix public docs/website examples for the renamed/relocated deploy command, but the migrate-to-vinext skill — which is public, user-facing documentation — was not updated. It still references the deprecated vinext deploy command, and worse, it documents behavior the command no longer has:

  • .agents/skills/migrate-to-vinext/SKILL.md:131:

    "If the user wants to deploy to Cloudflare Workers, use vinext deploy. It auto-generates wrangler.jsonc, worker entry, and Vite config if missing, installs @cloudflare/vite-plugin and wrangler, then builds and deploys."

    This is now factually wrong. The relocated deploy command no longer scaffolds anything — it validates and errors out telling the user to run vinext init --platform=cloudflare first (packages/cloudflare/src/deploy.ts:6 "Validates the project was prepared by vinext init --platform=cloudflare"; :341/:347 throw Missing ... Run \vinext init --platform=cloudflare` first.). I confirmed there is no writeFile/mkdir/scaffold path left in deploy.ts`.

  • Other stale vinext deploy references in the same skill: SKILL.md:34 (CLI table), SKILL.md:184, SKILL.md:215, .agents/skills/migrate-to-vinext/references/config-examples.md:78, :182, :191.

Since this PR is the one renaming the command (@vinext/cloudflare deploy / vinext-cloudflare deploy) and changing its behavior from generate→validate, the skill should be updated in the same change to avoid shipping a doc that tells migrating users a removed behavior still works. At minimum, SKILL.md:131 should be corrected (deploy validates, it does not generate — users should run vinext init --platform=cloudflare to scaffold), and the command name updated.

Verified (everything else checks out)

Website / README docs — README, packages/cloudflare/README.md, apps/web/app/page.tsx, and the tpr-demo pages/scripts consistently use npx @vinext/cloudflare deploy as the primary form with vp exec vinext-cloudflare deploy documented as the Vite+ local-bin form. The vinext --help Commands list correctly drops the deprecated deploy entry while the shim still functions (warns + delegates) — verified node dist/cli.js deploy --help still works.

Bins — packages/cloudflare/package.json:11-14 ships both @vinext/cloudflare (installs scope-stripped as cloudflare) and vinext-cloudflare keys. After vp install --frozen-lockfile, examples/tpr-demo/node_modules/.bin/ contains both cloudflare and vinext-cloudflare, and .bin/vinext-cloudflare --version → vinext-cloudflare v0.1.2, so tpr-demo's "deploy": "vinext-cloudflare deploy" script resolves. The tpr-demo correctly moved @vinext/cloudflare to dependencies (needed for the local bin); lockfile + frozen install pass.

CLI output — vinext-cloudflare v0.1.2; deploy --help consistently branded vinext-cloudflare, with the runner forms (npx @vinext/cloudflare, vpx @vinext/cloudflare, vp exec vinext-cloudflare) shown in the examples block.

Build / tests — vp run @vinext/cloudflare#build and vp run vinext#build both succeed. After rebuilding vinext, node packages/cloudflare/dist/cli.js deploy --help → exit 0 (the standalone cloudflare CLI requires a fresh vinext/dist for its vinext/internal/* peer-externalized imports — expected). vp test run tests/deploy.test.ts tests/tpr-kv-keys.test.ts → 271 passed; vp check on changed files clean.

Non-blocking (carried from prior rounds)

  1. Deprecation warning on stdout — packages/vinext/src/cli.ts:808 uses console.log (stdout) rather than console.warn (stderr). Deprecation notices conventionally go to stderr so they don't pollute piped deploy output. Minor.
  2. Scope-stripped cloudflare bin — the @vinext/cloudflare bin key only ever yields a generic cloudflare executable (collision-prone). Since vpx @vinext/cloudflare resolves by package name regardless and vinext-cloudflare is the documented local bin, the scoped key is effectively redundant.
  3. parsePositiveIntegerArg duplicated into cloudflare/src/deploy.ts; self-referential bare vinext/internal/* imports resolve only via the alias/tsconfig/export triple.

The doc-parity issue in the skill is the one thing worth fixing in this PR, since it's the PR that makes those docs wrong.

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2405.

Review outcome: one doc-parity issue worth fixing

The doc-adjustment update is sound — README, packages/cloudflare/README.md, apps/web, and the tpr-demo pages/scripts consistently use npx @vinext/cloudflare deploy as the primary form with vp exec vinext-cloudflare deploy for the Vite+ local bin. I verified bins, builds, and tests end-to-end (271 passing, both packages build, frozen-lockfile install passes, vinext-cloudflare bin resolves in tpr-demo's .bin).

🟡 The one finding: the migrate-to-vinext skill (public, user-facing docs) was not updated and is now both stale and factually wrong:

  • SKILL.md:131 still says vinext deploy "auto-generates wrangler.jsonc, worker entry, and Vite config if missing." The relocated command no longer scaffolds anything — it now validates and errors out telling the user to run vinext init --platform=cloudflare (confirmed in deploy.ts:6/341/347; no scaffold path remains). Since this PR is the one that changed the behavior from generate→validate, it should fix the doc in the same change.
  • Additional stale vinext deploy references at SKILL.md:34/184/215 and references/config-examples.md:78/182/191.

Non-blocking notes carried from prior rounds: deprecation warning moved to stdout (conventionally stderr), the scoped @vinext/cloudflare bin key is redundant (only ever yields a generic cloudflare binary), and the parsePositiveIntegerArg duplication / bare vinext/internal/* self-imports.

github run

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.

1 participant