void 0.21.3 → 0.21.5

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/cli/cli.mjs CHANGED
@@ -2020,7 +2020,7 @@ if (helpPath) {
2020
2020
  log.error(formatCloudflareUserMessage(err instanceof Error ? err.message : String(err)));
2021
2021
  process.exit(1);
2022
2022
  }
2023
- import("../deploy-BH0grP4y.mjs").then(({ runDeploy }) => {
2023
+ import("../deploy-CmePjn6m.mjs").then(({ runDeploy }) => {
2024
2024
  runDeploy(root, {
2025
2025
  projectSlug: deployArgs.projectSlug,
2026
2026
  dir: deployArgs.dir,
@@ -2052,8 +2052,8 @@ else if (command === "cloudflare") runSubcommandGroup("cloudflare", cloudflareSu
2052
2052
  else if (command === "project") runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (root, parsed) => import("../project-cmd-B44I8J_W.mjs").then(({ runProjectCommand }) => runProjectCommand(root, parsed)));
2053
2053
  else if (command === "secret") runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (root, parsed) => import("../secret-DN9sSNiV.mjs").then(({ runSecretCommand }) => runSecretCommand(root, parsed)));
2054
2054
  else if (command === "domain") runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (root, parsed) => import("../domain-Dmhvb2oU.mjs").then(({ runDomainCommand }) => runDomainCommand(root, parsed)));
2055
- else if (command === "email") runSubcommandGroup("email", emailSubcommands, parseEmailArgs, (root, parsed) => import("../email-BRKOG2RG.mjs").then(({ runEmailCommand }) => runEmailCommand(root, parsed)));
2056
- else if (command === "db") runSubcommandGroup("db", dbSubcommands, parseDbArgs, (root, parsed) => import("../db-DzJRn-qz.mjs").then(({ runDbCommand }) => runDbCommand(root, parsed)));
2055
+ else if (command === "email") runSubcommandGroup("email", emailSubcommands, parseEmailArgs, (root, parsed) => import("../email-CYq8VQ96.mjs").then(({ runEmailCommand }) => runEmailCommand(root, parsed)));
2056
+ else if (command === "db") runSubcommandGroup("db", dbSubcommands, parseDbArgs, (root, parsed) => import("../db-BAJbaEVn.mjs").then(({ runDbCommand }) => runDbCommand(root, parsed)));
2057
2057
  else if (command === "env") runSubcommandGroup("env", envSubcommands, parseEnvArgs, (root, parsed) => import("../env-Csi-tMbT.mjs").then(({ runEnvCommand }) => runEnvCommand(root, parsed)));
2058
2058
  else if (command === "gen") runSubcommandGroup("gen", genSubcommands, parseGenArgs, (root, parsed) => import("../gen-Vnv2f65C.mjs").then(({ runGenCommand }) => runGenCommand(root, parsed)));
2059
2059
  else if (command === "github") runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (root, parsed) => import("../github-cmd-BED3_9JD.mjs").then(({ runGithubCommand }) => runGithubCommand(root, parsed)));
@@ -2222,7 +2222,7 @@ else if (command === "prepare") {
2222
2222
  return;
2223
2223
  }
2224
2224
  case "deploy": {
2225
- const { runDeploy } = await import("../deploy-BH0grP4y.mjs");
2225
+ const { runDeploy } = await import("../deploy-CmePjn6m.mjs");
2226
2226
  await runDeploy(root);
2227
2227
  return;
2228
2228
  }
@@ -2235,8 +2235,8 @@ else if (command === "prepare") {
2235
2235
  case "domain": return runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (r, p) => import("../domain-Dmhvb2oU.mjs").then(({ runDomainCommand }) => runDomainCommand(r, p)));
2236
2236
  case "github": return runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (r, p) => import("../github-cmd-BED3_9JD.mjs").then(({ runGithubCommand }) => runGithubCommand(r, p)));
2237
2237
  case "build": return runSubcommandGroup("build", buildSubcommands, parseBuildArgs, (r, p) => import("../build-cmd-Dpt0jd-x.mjs").then(({ runBuildCommand }) => runBuildCommand(r, p)));
2238
- case "email": return runSubcommandGroup("email", emailSubcommands, parseEmailArgs, (r, p) => import("../email-BRKOG2RG.mjs").then(({ runEmailCommand }) => runEmailCommand(r, p)));
2239
- case "db": return runSubcommandGroup("db", dbSubcommands, parseDbArgs, (r, p) => import("../db-DzJRn-qz.mjs").then(({ runDbCommand }) => runDbCommand(r, p)));
2238
+ case "email": return runSubcommandGroup("email", emailSubcommands, parseEmailArgs, (r, p) => import("../email-CYq8VQ96.mjs").then(({ runEmailCommand }) => runEmailCommand(r, p)));
2239
+ case "db": return runSubcommandGroup("db", dbSubcommands, parseDbArgs, (r, p) => import("../db-BAJbaEVn.mjs").then(({ runDbCommand }) => runDbCommand(r, p)));
2240
2240
  case "env": return runSubcommandGroup("env", envSubcommands, parseEnvArgs, (r, p) => import("../env-Csi-tMbT.mjs").then(({ runEnvCommand }) => runEnvCommand(r, p)));
2241
2241
  case "gen": return runSubcommandGroup("gen", genSubcommands, parseGenArgs, (r, p) => import("../gen-Vnv2f65C.mjs").then(({ runGenCommand }) => runGenCommand(r, p)));
2242
2242
  case "prepare": {
@@ -862,7 +862,7 @@ async function runMigrate(root, dialect, remote) {
862
862
  log.error(getErrorMessage(error));
863
863
  process.exit(1);
864
864
  }
865
- const { checkWranglerMigrationApplySet, parseRootWranglerConfig } = await import("./deploy-BH0grP4y.mjs");
865
+ const { checkWranglerMigrationApplySet, parseRootWranglerConfig } = await import("./deploy-CmePjn6m.mjs");
866
866
  const applySet = await checkWranglerMigrationApplySet(context.operation.configPath, parseRootWranglerConfig(context.operation.configPath), binding, context.migrationsDir, rawMigrations);
867
867
  if (!applySet.ok) {
868
868
  log.error(applySet.reason);
@@ -1885,6 +1885,56 @@ function safePositional(value) {
1885
1885
  return code < 32 || code === 127;
1886
1886
  });
1887
1887
  }
1888
+ /** Existing-Worker handoffs retain their separate link and binding checks. */
1889
+ function createActiveSecretPinner(options) {
1890
+ const { output } = options;
1891
+ if (!output || options.handoffPending) return;
1892
+ return (activeVersionId, bindings) => pinActiveWorkerSecrets({
1893
+ output,
1894
+ activeVersionId,
1895
+ bindings,
1896
+ requiredNames: options.requiredNames,
1897
+ explicitNames: options.explicitNames
1898
+ });
1899
+ }
1900
+ /** Pin opaque secrets to the live version in the private, disposable Build Output. */
1901
+ async function pinActiveWorkerSecrets(options) {
1902
+ const { activeVersionId, bindings, requiredNames, explicitNames = [] } = options;
1903
+ if (!/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(activeVersionId)) return false;
1904
+ const secretNames = new Set(bindings.map((binding) => binding.name));
1905
+ const explicit = new Set(explicitNames);
1906
+ if (bindings.some((binding) => !safePositional(binding.name)) || secretNames.size !== bindings.length) return false;
1907
+ if (requiredNames.some((name) => !secretNames.has(name) && !explicit.has(name))) return false;
1908
+ const path = getWorkerConfigPath(options.output.directory);
1909
+ const worker = parseJson(readFileSync(path, "utf8"));
1910
+ if (!record(worker) || worker.env !== void 0 && !record(worker.env)) return false;
1911
+ const env = { ...worker.env ?? {} };
1912
+ for (const name of explicit) delete env[name];
1913
+ for (const [name, value] of Object.entries(env)) if (record(value) && (value.type === "secret" || value.type === "inherit" || value.type === "unsafe:inherit") && !secretNames.has(name) && !explicit.has(name)) return false;
1914
+ for (const { name } of bindings) {
1915
+ if (explicit.has(name)) continue;
1916
+ const current = env[name];
1917
+ if (current !== void 0 && (!record(current) || current.type !== "secret" && current.type !== "inherit" && current.type !== "unsafe:inherit")) return false;
1918
+ env[name] = {
1919
+ type: "unsafe:inherit",
1920
+ version_id: activeVersionId
1921
+ };
1922
+ }
1923
+ const unsafe = record(worker.unsafe) ? { ...worker.unsafe } : {};
1924
+ const metadata = record(unsafe.metadata) ? { ...unsafe.metadata } : {};
1925
+ if (metadata.bindings !== void 0) return false;
1926
+ if (metadata.keep_bindings !== void 0 && (!Array.isArray(metadata.keep_bindings) || !metadata.keep_bindings.every((type) => typeof type === "string"))) return false;
1927
+ metadata.keep_bindings = (metadata.keep_bindings ?? []).filter((type) => type !== "secret_text" && type !== "secret_key");
1928
+ unsafe.metadata = metadata;
1929
+ const next = {
1930
+ ...worker,
1931
+ env,
1932
+ unsafe
1933
+ };
1934
+ writeFileSync(path, JSON.stringify(next), { mode: 384 });
1935
+ await readBuildOutput(options.output.directory);
1936
+ return true;
1937
+ }
1888
1938
  /** Prevent cf's prebuilt uploader from creating a resource as a side effect. */
1889
1939
  function createCfResourceVerifier(options) {
1890
1940
  return async (directory) => {
@@ -7461,6 +7511,12 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7461
7511
  verifyCompatibilityRunner: assertUploaderStillPinned
7462
7512
  })
7463
7513
  }) : runWrangler;
7514
+ const pinActiveSecrets = createActiveSecretPinner({
7515
+ output: cfOutput,
7516
+ handoffPending: pinnedHandoffLink !== null,
7517
+ requiredNames: schemaFacts.requiredKeys,
7518
+ ...initialAuthSecret ? { explicitNames: [BETTER_AUTH_SECRET_NAME] } : {}
7519
+ });
7464
7520
  const execution = await activateCloudflareVersionWithProgress(deps.activateVersion ?? activateCloudflareVersion, {
7465
7521
  runWrangler: activationRunner,
7466
7522
  verifyUploader: cfOutput ? cfUploaderIsPinned : assertUploaderStillPinned,
@@ -7507,6 +7563,7 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7507
7563
  previousTrafficConfig: readWranglerPublishedTrafficSnapshot(configPath),
7508
7564
  queueConsumerCleanup,
7509
7565
  verifyInheritedSecrets: true,
7566
+ ...pinActiveSecrets ? { pinActiveSecrets } : {},
7510
7567
  atomic,
7511
7568
  ...initialAuthSecret ? { candidateSecrets: { [BETTER_AUTH_SECRET_NAME]: initialAuthSecret } } : {}
7512
7569
  }).finally(() => cfOutput?.dispose());
@@ -7581,9 +7638,8 @@ function formatCloudflareActivationError(result) {
7581
7638
  case "deployment-changed": return result.triggerRepair === "succeeded" ? `deploy: another Cloudflare operation superseded this deployment. Void restored triggers for the active version ${result.activeVersionId ?? "(unknown)"}; retry from fresh deployment status.` : `deploy: another Cloudflare operation superseded this deployment, but its trigger state could not be verified. Inspect the active Worker version ${result.activeVersionId ?? "(unknown)"} immediately.`;
7582
7639
  case "inheritance-source-unavailable": return "deploy: could not verify which Worker Version would supply inherited secrets; production traffic is unchanged.";
7583
7640
  case "inheritance-source-changed": return [
7584
- "deploy: the active Worker Version is not the latest upload, so inherited secrets cannot be pinned safely.",
7585
- "Supply explicit local values for every schema-marked text secret and retry. If the Worker has",
7586
- "non-text secret bindings, inspect its active and latest versions in Cloudflare before proceeding.",
7641
+ "deploy: the active Worker Version is not the latest upload, and its secrets could not be pinned safely.",
7642
+ "Inspect the active and latest Worker versions in Cloudflare before retrying.",
7587
7643
  "No version was uploaded; production traffic is unchanged."
7588
7644
  ].join("\n");
7589
7645
  case "traffic-preservation-unavailable": return ["deploy: the existing Worker has dashboard-managed routes or visibility that Void cannot safely republish.", "Add its current routes, workers_dev, and preview_urls values to the root Wrangler config, then retry; no version was uploaded."].join("\n");
@@ -1,2 +1,2 @@
1
- import { A as parseRootWranglerConfig, J as runDeploy, f as checkWranglerMigrationApplySet } from "./deploy-CsMJrPfM.mjs";
1
+ import { A as parseRootWranglerConfig, J as runDeploy, f as checkWranglerMigrationApplySet } from "./deploy-BfZyWZIn.mjs";
2
2
  export { checkWranglerMigrationApplySet, parseRootWranglerConfig, runDeploy };
@@ -1,6 +1,6 @@
1
1
  import { a as join } from "./dist-BVqXeR_h.mjs";
2
2
  import { c as import_picocolors, i as createSpinner } from "./output-CCH48AMM.mjs";
3
- import { L as resolveDeployTransport, O as isWranglerNonInteractiveOrCI, X as runWranglerAuthToken, _ as deriveWorkerName, _t as findEmailConfigConflicts, bt as renderEmailChecklist, gt as createNodeEmailDns, h as createDeployWranglerRunner, ht as applyEmailSetup, k as loadProvisionModule, v as discoverRootWranglerConfig, vt as preflightEmail, yt as renderAddressMap } from "./deploy-CsMJrPfM.mjs";
3
+ import { L as resolveDeployTransport, O as isWranglerNonInteractiveOrCI, X as runWranglerAuthToken, _ as deriveWorkerName, _t as findEmailConfigConflicts, bt as renderEmailChecklist, gt as createNodeEmailDns, h as createDeployWranglerRunner, ht as applyEmailSetup, k as loadProvisionModule, v as discoverRootWranglerConfig, vt as preflightEmail, yt as renderAddressMap } from "./deploy-BfZyWZIn.mjs";
4
4
  import { n as readProjectPaths } from "./project-paths-GgcMozYz.mjs";
5
5
  import { c as getToken, d as openBrowser } from "./auth-DLNN0D3Z.mjs";
6
6
  import { o as readProjectConfig } from "./project-C0Fqu05O.mjs";
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as __toESM } from "./rolldown-runtime-DXIUcv95.mjs";
2
2
  import { a as join, l as relative, n as dirname, u as resolve } from "./dist-BVqXeR_h.mjs";
3
3
  import { d as require_picocolors } from "./output-CCH48AMM.mjs";
4
- import { G as resolveWorkerDirName, St as hasDocumentRoutes, V as resolveDistDir, ct as prerenderPagesNode, dt as collectRevalidate, ft as hasRevalidate, mt as SEND_EMAIL_BINDING, ot as generateQueueResolverSource, pt as scanDurableObjectModules, st as prerenderPages, ut as collectPages, wt as resolveAssetRoutingPolicy, xt as hasApiRoutes } from "./deploy-CsMJrPfM.mjs";
4
+ import { G as resolveWorkerDirName, St as hasDocumentRoutes, V as resolveDistDir, ct as prerenderPagesNode, dt as collectRevalidate, ft as hasRevalidate, mt as SEND_EMAIL_BINDING, ot as generateQueueResolverSource, pt as scanDurableObjectModules, st as prerenderPages, ut as collectPages, wt as resolveAssetRoutingPolicy, xt as hasApiRoutes } from "./deploy-BfZyWZIn.mjs";
5
5
  import { i as resolveProjectPaths, n as readProjectPaths, t as assertNoRootSourceConventionConflicts } from "./project-paths-GgcMozYz.mjs";
6
6
  import { n as loadVoidAuthConfig, t as findVoidAuthConfig } from "./config-DPByhVck.mjs";
7
7
  import { r as scopedAccessCredentials } from "./cf-access-DRsQRe6k.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "void",
3
- "version": "0.21.3",
3
+ "version": "0.21.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -325,11 +325,11 @@
325
325
  "@cloudflare/vite-plugin": "1.54.7",
326
326
  "@cloudflare/workers-types": "^4.20260702.1",
327
327
  "@napi-rs/keyring": "2.0.0",
328
- "@void/deploy-cloudflare": "0.21.3",
329
- "@void/deploy-core": "0.21.3",
330
- "@void/edge": "0.21.3",
331
- "@void/isr": "0.21.3",
332
- "@void/platform": "0.21.3",
328
+ "@void/deploy-cloudflare": "0.21.5",
329
+ "@void/deploy-core": "0.21.5",
330
+ "@void/edge": "0.21.5",
331
+ "@void/isr": "0.21.5",
332
+ "@void/platform": "0.21.5",
333
333
  "acorn": "^8.15.0",
334
334
  "better-auth": "^1.7.3",
335
335
  "better-sqlite3": "^13.0.3",
@@ -379,7 +379,7 @@
379
379
  "zod": "^4.6.1"
380
380
  },
381
381
  "peerDependencies": {
382
- "@void/md": "0.21.3",
382
+ "@void/md": "0.21.5",
383
383
  "arktype": ">=2.0.0",
384
384
  "valibot": ">=1.0.0-beta.7",
385
385
  "vite": "^8.0.0",
@@ -308,6 +308,7 @@ Custom D1 layouts are supported through `migrations_dir`, `migrations_table`, an
308
308
  Root `.env` is local-only and is never emitted into Worker vars. Every non-client key declared in `env.ts` is a server value: store it with `void secret put <NAME>`. Void emits required server names through `secrets.required`, rejects plaintext Worker vars with those names, and preserves existing remote secrets.
309
309
 
310
310
  For apps using auth, Void creates a random 32-byte `BETTER_AUTH_SECRET` only when the remote secret is missing. It reuses that value on later deploys. Both auth secrets and schema-marked secrets can be set up on a new Worker's draft before its first upload.
311
+ If an earlier deployment leaves a newer inactive upload, Void pins later uploads to the active version's secret bindings. You do not need to recover the plaintext values from Cloudflare.
311
312
 
312
313
  ### Readiness and rollback
313
314
 
@@ -995,6 +995,7 @@ Void records provisioned resource IDs in `void.lock.json`. Commit it for other m
995
995
  `.env` stays local. Store server keys declared in `env.ts` with `void secret put <NAME>`; Void rejects them as plaintext Worker vars. If the first deploy reports missing remote secrets, set them and retry. Custom D1 migration layouts must match the exact files, contents, and order Void validated. Direct deploy and Cloudflare commands use the top-level settings, not named environments or alternate config paths.
996
996
 
997
997
  Existing remote secrets are preserved. Void also preserves or creates `BETTER_AUTH_SECRET` for auth apps.
998
+ If a failed upload leaves a newer inactive version, later deployments inherit secret bindings from the live version without requiring their plaintext values.
998
999
 
999
1000
  **Email.** If the app uses `sendEmail()` or `email/` handlers, set `email.from` in `void.config.ts`. Void shows the Cloudflare account changes and asks before applying them. Later deploys skip the prompt once setup is ready. Without `email.from`, deploy continues without email. For CI, run `void email setup --platform cloudflare` locally and commit `void.lock.json`; pass `--require-email` to fail when email is unavailable. DNS propagation may delay a deploy after setup. See [Email on your own Cloudflare account](../guide/email.md#your-own-cloudflare-account) for setup and recovery.
1000
1001