@vinext/cloudflare 1.0.0-beta.10 → 1.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/deploy.js CHANGED
@@ -7,7 +7,7 @@ import { VINEXT_EXPECTED_WORKER_VERSION_HEADER } from "./version-headers.js";
7
7
  import { formatImageOptimizationHint, formatMissingCacheAdapterError, resolveCdnAdapterConfig, resolveKvDataAdapterConfig, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, viteConfigHasImageAdapter, workerEntryHasCacheHandler } from "./deploy-config.js";
8
8
  import { assertCdnVersionMetadataConfig } from "./wrangler-version-metadata.js";
9
9
  import { parseWorkerDeploymentUrl } from "./worker-deployment-url.js";
10
- import { runWranglerDeploymentStatus, runWranglerTriggersDeploy, runWranglerVersionDeploy, runWranglerVersionUpload } from "./version-deploy.js";
10
+ import { readBuildOutputWorkerName, runCfDeploymentStatus, runCfTriggersDeploy, runCfVersionDeploy, runCfVersionUpload, runWranglerDeploymentStatus, runWranglerTriggersDeploy, runWranglerVersionDeploy, runWranglerVersionUpload } from "./version-deploy.js";
11
11
  import { buildPrerenderKVPairs } from "./prerender-kv-populate.js";
12
12
  import { createRequire } from "node:module";
13
13
  import fs from "node:fs";
@@ -20,7 +20,9 @@ import os from "node:os";
20
20
  import { spawn } from "node:child_process";
21
21
  import { parseArgs } from "node:util";
22
22
  import { pathToFileURL } from "node:url";
23
+ import { VINEXT_BUILD_LIFECYCLE_CONFIG } from "vinext/internal/build/lifecycle";
23
24
  import { runPrerender } from "vinext/internal/build/run-prerender";
25
+ import { printBuildReport } from "vinext/internal/build/report";
24
26
  import { loadDotenv } from "vinext/internal/config/dotenv";
25
27
  import { findVinextNextConfigInPlugins, loadNextConfig, resolveNextConfig, resolveNextConfigInput } from "vinext/internal/config/next-config";
26
28
  import { cacheWarmupStatusSource, findVinextCacheConfigInPlugins, findVinextPrerenderConfigInPlugins, findVinextRouteRootConfigInPlugins, formatVinextPrerenderLabel, hasBuildIdentityResponseHeader, hasUncachedRequestRouting, hasVerbatimResponseVary, isConfiguredCdnResponsePolicyHeader, requiresRouteCacheabilityProbeManifest, resolveVinextPrerenderDecision, supportsCanonicalRscWarmup } from "vinext/internal/config/prerender";
@@ -256,10 +258,13 @@ async function loadProjectWranglerApi(root) {
256
258
  pathToFileURL(wranglerPath).href
257
259
  );
258
260
  }
259
- async function loadDeployViteConfigMetadata(root) {
261
+ function resolveViteBuildMode(deploymentTool, env) {
262
+ return deploymentTool === "cf" ? env ?? "production" : "production";
263
+ }
264
+ async function loadDeployViteConfigMetadata(root, mode) {
260
265
  const plugins = (await (await loadProjectViteApi(root)).loadConfigFromFile({
261
266
  command: "build",
262
- mode: "production"
267
+ mode
263
268
  }, void 0, root))?.config.plugins;
264
269
  return {
265
270
  cacheConfig: await findVinextCacheConfigInPlugins(plugins),
@@ -268,14 +273,23 @@ async function loadDeployViteConfigMetadata(root) {
268
273
  routeRootConfig: await findVinextRouteRootConfigInPlugins(plugins)
269
274
  };
270
275
  }
271
- async function runBuild(info, env) {
276
+ async function runBuild(info, env, mode) {
272
277
  console.log("\n Building for Cloudflare Workers...\n");
273
278
  const { createBuilder } = await loadProjectViteApi(info.root);
279
+ let completed = false;
274
280
  await withCloudflareEnv(env, async () => {
275
- await (await createBuilder({ root: info.root })).buildApp();
281
+ const invocation = { onComplete() {
282
+ completed = true;
283
+ } };
284
+ await (await createBuilder({
285
+ root: info.root,
286
+ mode,
287
+ [VINEXT_BUILD_LIFECYCLE_CONFIG]: invocation
288
+ })).buildApp();
276
289
  });
290
+ if (!completed) throw new Error("[vinext] The Cloudflare build lifecycle did not complete.");
277
291
  }
278
- async function populateKVCacheFromPrerenderedArtifacts(root, wranglerEnv, cacheConfig) {
292
+ async function populateKVCacheFromPrerenderedArtifacts(root, deploymentTool, env, cacheConfig) {
279
293
  const kvConfig = resolveKvDataAdapterConfig(cacheConfig);
280
294
  if (!kvConfig) return;
281
295
  const { routeCount, pairs } = buildPrerenderKVPairs(path.join(root, "dist", "server"), {
@@ -286,9 +300,10 @@ async function populateKVCacheFromPrerenderedArtifacts(root, wranglerEnv, cacheC
286
300
  console.log(" KV cache: Skipping prerender upload (no App Router prerendered cache entries found).");
287
301
  return;
288
302
  }
289
- await runWranglerKVBulkPut(root, {
303
+ await runKVBulkPut(root, {
290
304
  binding: kvConfig.binding,
291
- env: wranglerEnv,
305
+ deploymentTool,
306
+ env,
292
307
  pairs
293
308
  });
294
309
  console.log(` KV cache: Uploaded ${pairs.length} entr${pairs.length === 1 ? "y" : "ies"} for ${routeCount} prerendered route${routeCount === 1 ? "" : "s"}.`);
@@ -309,6 +324,27 @@ function buildWranglerDeployArgs(options) {
309
324
  env
310
325
  };
311
326
  }
327
+ function resolveDeploymentTool(root) {
328
+ return fs.existsSync(path.join(root, "cloudflare.config.ts")) ? "cf" : "wrangler";
329
+ }
330
+ function isCfCliInstalled(root, resolvePackageJson = (projectRoot) => {
331
+ try {
332
+ return createRequire(path.join(projectRoot, "package.json")).resolve("cf/package.json");
333
+ } catch {
334
+ return findInNodeModules(projectRoot, "cf/package.json");
335
+ }
336
+ }) {
337
+ return resolvePackageJson(root) !== null;
338
+ }
339
+ function buildCfDeployArgs(options) {
340
+ const mode = options.env || (options.preview ? "preview" : void 0);
341
+ const args = ["deploy", "--prebuilt"];
342
+ if (mode) args.push("--mode", validateWranglerEnvName(mode));
343
+ return {
344
+ args,
345
+ mode
346
+ };
347
+ }
312
348
  function buildWranglerKVBulkPutArgs(options) {
313
349
  const env = options.env || void 0;
314
350
  const args = [
@@ -347,6 +383,21 @@ function resolveWranglerBin(root, resolvePackageJson = (projectRoot) => {
347
383
  }
348
384
  return path.join(root, "node_modules", "wrangler", "bin", "wrangler.js");
349
385
  }
386
+ function resolveCfBin(root, resolvePackageJson = (projectRoot) => {
387
+ try {
388
+ return createRequire(path.join(projectRoot, "package.json")).resolve("cf/package.json");
389
+ } catch {
390
+ return findInNodeModules(projectRoot, "cf/package.json");
391
+ }
392
+ }) {
393
+ const packageJsonPath = resolvePackageJson(root);
394
+ if (packageJsonPath) {
395
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
396
+ const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.cf;
397
+ if (bin) return path.resolve(path.dirname(packageJsonPath), bin);
398
+ }
399
+ return path.join(root, "node_modules", "cf", "bin", "cf");
400
+ }
350
401
  function buildNodeCliInvocation(scriptPath, args, nodeExecutable = process.execPath) {
351
402
  return {
352
403
  file: nodeExecutable,
@@ -361,21 +412,35 @@ function buildWranglerInvocation(root, options, nodeExecutable = process.execPat
361
412
  env
362
413
  };
363
414
  }
364
- async function runWranglerKVBulkPut(root, options, execute = spawn, nodeExecutable = process.execPath) {
415
+ async function runKVBulkPut(root, options, execute = spawn, nodeExecutable = process.execPath) {
416
+ const cfNamespaceId = (() => {
417
+ if (options.deploymentTool !== "cf") return void 0;
418
+ const configPath = path.join(root, ".cloudflare/output/v0/workers/default/worker.config.json");
419
+ const binding = JSON.parse(fs.readFileSync(configPath, "utf8")).env?.[options.binding];
420
+ if (binding?.type !== "kv" || typeof binding.id !== "string" || !binding.id) throw new Error(`[vinext] Generated Cloudflare Build Output does not declare KV binding ${JSON.stringify(options.binding)} with a namespace ID.`);
421
+ return binding.id;
422
+ })();
365
423
  const tempDir = fs.mkdtempSync(path.join(options.tempDir ?? os.tmpdir(), "vinext-kv-bulk-"));
366
424
  try {
367
- const wranglerBin = resolveWranglerBin(root);
425
+ const cliBin = cfNamespaceId ? resolveCfBin(root) : resolveWranglerBin(root);
368
426
  const totalChunks = Math.ceil(options.pairs.length / KV_BULK_PUT_CHUNK_SIZE);
369
427
  for (let i = 0; i < totalChunks; i++) {
370
428
  const filePath = path.join(tempDir, `prerender-kv-${i}.json`);
371
429
  const chunk = options.pairs.slice(i * KV_BULK_PUT_CHUNK_SIZE, (i + 1) * KV_BULK_PUT_CHUNK_SIZE);
372
430
  fs.writeFileSync(filePath, JSON.stringify(chunk), "utf-8");
373
- const { args } = buildWranglerKVBulkPutArgs({
431
+ const invocation = buildNodeCliInvocation(cliBin, cfNamespaceId ? [
432
+ "kv",
433
+ "bulk",
434
+ "update",
435
+ cfNamespaceId,
436
+ "--body",
437
+ `@${filePath}`,
438
+ ...options.env ? ["--mode", validateWranglerEnvName(options.env)] : []
439
+ ] : buildWranglerKVBulkPutArgs({
374
440
  binding: options.binding,
375
441
  env: options.env,
376
442
  filePath
377
- });
378
- const invocation = buildNodeCliInvocation(wranglerBin, args, nodeExecutable);
443
+ }).args, nodeExecutable);
379
444
  const child = execute(invocation.file, invocation.args, {
380
445
  cwd: root,
381
446
  stdio: "inherit",
@@ -389,7 +454,7 @@ async function runWranglerKVBulkPut(root, options, execute = spawn, nodeExecutab
389
454
  return;
390
455
  }
391
456
  const exitReason = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
392
- reject(/* @__PURE__ */ new Error(`Wrangler KV bulk put failed with ${exitReason}.`));
457
+ reject(/* @__PURE__ */ new Error(`${cfNamespaceId ? "cf" : "Wrangler"} KV bulk upload failed with ${exitReason}.`));
393
458
  });
394
459
  });
395
460
  }
@@ -439,6 +504,38 @@ async function runWranglerDeploy(root, options, execute = spawn) {
439
504
  });
440
505
  return parseWorkerDeploymentUrl(output) ?? "(URL not detected in wrangler output)";
441
506
  }
507
+ async function runCfDeploy(root, options, execute = spawn) {
508
+ const { args, mode } = buildCfDeployArgs(options);
509
+ console.log(mode ? `\n Deploying Build Output in mode: ${mode}...` : "\n Deploying Build Output...");
510
+ const child = execute(process.execPath, [resolveCfBin(root), ...args], {
511
+ cwd: root,
512
+ stdio: [
513
+ "inherit",
514
+ "pipe",
515
+ "pipe"
516
+ ],
517
+ shell: false
518
+ });
519
+ let output = "";
520
+ child.stdout?.on("data", (chunk) => {
521
+ const text = chunk.toString();
522
+ output += text;
523
+ process.stdout.write(text);
524
+ });
525
+ child.stderr?.on("data", (chunk) => {
526
+ const text = chunk.toString();
527
+ output += text;
528
+ process.stderr.write(text);
529
+ });
530
+ await new Promise((resolve, reject) => {
531
+ child.once("error", reject);
532
+ child.once("close", (code, signal) => {
533
+ if (code === 0) resolve();
534
+ else reject(/* @__PURE__ */ new Error(`cf deploy failed with ${signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`}.`));
535
+ });
536
+ });
537
+ return parseWorkerDeploymentUrl(output) ?? "(URL not detected in cf output)";
538
+ }
442
539
  function hasCdnWarmRequests(plan) {
443
540
  return plan.paths.length + (plan.pagesDataPaths?.length ?? 0) + (plan.routeHandlerPaths?.length ?? 0) + plan.rscPaths.length + plan.loadingShellPaths.length > 0;
444
541
  }
@@ -472,6 +569,30 @@ function selectTPRWarmPlan(plan, traffic, coverage, limit) {
472
569
  function projectRequiresRouteCacheabilityProbeManifest(project, cacheConfig) {
473
570
  return (project.isAppRouter || project.isPagesRouter) && requiresRouteCacheabilityProbeManifest(cacheConfig);
474
571
  }
572
+ function deploymentWorkerName(options) {
573
+ if (!options.name) throw new Error("Could not detect the Worker name for its deployment.");
574
+ return options.name;
575
+ }
576
+ function runDeploymentStatus(root, options) {
577
+ return options.deploymentTool === "cf" ? runCfDeploymentStatus(root, {
578
+ name: deploymentWorkerName(options),
579
+ env: options.env,
580
+ verbose: options.verbose
581
+ }) : runWranglerDeploymentStatus(root, options);
582
+ }
583
+ function runVersionDeploy(root, traffic, options, phase) {
584
+ return options.deploymentTool === "cf" ? runCfVersionDeploy(root, traffic, {
585
+ name: deploymentWorkerName(options),
586
+ env: options.env,
587
+ verbose: options.verbose
588
+ }, phase) : runWranglerVersionDeploy(root, traffic, options, phase);
589
+ }
590
+ function runTriggersDeploy(root, options) {
591
+ return options.deploymentTool === "cf" ? runCfTriggersDeploy(root, options) : runWranglerTriggersDeploy(root, options);
592
+ }
593
+ function runVersionUpload(root, options) {
594
+ return options.deploymentTool === "cf" ? runCfVersionUpload(root, options) : runWranglerVersionUpload(root, options);
595
+ }
475
596
  function cdnWarmTargetKey(target) {
476
597
  return `${target.kind}\0${target.sourcePathname}`;
477
598
  }
@@ -555,7 +676,8 @@ async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
555
676
  warmPlanDiscovered = true;
556
677
  };
557
678
  if (!options.discoverWarmPlan || hasPreparedWarmPlan) remainingWarmPlan = prepareWarmPlan(remainingWarmPlan);
558
- const upload = options.uploadedVersion ?? runWranglerVersionUpload(root, options);
679
+ const upload = options.uploadedVersion ?? runVersionUpload(root, options);
680
+ const wranglerOptions = resolveDeploymentControlPlaneOptions(options, upload);
559
681
  const warmUploadedVersion = (targetUrl, headers, propagatingTarget = false, plan = {
560
682
  loadingShellPaths: remainingWarmPlan.loadingShellPaths,
561
683
  pagesDataPaths: remainingWarmPlan.pagesDataPaths,
@@ -584,10 +706,10 @@ async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
584
706
  strict: requireCacheHit || !allowUnverifiedPromotion,
585
707
  statusSource: options.statusSource
586
708
  });
587
- const wranglerConfig = parseWranglerConfig(root, options.config);
709
+ const wranglerConfig = options.deploymentTool === "cf" ? null : parseWranglerConfig(root, options.config);
588
710
  let deploymentStatus;
589
711
  try {
590
- deploymentStatus = runWranglerDeploymentStatus(root, options);
712
+ deploymentStatus = runDeploymentStatus(root, wranglerOptions);
591
713
  } catch (error) {
592
714
  if (!hasPreparedWarmPlan) throw error;
593
715
  throw new StagedWarmupError(formatUnknownError(error), { cause: error });
@@ -603,14 +725,14 @@ async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
603
725
  let triggersApplied = options.triggersAlreadyApplied === true;
604
726
  function applyTriggers() {
605
727
  if (triggersApplied) return;
606
- triggersDeployedUrl = runWranglerTriggersDeploy(root, options).deployedUrl;
728
+ triggersDeployedUrl = runTriggersDeploy(root, wranglerOptions).deployedUrl;
607
729
  triggersApplied = true;
608
730
  }
609
731
  if (stagingTraffic) {
610
732
  try {
611
- staged = runWranglerVersionDeploy(root, stagingTraffic, options, "stage");
733
+ staged = runVersionDeploy(root, stagingTraffic, wranglerOptions, "stage");
612
734
  } catch (error) {
613
- throw reconcileVersionDeployFailure(root, options, error, {
735
+ throw reconcileVersionDeployFailure(root, wranglerOptions, error, {
614
736
  desiredTraffic: stagingTraffic,
615
737
  desiredDescription: "The uploaded version is staged at 0% with the previous version still serving 100% traffic; Worker triggers/routes were not changed.",
616
738
  priorState: deploymentStatus,
@@ -619,7 +741,7 @@ async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
619
741
  }
620
742
  try {
621
743
  if (hasPreparedWarmPlan) {
622
- stagedDeploymentState = runWranglerDeploymentStatus(root, options);
744
+ stagedDeploymentState = runDeploymentStatus(root, wranglerOptions);
623
745
  if (!deploymentTrafficEquals(stagedDeploymentState.versions, stagingTraffic)) throw new Error("Two-stage CDN warming stopped because Worker deployment traffic changed before production triggers could be applied. No final version was promoted.");
624
746
  }
625
747
  applyTriggers();
@@ -627,7 +749,7 @@ async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
627
749
  throw withStagedVersionCleanupNote(error);
628
750
  }
629
751
  const targetUrl = resolveCdnWarmupTargetUrl(root, triggersDeployedUrl, options) ?? staged.deployedUrl;
630
- const headers = buildVersionOverrideHeaders(options.name ?? upload.workerName ?? resolveWorkerNameForVersionOverride(wranglerConfig, options), upload.versionId);
752
+ const headers = buildVersionOverrideHeaders(options.name ?? upload.workerName ?? resolveWorkerNameForVersionOverride(wranglerConfig, wranglerOptions), upload.versionId);
631
753
  if (targetUrl && headers) try {
632
754
  if (!warmPlanDiscovered) {
633
755
  console.log(" CDN warmup: discovering paths from the staged Worker version...");
@@ -719,9 +841,9 @@ async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
719
841
  if (hasPreparedWarmPlan && stagedDeploymentState) {
720
842
  let currentDeployment;
721
843
  try {
722
- currentDeployment = runWranglerDeploymentStatus(root, options);
844
+ currentDeployment = runDeploymentStatus(root, wranglerOptions);
723
845
  } catch (error) {
724
- throw new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} CDN warmup cannot confirm that the uploaded Worker remains staged at 0% traffic; production Worker triggers/routes remain applied. Inspect \`wrangler deployments status\` before continuing.`, { cause: error });
846
+ throw new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} CDN warmup cannot confirm that the uploaded Worker remains staged at 0% traffic; production Worker triggers/routes remain applied. Inspect the active Worker deployment before continuing.`, { cause: error });
725
847
  }
726
848
  if (!deploymentStateEquals(currentDeployment, stagedDeploymentState)) throw new StagedWarmupError("Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed before the no-promote handoff. The uploaded Worker is not confirmed staged at 0% traffic; production Worker triggers/routes remain applied.");
727
849
  }
@@ -744,13 +866,13 @@ async function deployUploadedVersionWithCdnWarmup(root, paths, options) {
744
866
  }
745
867
  }
746
868
  if (hasPreparedWarmPlan && stagingTraffic) {
747
- prePromotionState = runWranglerDeploymentStatus(root, options);
869
+ prePromotionState = runDeploymentStatus(root, wranglerOptions);
748
870
  if (!deploymentTrafficEquals(prePromotionState.versions, stagingTraffic) || stagedDeploymentState && !deploymentStateEquals(prePromotionState, stagedDeploymentState)) throw new Error("Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed before the final version could be promoted. No final version was promoted.");
749
871
  }
750
872
  promotionAttempted = true;
751
- deployed = runWranglerVersionDeploy(root, promotionTraffic, options, stagedCacheFilled ? "promote-warmed" : "promote-uploaded");
873
+ deployed = runVersionDeploy(root, promotionTraffic, wranglerOptions, stagedCacheFilled ? "promote-warmed" : "promote-uploaded");
752
874
  } catch (error) {
753
- if (promotionAttempted) throw reconcileVersionDeployFailure(root, options, error, {
875
+ if (promotionAttempted) throw reconcileVersionDeployFailure(root, wranglerOptions, error, {
754
876
  desiredTraffic: promotionTraffic,
755
877
  desiredDescription: "The uploaded version is already promoted to 100%; Worker triggers/routes may already have changed.",
756
878
  priorState: prePromotionState ?? (stagingTraffic ? {
@@ -802,7 +924,7 @@ function reconcileVersionDeployFailure(root, options, error, expected) {
802
924
  let reconciliation;
803
925
  let mayNeedCleanup = true;
804
926
  try {
805
- const current = runWranglerDeploymentStatus(root, options);
927
+ const current = runDeploymentStatus(root, options);
806
928
  if (deploymentTrafficEquals(current.versions, expected.desiredTraffic)) reconciliation = expected.desiredDescription;
807
929
  else if (deploymentStateEquals(current, expected.priorState)) {
808
930
  reconciliation = expected.priorDescription;
@@ -815,20 +937,21 @@ function reconcileVersionDeployFailure(root, options, error, expected) {
815
937
  return mayNeedCleanup ? new StagedWarmupError(detail, { cause: error }) : new Error(detail, { cause: error });
816
938
  }
817
939
  function assertDeploymentStateUnchanged(root, options, expected, message) {
818
- if (!deploymentStateEquals(runWranglerDeploymentStatus(root, options), expected)) throw new Error(message);
940
+ if (!deploymentStateEquals(runDeploymentStatus(root, options), expected)) throw new Error(message);
819
941
  }
820
942
  async function deployWithCacheabilityProbe(root, options) {
821
943
  if (!options.discoverWarmPlan) throw new Error("Two-stage CDN warming requires staged Worker route discovery before cacheability probing.");
822
944
  const prerenderSecret = readPrerenderSecret(root);
823
- const probeUpload = runWranglerVersionUpload(root, options);
824
- const initialDeployment = runWranglerDeploymentStatus(root, options);
945
+ const probeUpload = runVersionUpload(root, options);
946
+ const wranglerOptions = resolveDeploymentControlPlaneOptions(options, probeUpload);
947
+ const initialDeployment = runDeploymentStatus(root, wranglerOptions);
825
948
  const probeTraffic = getZeroPercentStagingTraffic(initialDeployment, probeUpload.versionId);
826
949
  if (!probeTraffic) throw new Error("Two-stage CDN warming cannot stage the probe Worker at 0% because the current deployment is not exactly one version serving 100% traffic. No traffic or triggers were changed.");
827
950
  let stagedProbe;
828
951
  try {
829
- stagedProbe = runWranglerVersionDeploy(root, probeTraffic, options, "stage");
952
+ stagedProbe = runVersionDeploy(root, probeTraffic, wranglerOptions, "stage");
830
953
  } catch (error) {
831
- throw reconcileVersionDeployFailure(root, options, error, {
954
+ throw reconcileVersionDeployFailure(root, wranglerOptions, error, {
832
955
  desiredTraffic: probeTraffic,
833
956
  desiredDescription: "The probe version is staged at 0% with the previous version still serving 100% traffic; production Worker triggers/routes were not changed.",
834
957
  priorState: initialDeployment,
@@ -837,16 +960,16 @@ async function deployWithCacheabilityProbe(root, options) {
837
960
  }
838
961
  let stagedProbeDeployment;
839
962
  try {
840
- stagedProbeDeployment = runWranglerDeploymentStatus(root, options);
963
+ stagedProbeDeployment = runDeploymentStatus(root, wranglerOptions);
841
964
  if (!deploymentTrafficEquals(stagedProbeDeployment.versions, probeTraffic)) throw new Error("Two-stage CDN warming stopped because Worker deployment traffic changed immediately after the probe version was staged.");
842
965
  } catch (error) {
843
- throw new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} The probe staging command completed, but its current deployment state could not be confirmed; production Worker triggers/routes were not changed. Inspect \`wrangler deployments status\` and remove the staged probe version if necessary.`, { cause: error });
966
+ throw new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} The probe staging command completed, but its current deployment state could not be confirmed; production Worker triggers/routes were not changed. Inspect the active Worker deployment and remove the staged probe version if necessary.`, { cause: error });
844
967
  }
845
968
  let prepared;
846
969
  try {
847
970
  const targetUrl = resolveCdnWarmupTargetUrl(root, stagedProbe.deployedUrl ?? probeUpload.previewUrl, options);
848
- const wranglerConfig = parseWranglerConfig(root, options.config);
849
- const headers = buildVersionOverrideHeaders(options.name ?? probeUpload.workerName ?? resolveWorkerNameForVersionOverride(wranglerConfig, options), probeUpload.versionId);
971
+ const wranglerConfig = options.deploymentTool === "cf" ? null : parseWranglerConfig(root, wranglerOptions.config);
972
+ const headers = buildVersionOverrideHeaders(options.name ?? probeUpload.workerName ?? resolveWorkerNameForVersionOverride(wranglerConfig, wranglerOptions), probeUpload.versionId);
850
973
  if (!targetUrl || !headers) throw new Error("Two-stage CDN warming needs a production URL and Worker name for version-overridden cacheability probes.");
851
974
  console.log(" CDN warmup: discovering paths from the staged probe Worker...");
852
975
  const discovered = await options.discoverWarmPlan({
@@ -937,14 +1060,11 @@ async function deployWithCacheabilityProbe(root, options) {
937
1060
  rscPaths: probe.cacheableTargets.filter((target) => target.kind === "rsc-full").map((target) => target.sourcePathname)
938
1061
  };
939
1062
  const selectedFinalPlan = options.selectWarmPlan?.(finalPlan) ?? finalPlan;
940
- assertDeploymentStateUnchanged(root, options, stagedProbeDeployment, "Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed while cacheability was being probed. No final version was promoted.");
941
- const finalConfig = writeCacheabilityManifestArtifact(root, options.config, probe.manifest);
942
- const finalUpload = runWranglerVersionUpload(root, {
943
- config: finalConfig,
944
- env: options.env,
945
- name: options.name,
946
- preview: options.preview,
947
- verbose: options.verbose
1063
+ assertDeploymentStateUnchanged(root, wranglerOptions, stagedProbeDeployment, "Two-stage CDN warming stopped because Worker deployment traffic or deployment identity changed while cacheability was being probed. No final version was promoted.");
1064
+ const finalConfig = writeCacheabilityManifestArtifact(root, options.config, probe.manifest, options.deploymentTool);
1065
+ const finalUpload = runVersionUpload(root, {
1066
+ ...options,
1067
+ config: options.deploymentTool === "cf" ? options.config : finalConfig
948
1068
  });
949
1069
  prepared = {
950
1070
  optionalWarmTargetKeys: new Set(probe.speculativeTargets.map(cdnWarmTargetKey)),
@@ -986,6 +1106,15 @@ function getZeroPercentStagingTraffic(deployment, versionId) {
986
1106
  function getWranglerTargetEnv(options) {
987
1107
  return options.env || (options.preview ? "preview" : void 0);
988
1108
  }
1109
+ function resolveDeploymentControlPlaneOptions(options, upload) {
1110
+ if (options.deploymentTool !== "cf") return options;
1111
+ const name = upload.workerName ?? options.name;
1112
+ if (!name) throw new Error("Could not detect the uploaded Worker name needed for staged CDN warming.");
1113
+ return {
1114
+ ...options,
1115
+ name
1116
+ };
1117
+ }
989
1118
  function resolveWorkerNameForVersionOverride(config, options) {
990
1119
  if (options.name) return options.name;
991
1120
  const env = getWranglerTargetEnv(options);
@@ -1010,24 +1139,29 @@ function withStagedProbeVersionCleanupNote(error) {
1010
1139
  return new StagedWarmupError(`${error instanceof Error ? error.message : String(error)} The probe version may remain staged at 0% with the previous version still serving 100% traffic; production Worker triggers/routes were not changed.`, { cause: error });
1011
1140
  }
1012
1141
  function getStagedVersionCleanupNote() {
1013
- return "The uploaded version may remain staged at 0% with the previous version still serving 100% traffic; Worker triggers/routes may also have changed because trigger deployment runs before warming. Rerun deploy to promote it or use `wrangler versions deploy` to choose the desired version split.";
1142
+ return "The uploaded version may remain staged at 0% with the previous version still serving 100% traffic; Worker triggers/routes may also have changed because trigger deployment runs before warming. Rerun deploy to promote it or create a deployment with the desired version split.";
1014
1143
  }
1015
1144
  function withPromotedVersionTriggerNote(error) {
1016
1145
  const message = error instanceof Error ? error.message : String(error);
1017
- return new Error(`${message} The uploaded version may already be promoted to 100%, but Worker triggers/routes may not be updated; rerun deploy or \`wrangler triggers deploy\` after fixing the trigger error.`, { cause: error });
1146
+ return new Error(`${message} The uploaded version may already be promoted to 100%, but Worker triggers/routes may not be updated; rerun deploy or apply the Worker triggers after fixing the trigger error.`, { cause: error });
1018
1147
  }
1019
1148
  function withPromotedVersionWarmupNote(error) {
1020
1149
  const message = error instanceof Error ? error.message : String(error);
1021
- return new Error(`${message} The uploaded version is already promoted to 100% and its Worker triggers/routes were updated; rerun deploy to retry cache warming or roll back with \`wrangler versions deploy\`.`, { cause: error });
1150
+ return new Error(`${message} The uploaded version is already promoted to 100% and its Worker triggers/routes were updated; rerun deploy to retry cache warming or create a deployment for the previous version.`, { cause: error });
1022
1151
  }
1023
1152
  async function deploy(options) {
1024
1153
  if (options.warmCdnTarget !== void 0 && !options.warmCdnCache) throw new Error("--warm-cdn-target requires --experimental-warm-cdn-cache.");
1025
1154
  const warmCdnTarget = options.warmCdnTarget === void 0 ? void 0 : validateCdnWarmTarget(options.warmCdnTarget);
1026
- const deployEnv = validateWranglerEnvName(options.env || (options.preview ? "preview" : "production"));
1155
+ const deployEnv = options.env || (options.preview ? "preview" : void 0);
1156
+ if (deployEnv) validateWranglerEnvName(deployEnv);
1027
1157
  const root = path.resolve(options.root);
1158
+ const deploymentTool = resolveDeploymentTool(root);
1159
+ if (deploymentTool === "cf" && options.name) throw new Error("--name is not supported for cloudflare.config.ts projects. Set `name` in cloudflare.config.ts instead.");
1160
+ const viteMode = resolveViteBuildMode(deploymentTool, deployEnv);
1161
+ const wranglerFallbackEnv = deploymentTool === "cf" ? void 0 : deployEnv;
1028
1162
  loadDotenv({
1029
1163
  root,
1030
- mode: "production"
1164
+ mode: viteMode
1031
1165
  });
1032
1166
  console.log("\n vinext-cloudflare deploy\n");
1033
1167
  const info = detectProject(root);
@@ -1045,20 +1179,20 @@ async function deploy(options) {
1045
1179
  if (missingScaffolding.length > 0) throw new Error(`Missing Cloudflare deployment setup: ${missingScaffolding.join(", ")}. Run \`vinext init --platform=cloudflare\` first.`);
1046
1180
  const missingDeps = getMissingDeps(info);
1047
1181
  if (missingDeps.length > 0) throw new Error(`Missing deployment dependencies: ${missingDeps.map((dependency) => dependency.name).join(", ")}. Run \`vinext init --platform=cloudflare\` first.`);
1182
+ if (deploymentTool === "cf" && !isCfCliInstalled(root)) throw new Error("Missing deployment dependencies: cf. Run `vinext init --platform=cloudflare` first.");
1048
1183
  if (info.hasViteConfig && !viteConfigHasCloudflarePlugin(root)) throw new Error(formatMissingCloudflarePluginError({ isAppRouter: info.isAppRouter }));
1049
- if (info.hasISR && !viteConfigHasCacheAdapter(root) && !workerEntryHasCacheHandler(root)) throw new Error(formatMissingCacheAdapterError({}));
1184
+ if (info.hasISR && !viteConfigHasCacheAdapter(root) && !workerEntryHasCacheHandler(root)) throw new Error(formatMissingCacheAdapterError({ typedConfig: deploymentTool === "cf" }));
1050
1185
  if (!viteConfigHasImageAdapter(root)) {
1051
1186
  console.log();
1052
- console.log(formatImageOptimizationHint());
1187
+ console.log(formatImageOptimizationHint(deploymentTool === "cf"));
1053
1188
  }
1054
1189
  if (options.dryRun) {
1055
1190
  console.log("\n Dry run complete. No build or deploy performed.\n");
1056
1191
  return;
1057
1192
  }
1058
- const buildEnv = deployEnv === "production" && !options.env ? void 0 : deployEnv;
1059
- const viteConfigMetadata = await withCloudflareEnv(buildEnv, () => loadDeployViteConfigMetadata(info.root));
1193
+ const viteConfigMetadata = await withCloudflareEnv(deployEnv, () => loadDeployViteConfigMetadata(info.root, viteMode));
1060
1194
  const cdnAdapterConfig = resolveCdnAdapterConfig(viteConfigMetadata.cacheConfig);
1061
- const nextConfig = await withCloudflareEnv(buildEnv, async () => {
1195
+ const nextConfig = await withCloudflareEnv(deployEnv, async () => {
1062
1196
  const inlineNextConfig = viteConfigMetadata.nextConfig;
1063
1197
  const rawNextConfig = inlineNextConfig ? await resolveNextConfigInput(inlineNextConfig, PHASE_PRODUCTION_BUILD) : await loadNextConfig(info.root, PHASE_PRODUCTION_BUILD);
1064
1198
  return resolveNextConfig(rawNextConfig, info.root);
@@ -1076,7 +1210,7 @@ async function deploy(options) {
1076
1210
  const hasCanonicalRscWarmup = supportsCanonicalRscWarmup(viteConfigMetadata.cacheConfig);
1077
1211
  const needsCacheabilityProbeManifest = projectRequiresRouteCacheabilityProbeManifest(info, viteConfigMetadata.cacheConfig);
1078
1212
  const shouldEmitPrerenderPathManifest = !options.skipBuild && prerenderDecision;
1079
- if (!options.skipBuild) await runBuild(info, buildEnv);
1213
+ if (!options.skipBuild) await runBuild(info, deployEnv, viteMode);
1080
1214
  else console.log("\n Skipping build (--skip-build)");
1081
1215
  const canWarmTpr = options.experimentalTPR && !prerenderDecision && hasBuildIdentityHeader;
1082
1216
  if (options.experimentalTPR && prerenderDecision) console.log(" TPR: Skipping route selection (all-route prerendering configured)");
@@ -1084,22 +1218,24 @@ async function deploy(options) {
1084
1218
  const tpr = canWarmTpr ? await resolveTPRRoutes({
1085
1219
  root,
1086
1220
  config: options.config,
1087
- env: buildEnv,
1221
+ env: wranglerFallbackEnv,
1088
1222
  hostname: warmCdnTarget ? new URL(warmCdnTarget).hostname : void 0,
1223
+ typedConfig: deploymentTool === "cf",
1089
1224
  window: Math.max(1, options.tprWindow ?? 24)
1090
1225
  }) : null;
1091
1226
  if (tpr?.skipped) console.log(` TPR: Skipped (${tpr.skipped})`);
1092
1227
  const tprRoutes = tpr?.routes ?? [];
1093
1228
  const wranglerOptions = {
1094
- env: deployEnv === "production" && !options.env ? void 0 : deployEnv,
1095
- name: options.name,
1229
+ env: deploymentTool === "cf" ? viteMode : deployEnv,
1230
+ name: deploymentTool === "cf" ? readBuildOutputWorkerName(root) : options.name,
1096
1231
  config: options.config,
1097
- verbose: options.verbose
1232
+ verbose: options.verbose,
1233
+ deploymentTool
1098
1234
  };
1099
1235
  let shouldWarmTpr = tprRoutes.length > 0;
1100
1236
  if (shouldWarmTpr && !options.warmCdnCache) {
1101
1237
  try {
1102
- shouldWarmTpr = getZeroPercentStagingTraffic(runWranglerDeploymentStatus(root, wranglerOptions), "tpr-preflight") !== null;
1238
+ shouldWarmTpr = getZeroPercentStagingTraffic(runDeploymentStatus(root, wranglerOptions), "tpr-preflight") !== null;
1103
1239
  } catch {
1104
1240
  shouldWarmTpr = false;
1105
1241
  }
@@ -1110,25 +1246,32 @@ async function deploy(options) {
1110
1246
  const candidatePathsOnly = shouldSelectTpr && !needsCacheabilityProbeManifest;
1111
1247
  const shouldPrerenderLocally = prerenderDecision && (!shouldWarmCdnCache || prerenderDecision.reason === "next-export");
1112
1248
  if (shouldWarmCdnCache && cdnAdapterConfig) {
1113
- const wrangler = await loadProjectWranglerApi(info.root);
1114
- const previousCwd = process.cwd();
1115
- try {
1116
- process.chdir(info.root);
1117
- const config = wrangler.unstable_readConfig({
1118
- config: options.config,
1119
- env: buildEnv
1120
- }, {
1121
- hideWarnings: true,
1122
- preserveOriginalMain: true,
1123
- useRedirectIfAvailable: true
1124
- });
1125
- assertCdnVersionMetadataConfig({
1126
- binding: cdnAdapterConfig.versionMetadataBinding,
1127
- configuredBinding: config.version_metadata?.binding,
1128
- configPath: config.configPath
1129
- });
1130
- } finally {
1131
- process.chdir(previousCwd);
1249
+ if (deploymentTool === "cf") {
1250
+ const configPath = path.join(root, ".cloudflare/output/v0/workers/default/worker.config.json");
1251
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
1252
+ const binding = cdnAdapterConfig.versionMetadataBinding;
1253
+ if (config.env?.[binding]?.type !== "version-metadata") throw new Error(`[vinext] Cloudflare CDN warmup requires version metadata binding ${JSON.stringify(binding)} in the generated Build Output Worker config ${configPath}.`);
1254
+ } else {
1255
+ const wrangler = await loadProjectWranglerApi(info.root);
1256
+ const previousCwd = process.cwd();
1257
+ try {
1258
+ process.chdir(info.root);
1259
+ const config = wrangler.unstable_readConfig({
1260
+ config: options.config,
1261
+ env: wranglerFallbackEnv
1262
+ }, {
1263
+ hideWarnings: true,
1264
+ preserveOriginalMain: true,
1265
+ useRedirectIfAvailable: true
1266
+ });
1267
+ assertCdnVersionMetadataConfig({
1268
+ binding: cdnAdapterConfig.versionMetadataBinding,
1269
+ configuredBinding: config.version_metadata?.binding,
1270
+ configPath: config.configPath
1271
+ });
1272
+ } finally {
1273
+ process.chdir(previousCwd);
1274
+ }
1132
1275
  }
1133
1276
  }
1134
1277
  if (shouldEmitPrerenderPathManifest) await emitPrerenderPathManifest({
@@ -1141,22 +1284,31 @@ async function deploy(options) {
1141
1284
  routeRootConfig: viteConfigMetadata.routeRootConfig
1142
1285
  });
1143
1286
  let ranPrerender = false;
1144
- if (shouldPrerenderLocally) {
1287
+ let prerenderResult = void 0;
1288
+ if (shouldPrerenderLocally && !ranPrerender) {
1145
1289
  console.log(`\n ${formatVinextPrerenderLabel(prerenderDecision)}`);
1146
1290
  if (nextConfig.enablePrerenderSourceMaps) {
1147
1291
  process.setSourceMapsEnabled(true);
1148
1292
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 50);
1149
1293
  }
1150
- await runPrerender({
1294
+ prerenderResult = await runPrerender({
1151
1295
  root: info.root,
1152
- concurrency: options.prerenderConcurrency,
1296
+ concurrency: options.prerenderConcurrency ?? viteConfigMetadata.prerenderConfig?.concurrency,
1153
1297
  nextConfig,
1154
1298
  routeRootConfig: viteConfigMetadata.routeRootConfig
1155
1299
  });
1156
1300
  ranPrerender = true;
1157
1301
  }
1302
+ if (!options.skipBuild) {
1303
+ await printBuildReport({
1304
+ root: info.root,
1305
+ pageExtensions: nextConfig.pageExtensions,
1306
+ prerenderResult: prerenderResult ?? void 0
1307
+ });
1308
+ console.log("\n Build complete.\n");
1309
+ }
1158
1310
  if (ranPrerender) try {
1159
- await populateKVCacheFromPrerenderedArtifacts(root, deployEnv === "production" && !options.env ? void 0 : deployEnv, viteConfigMetadata.cacheConfig);
1311
+ await populateKVCacheFromPrerenderedArtifacts(root, deploymentTool, deploymentTool === "cf" ? viteMode : wranglerFallbackEnv, viteConfigMetadata.cacheConfig);
1160
1312
  } catch (error) {
1161
1313
  console.log(` KV cache: Skipping prerender upload (${formatUnknownError(error)}). Continuing with deploy.`);
1162
1314
  }
@@ -1220,13 +1372,17 @@ async function deploy(options) {
1220
1372
  if (options.warmCdnCache || options.warmCdnPromote === false && error instanceof StagedWarmupError) throw error;
1221
1373
  console.log(` TPR: Skipping pre-warm (${formatUnknownError(error)}). Continuing with deploy.`);
1222
1374
  }
1223
- if (url === void 0) url = await runWranglerDeploy(root, {
1224
- ...wranglerOptions,
1225
- promote: options.warmCdnPromote
1226
- });
1375
+ if (url === void 0) {
1376
+ if (deploymentTool === "cf" && options.warmCdnPromote === false) url = runCfVersionUpload(root, wranglerOptions).previewUrl ?? "(Preview URL not detected in cf output)";
1377
+ else if (deploymentTool === "cf") url = await runCfDeploy(root, wranglerOptions);
1378
+ else url = await runWranglerDeploy(root, {
1379
+ ...wranglerOptions,
1380
+ promote: options.warmCdnPromote
1381
+ });
1382
+ }
1227
1383
  console.log("\n ─────────────────────────────────────────");
1228
1384
  console.log(` ${options.warmCdnPromote === false ? "Version URL" : "Deployed to"}: ${url}`);
1229
1385
  console.log(" ─────────────────────────────────────────\n");
1230
1386
  }
1231
1387
  //#endregion
1232
- export { DEFAULT_CDN_WARM_PROMOTION_DELAY_MS, buildNodeCliInvocation, buildVersionOverrideHeaders, buildWranglerDeployArgs, buildWranglerInvocation, buildWranglerKVBulkPutArgs, deploy, deployWithCdnWarmup, getZeroPercentStagingTraffic, hasCdnWarmRequests, parseDeployArgs, projectRequiresRouteCacheabilityProbeManifest, resolveCdnWarmupTargetUrl, resolveWorkerNameForVersionOverride, resolveWranglerBin, runWranglerDeploy, runWranglerKVBulkPut, selectTPRWarmPlan, validateWranglerEnvName, withCloudflareEnv };
1388
+ export { DEFAULT_CDN_WARM_PROMOTION_DELAY_MS, buildCfDeployArgs, buildNodeCliInvocation, buildVersionOverrideHeaders, buildWranglerDeployArgs, buildWranglerInvocation, buildWranglerKVBulkPutArgs, deploy, deployWithCdnWarmup, getZeroPercentStagingTraffic, hasCdnWarmRequests, isCfCliInstalled, parseDeployArgs, projectRequiresRouteCacheabilityProbeManifest, resolveCdnWarmupTargetUrl, resolveCfBin, resolveDeploymentControlPlaneOptions, resolveDeploymentTool, resolveViteBuildMode, resolveWorkerNameForVersionOverride, resolveWranglerBin, runCfDeploy, runKVBulkPut, runWranglerDeploy, selectTPRWarmPlan, validateWranglerEnvName, withCloudflareEnv };
package/dist/tpr.d.ts CHANGED
@@ -9,6 +9,8 @@ export type TPROptions = {
9
9
  env?: string;
10
10
  /** Explicit domain used to resolve the analytics zone, overriding Wrangler routes. */
11
11
  hostname?: string;
12
+ /** Read domains from generated Build Output instead of a Wrangler config. */
13
+ typedConfig?: boolean;
12
14
  /** Analytics lookback window in hours. Default: 24. */
13
15
  window: number;
14
16
  };