@saastemly/voidcommerce 0.3.0 → 0.4.0

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.
@@ -11,11 +11,14 @@ import {
11
11
  workerHosts,
12
12
  writeManifest,
13
13
  zone
14
- } from "./index-zh3tmj08.js";
14
+ } from "./index-pz6m2hkm.js";
15
15
  import {
16
- CHOICES,
17
- __require
18
- } from "./index-z4qazajn.js";
16
+ CHOICES
17
+ } from "./index-844b3qn9.js";
18
+ import {
19
+ __require,
20
+ __toESM
21
+ } from "./index-0v6na3yp.js";
19
22
 
20
23
  // src/generate/auth.ts
21
24
  var VAT = {
@@ -841,6 +844,7 @@ function strictDependencies(manifest) {
841
844
  for (const name of [
842
845
  "@hono/node-server",
843
846
  "@dotenvx/dotenvx",
847
+ "husky",
844
848
  "@rolldown/plugin-babel",
845
849
  "@tailwindcss/vite",
846
850
  "@types/node",
@@ -1437,7 +1441,7 @@ import color from "picocolors";
1437
1441
  // package.json
1438
1442
  var package_default = {
1439
1443
  name: "@saastemly/voidcommerce",
1440
- version: "0.3.0",
1444
+ version: "0.4.0",
1441
1445
  description: "Void, with a shop in it. `vc init` walks you through Better Auth, betterCommerce and every plugin; everything else passes through to `void`.",
1442
1446
  type: "module",
1443
1447
  license: "MIT",
@@ -1471,6 +1475,7 @@ var package_default = {
1471
1475
  },
1472
1476
  dependencies: {
1473
1477
  "@clack/prompts": "^0.11.0",
1478
+ "@dotenvx/primitives": "^2.2.0",
1474
1479
  picocolors: "^1.1.1"
1475
1480
  },
1476
1481
  devDependencies: {
@@ -2478,6 +2483,8 @@ async function generateStrictRoot(root, manifest, result) {
2478
2483
  scripts["deploy"] ??= "vc deploy";
2479
2484
  scripts["import:catalog"] ??= "vc import";
2480
2485
  scripts["maildev"] ??= "maildev --smtp 1025 --web 1080";
2486
+ scripts["secrets"] ??= "vc secrets";
2487
+ scripts["prepare"] ??= "husky";
2481
2488
  pkg["scripts"] = scripts;
2482
2489
  }, result);
2483
2490
  await put(root, ".gitignore", `node_modules
@@ -2492,6 +2499,12 @@ dist
2492
2499
  .DS_Store
2493
2500
  `, result, "own");
2494
2501
  await put(root, "patches/void@0.10.13.patch", renderVoidPatch(), result, "regenerate");
2502
+ await put(root, ".husky/pre-commit", `#!/usr/bin/env sh
2503
+ # Generated by \`vc init\`. Refuses a commit that would put a secret in the
2504
+ # clear in .env.secrets — which cannot be undone by a later commit,
2505
+ # because the value stays in the history.
2506
+ bunx vc guard
2507
+ `, result, "regenerate");
2495
2508
  await put(root, ".github/workflows/void-dist.yml", renderDistWorkflow(manifest), result, "regenerate");
2496
2509
  await put(root, "DEPLOY.md", renderDeployReadme(manifest, zone(manifest), workerHosts(manifest)), result, "regenerate");
2497
2510
  await put(root, ".env", renderEnvLocal(manifest), result, "own");
@@ -2856,30 +2869,167 @@ ${ran.out.trim()}`);
2856
2869
  }
2857
2870
 
2858
2871
  // src/deploy/preflight.ts
2859
- import { existsSync as existsSync8, readFileSync as readFileSync3 } from "node:fs";
2860
- import { join as join8 } from "node:path";
2861
- import color4 from "picocolors";
2872
+ import { existsSync as existsSync9, readFileSync as readFileSync4 } from "node:fs";
2873
+ import { join as join9 } from "node:path";
2874
+ import color5 from "picocolors";
2862
2875
 
2863
2876
  // src/deploy/secrets.ts
2864
- import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync } from "node:fs";
2877
+ import { existsSync as existsSync8, readFileSync as readFileSync3, writeFileSync } from "node:fs";
2865
2878
  import { spawn as spawn3 } from "node:child_process";
2866
- import { delimiter as delimiter3, dirname as dirname5, join as join7 } from "node:path";
2879
+ import { delimiter as delimiter3, dirname as dirname5, join as join8 } from "node:path";
2867
2880
  import { tmpdir } from "node:os";
2881
+ import color4 from "picocolors";
2882
+
2883
+ // src/deploy/keys.ts
2884
+ import { hkdfSync } from "node:crypto";
2885
+ import { existsSync as existsSync7, readFileSync as readFileSync2 } from "node:fs";
2886
+ import { join as join7 } from "node:path";
2868
2887
  import color3 from "picocolors";
2888
+ var INFO = "voidcommerce/dotenvx/secrets/v1";
2889
+ function apiToken() {
2890
+ return process.env["CLOUDFLARE_API_TOKEN"] || process.env["CF_API_TOKEN"] || null;
2891
+ }
2892
+ function derivePrivateKey(token, accountId) {
2893
+ const bytes = hkdfSync("sha256", Buffer.from(token, "utf8"), Buffer.from(accountId, "utf8"), Buffer.from(INFO), 32);
2894
+ return Buffer.from(bytes).toString("hex");
2895
+ }
2896
+ async function publicKeyFor(privateKey) {
2897
+ try {
2898
+ const { derive } = await import("./index-mpr7gm6k.js").then((m)=>__toESM(m.default,1));
2899
+ return derive(privateKey);
2900
+ } catch {
2901
+ return null;
2902
+ }
2903
+ }
2904
+ function committedPublicKey(root) {
2905
+ const path = join7(root, SECRETS_FILE);
2906
+ if (!existsSync7(path))
2907
+ return null;
2908
+ const match = /^\s*DOTENV_PUBLIC_KEY_SECRETS\s*=\s*["']?([0-9a-fA-F]+)["']?/m.exec(readFileSync2(path, "utf8"));
2909
+ return match?.[1] ?? null;
2910
+ }
2911
+ async function keyFor(manifest, root) {
2912
+ const token = apiToken();
2913
+ if (!token) {
2914
+ return {
2915
+ ok: false,
2916
+ reason: `CLOUDFLARE_API_TOKEN is not set.
2917
+ The key is derived from it, so there is nothing to derive from. Note that
2918
+ ` + " `wrangler login` does NOT set one — this scheme needs an API token, from\n" + " My Profile → API Tokens."
2919
+ };
2920
+ }
2921
+ const accountId = manifest.cloudflare?.accountId || process.env["CLOUDFLARE_ACCOUNT_ID"] || "";
2922
+ if (!accountId) {
2923
+ return {
2924
+ ok: false,
2925
+ reason: "the account is not pinned, and it salts the derivation.\n Run `vc deploy --cloudflare --provision` once, or set CLOUDFLARE_ACCOUNT_ID."
2926
+ };
2927
+ }
2928
+ const privateKey = derivePrivateKey(token, accountId);
2929
+ const publicKey = await publicKeyFor(privateKey);
2930
+ if (!publicKey) {
2931
+ return { ok: false, reason: "@dotenvx/dotenvx is not installed here, so a key cannot be derived. `bun add -d @dotenvx/dotenvx`" };
2932
+ }
2933
+ const committed = committedPublicKey(root);
2934
+ if (!committed)
2935
+ return { ok: true, privateKey, publicKey, fresh: true };
2936
+ if (committed.toLowerCase() !== publicKey.toLowerCase()) {
2937
+ return {
2938
+ ok: false,
2939
+ reason: `this token does not derive the key ${SECRETS_FILE} was encrypted under.
2940
+
2941
+ encrypted under: ${committed.slice(0, 16)}…
2942
+ ` + ` this token gives: ${publicKey.slice(0, 16)}…
2943
+
2944
+ ` + ` That is one of three things, and all of them are the same fix:
2945
+ ` + ` · the token was rotated since the secrets were encrypted
2946
+ ` + ` · this is a different admin's token
2947
+ ` + ` · CLOUDFLARE_ACCOUNT_ID is not the account they were encrypted for
2948
+
2949
+ ` + ` If you still have the ORIGINAL token, \`vc keys rotate\` re-encrypts
2950
+ everything under the new one. If you do not, the values are unrecoverable
2951
+ and must be entered again.`
2952
+ };
2953
+ }
2954
+ return { ok: true, privateKey, publicKey, fresh: false };
2955
+ }
2956
+ async function keysCommand(manifest, root, args) {
2957
+ if (args.includes("--rotate"))
2958
+ return rotate(manifest, root);
2959
+ const state = await keyFor(manifest, root);
2960
+ console.log(`
2961
+ The key is DERIVED from CLOUDFLARE_API_TOKEN, salted with the account id.`);
2962
+ console.log(color3.dim(`Nothing is stored, so nothing can leak — and nothing can be recovered.
2963
+ `));
2964
+ if (!state.ok) {
2965
+ console.error(`${color3.red("✗")} ${state.reason}
2966
+ `);
2967
+ return 1;
2968
+ }
2969
+ console.log(` derives public key ${state.publicKey.slice(0, 20)}…`);
2970
+ console.log(state.fresh ? ` ${color3.dim(`${SECRETS_FILE} does not exist yet, so there is nothing to check against`)}` : ` ${color3.green("✓")} matches what ${SECRETS_FILE} was encrypted under`);
2971
+ console.log(color3.yellow(`
2972
+ ! Rotating this API token makes every secret in ${SECRETS_FILE} unreadable.
2973
+ Run \`vc keys --rotate\` with the NEW token exported and the old one in
2974
+ CLOUDFLARE_API_TOKEN_OLD, BEFORE the old one stops working.
2975
+ `));
2976
+ return 0;
2977
+ }
2978
+ async function rotate(manifest, root) {
2979
+ const oldToken = process.env["CLOUDFLARE_API_TOKEN_OLD"];
2980
+ const newToken = apiToken();
2981
+ if (!oldToken || !newToken) {
2982
+ console.error(`
2983
+ vc: rotation needs BOTH tokens:
2984
+ CLOUDFLARE_API_TOKEN_OLD=<the one the secrets were encrypted under>
2985
+ CLOUDFLARE_API_TOKEN=<the new one>
2986
+
2987
+ The old one is the only thing that can read the current values.
2988
+ `);
2989
+ return 1;
2990
+ }
2991
+ const accountId = manifest.cloudflare?.accountId || process.env["CLOUDFLARE_ACCOUNT_ID"] || "";
2992
+ if (!accountId) {
2993
+ console.error("vc: the account is not pinned, and it salts the derivation.");
2994
+ return 1;
2995
+ }
2996
+ const oldKey = derivePrivateKey(oldToken, accountId);
2997
+ const oldPublic = await publicKeyFor(oldKey);
2998
+ const committed = committedPublicKey(root);
2999
+ if (committed && oldPublic && committed.toLowerCase() !== oldPublic.toLowerCase()) {
3000
+ console.error(`
3001
+ vc: CLOUDFLARE_API_TOKEN_OLD does not derive the key ${SECRETS_FILE} was encrypted under either.
3002
+ `);
3003
+ return 1;
3004
+ }
3005
+ console.log(`
3006
+ Rotation is a decrypt with the old key and an encrypt with the new one.
3007
+ Run these two, in this order, from ${root}:
3008
+
3009
+ ${color3.cyan(`DOTENV_PRIVATE_KEY_SECRETS=<old> bunx dotenvx decrypt -f ${SECRETS_FILE}`)}
3010
+ ${color3.cyan(`bunx dotenvx encrypt -f ${SECRETS_FILE}`)} ${color3.dim("# under the new derived key")}
3011
+
3012
+ ` + color3.dim(`vc does not run them for you: the middle state is your secrets in
3013
+ plaintext on disk, and that is a moment to be deliberate about.
3014
+ `));
3015
+ return 0;
3016
+ }
3017
+
3018
+ // src/deploy/secrets.ts
2869
3019
  var SECRETS_FILE = ".env.secrets";
2870
3020
  var PRIVATE_KEY_VAR = "DOTENV_PRIVATE_KEY_SECRETS";
2871
3021
  function findDotenvx(from) {
2872
3022
  let dir = from;
2873
3023
  for (;; ) {
2874
- const local = join7(dir, "node_modules", ".bin", "dotenvx");
2875
- if (existsSync7(local))
3024
+ const local = join8(dir, "node_modules", ".bin", "dotenvx");
3025
+ if (existsSync8(local))
2876
3026
  return local;
2877
3027
  const parent = dirname5(dir);
2878
3028
  if (parent === dir)
2879
3029
  break;
2880
3030
  dir = parent;
2881
3031
  }
2882
- return (process.env["PATH"] ?? "").split(delimiter3).filter(Boolean).some((entry) => existsSync7(join7(entry, "dotenvx"))) ? "dotenvx" : null;
3032
+ return (process.env["PATH"] ?? "").split(delimiter3).filter(Boolean).some((entry) => existsSync8(join8(entry, "dotenvx"))) ? "dotenvx" : null;
2883
3033
  }
2884
3034
  function run(cmd, args, cwd, env = {}) {
2885
3035
  return new Promise((resolve) => {
@@ -2896,11 +3046,11 @@ function run(cmd, args, cwd, env = {}) {
2896
3046
  });
2897
3047
  }
2898
3048
  function declaredSecretNames(root) {
2899
- const path = join7(root, SECRETS_FILE);
2900
- if (!existsSync7(path))
3049
+ const path = join8(root, SECRETS_FILE);
3050
+ if (!existsSync8(path))
2901
3051
  return new Set;
2902
3052
  const names = new Set;
2903
- for (const line2 of readFileSync2(path, "utf8").split(`
3053
+ for (const line2 of readFileSync3(path, "utf8").split(`
2904
3054
  `)) {
2905
3055
  const match = /^\s*([A-Z][A-Z0-9_]*)\s*=/.exec(line2);
2906
3056
  if (match && !match[1].startsWith("DOTENV_"))
@@ -2909,11 +3059,11 @@ function declaredSecretNames(root) {
2909
3059
  return names;
2910
3060
  }
2911
3061
  function plaintextSecretNames(root) {
2912
- const path = join7(root, SECRETS_FILE);
2913
- if (!existsSync7(path))
3062
+ const path = join8(root, SECRETS_FILE);
3063
+ if (!existsSync8(path))
2914
3064
  return [];
2915
3065
  const bare = [];
2916
- for (const line2 of readFileSync2(path, "utf8").split(`
3066
+ for (const line2 of readFileSync3(path, "utf8").split(`
2917
3067
  `)) {
2918
3068
  const match = /^\s*([A-Z][A-Z0-9_]*)\s*=\s*(.*)$/.exec(line2);
2919
3069
  if (!match || match[1].startsWith("DOTENV_"))
@@ -2926,25 +3076,24 @@ function plaintextSecretNames(root) {
2926
3076
  }
2927
3077
  async function decryptSecrets(project) {
2928
3078
  const root = project.root;
2929
- if (!existsSync7(join7(root, SECRETS_FILE)))
3079
+ if (!existsSync8(join8(root, SECRETS_FILE)))
2930
3080
  return { error: `${SECRETS_FILE} does not exist — \`vc secrets init\` writes one` };
2931
3081
  const dotenvx = findDotenvx(root);
2932
3082
  if (!dotenvx)
2933
3083
  return { error: "dotenvx is not installed. `bun add -d @dotenvx/dotenvx`" };
2934
- if (!process.env[PRIVATE_KEY_VAR] && !existsSync7(join7(root, ".env.keys"))) {
2935
- return {
2936
- error: `${PRIVATE_KEY_VAR} is not set and there is no .env.keys here.
2937
- It is the one value that stays out of the repository; set it where the deploy runs.`
2938
- };
2939
- }
2940
- const result = await run(dotenvx, ["decrypt", "-f", SECRETS_FILE, "--stdout"], root);
3084
+ const key = await keyFor(project.manifest, root);
3085
+ if (!key.ok)
3086
+ return { error: key.reason };
3087
+ const result = await run(dotenvx, ["decrypt", "-f", SECRETS_FILE, "--stdout"], root, {
3088
+ [PRIVATE_KEY_VAR]: key.privateKey
3089
+ });
2941
3090
  if (result.code !== 0)
2942
3091
  return { error: `dotenvx could not decrypt ${SECRETS_FILE}: ${result.out.trim().split(`
2943
3092
  `).slice(-2).join(" ")}` };
2944
3093
  const lines = result.out.split(`
2945
3094
  `).filter((line2) => /^\s*[A-Z][A-Z0-9_]*\s*=/.test(line2) && !line2.trimStart().startsWith("DOTENV_"));
2946
3095
  const names = lines.map((line2) => line2.slice(0, line2.indexOf("=")).trim());
2947
- const path = join7(tmpdir(), `vc-secrets-${process.pid}-${Date.now()}.env`);
3096
+ const path = join8(tmpdir(), `vc-secrets-${process.pid}-${Date.now()}.env`);
2948
3097
  writeFileSync(path, `${lines.join(`
2949
3098
  `)}
2950
3099
  `, { mode: 384 });
@@ -2966,9 +3115,9 @@ async function secretsCommand(project, args) {
2966
3115
  const bare = plaintextSecretNames(root);
2967
3116
  if (args.includes("--init"))
2968
3117
  return initSecrets(project);
2969
- if (!existsSync7(join7(root, SECRETS_FILE))) {
3118
+ if (!existsSync8(join8(root, SECRETS_FILE))) {
2970
3119
  console.log(`
2971
- ${color3.yellow("No " + SECRETS_FILE + " yet.")} Secrets are set by hand with \`wrangler secret put\`,
3120
+ ${color4.yellow("No " + SECRETS_FILE + " yet.")} Secrets are set by hand with \`wrangler secret put\`,
2972
3121
  ` + `which means the shop cannot be rebuilt from a checkout.
2973
3122
 
2974
3123
  ` + ` vc secrets --init write one, with every required key as \`unset\`
@@ -2979,15 +3128,15 @@ ${color3.yellow("No " + SECRETS_FILE + " yet.")} Secrets are set by hand with \`
2979
3128
  ${SECRETS_FILE} — committed, encrypted, and read by \`vc deploy --cloudflare\`
2980
3129
  `);
2981
3130
  for (const key of required) {
2982
- const state = declared.has(key.key) ? color3.green("declared") : color3.red("MISSING ");
3131
+ const state = declared.has(key.key) ? color4.green("declared") : color4.red("MISSING ");
2983
3132
  console.log(` ${state} ${key.key}`);
2984
3133
  }
2985
3134
  const extra = [...declared].filter((name) => !required.some((key) => key.key === name));
2986
3135
  for (const name of extra)
2987
- console.log(` ${color3.dim("extra ")} ${name} ${color3.dim("— not required by this shop")}`);
3136
+ console.log(` ${color4.dim("extra ")} ${name} ${color4.dim("— not required by this shop")}`);
2988
3137
  if (bare.length > 0) {
2989
3138
  console.log(`
2990
- ${color3.red("✗")} ${bare.length} value${bare.length === 1 ? " is" : "s are"} committed IN THE CLEAR: ${bare.join(", ")}
3139
+ ${color4.red("✗")} ${bare.length} value${bare.length === 1 ? " is" : "s are"} committed IN THE CLEAR: ${bare.join(", ")}
2991
3140
  ` + ` Run \`dotenvx encrypt -f ${SECRETS_FILE}\` before committing again.
2992
3141
  `);
2993
3142
  return 1;
@@ -2995,23 +3144,30 @@ ${color3.red("✗")} ${bare.length} value${bare.length === 1 ? " is" : "s are"}
2995
3144
  const missing = required.filter((key) => !declared.has(key.key));
2996
3145
  if (missing.length > 0) {
2997
3146
  console.log(`
2998
- ${color3.red("✗")} ${missing.length} required secret${missing.length === 1 ? "" : "s"} not declared.
3147
+ ${color4.red("✗")} ${missing.length} required secret${missing.length === 1 ? "" : "s"} not declared.
2999
3148
  ` + ` dotenvx set ${missing[0].key} '…' -f ${SECRETS_FILE}
3000
3149
  `);
3001
3150
  return 1;
3002
3151
  }
3003
3152
  console.log(`
3004
- ${color3.green("✓")} every secret this shop needs is declared and encrypted.
3153
+ ${color4.green("✓")} every secret this shop needs is declared and encrypted.
3005
3154
  `);
3006
3155
  return 0;
3007
3156
  }
3008
3157
  async function initSecrets(project) {
3009
- const path = join7(project.root, SECRETS_FILE);
3010
- if (existsSync7(path)) {
3158
+ const path = join8(project.root, SECRETS_FILE);
3159
+ if (existsSync8(path)) {
3011
3160
  console.error(`vc: ${SECRETS_FILE} already exists; not overwriting it.`);
3012
3161
  return 1;
3013
3162
  }
3014
- const required = allEnvKeys(project.manifest).filter((key) => !key.plaintext);
3163
+ const key = await keyFor(project.manifest, project.root);
3164
+ if (!key.ok) {
3165
+ console.error(`
3166
+ vc: ${key.reason}
3167
+ `);
3168
+ return 1;
3169
+ }
3170
+ const required = allEnvKeys(project.manifest).filter((key2) => !key2.plaintext);
3015
3171
  const body = [
3016
3172
  "# Secrets, encrypted, and COMMITTED.",
3017
3173
  "#",
@@ -3019,22 +3175,29 @@ async function initSecrets(project) {
3019
3175
  "# secret changed without showing what it changed to. `unset` is the",
3020
3176
  "# documented placeholder and preflight refuses it.",
3021
3177
  "#",
3022
- "# The private key stays out of the repository. It lives in .env.keys",
3023
- "# locally (gitignored) and as one build variable where the deploy runs.",
3178
+ "# The private key is DERIVED from CLOUDFLARE_API_TOKEN, salted with the",
3179
+ "# account id it is stored nowhere, so there is no .env.keys to leak.",
3180
+ "#",
3181
+ "# The cost of that: rotating the token makes every value below",
3182
+ "# permanently unreadable. `vc keys --rotate` re-encrypts while you still",
3183
+ "# have the old token; after that there is no recovery.",
3024
3184
  "",
3025
- ...required.flatMap((key) => [`# ${key.breaks}${key.where ? ` — from: ${key.where}` : ""}`, `${key.key}=unset`, ""])
3185
+ `DOTENV_PUBLIC_KEY_SECRETS="${key.publicKey}"`,
3186
+ "",
3187
+ ...required.flatMap((key2) => [`# ${key2.breaks}${key2.where ? ` — from: ${key2.where}` : ""}`, `${key2.key}=unset`, ""])
3026
3188
  ].join(`
3027
3189
  `);
3028
3190
  writeFileSync(path, body, { mode: 384 });
3029
3191
  console.log(`
3030
- ${color3.green("+")} ${SECRETS_FILE} — ${required.length} keys, all \`unset\`
3192
+ ${color4.green("+")} ${SECRETS_FILE} — ${required.length} keys, all \`unset\`, under the key this token derives
3031
3193
 
3032
3194
  ` + `Next:
3033
3195
  ` + ` 1. put the real values in, then
3034
- ` + ` 2. ${color3.cyan(`dotenvx encrypt -f ${SECRETS_FILE}`)}
3035
- ` + ` 3. commit ${SECRETS_FILE}; never commit .env.keys
3036
- ` + ` 4. set ${PRIVATE_KEY_VAR} where the deploy runs
3037
- `);
3196
+ ` + ` 2. ${color4.cyan(`bunx dotenvx encrypt -f ${SECRETS_FILE}`)}
3197
+ ` + ` 3. commit it there is no key file to keep out
3198
+
3199
+ ` + color4.yellow(`! Rotating CLOUDFLARE_API_TOKEN makes these unreadable. \`vc keys\` explains.
3200
+ `));
3038
3201
  return 0;
3039
3202
  }
3040
3203
 
@@ -3042,10 +3205,10 @@ ${color3.green("+")} ${SECRETS_FILE} — ${required.length} keys, all \`unset\`
3042
3205
  var UNSET = "unset";
3043
3206
  function productionEnv(appDir) {
3044
3207
  const out = new Map;
3045
- const path = join8(appDir, ".env.production");
3046
- if (!existsSync8(path))
3208
+ const path = join9(appDir, ".env.production");
3209
+ if (!existsSync9(path))
3047
3210
  return out;
3048
- for (const line2 of readFileSync3(path, "utf8").split(`
3211
+ for (const line2 of readFileSync4(path, "utf8").split(`
3049
3212
  `)) {
3050
3213
  const match = /^\s*([A-Z0-9_]+)\s*=\s*(.*)$/.exec(line2);
3051
3214
  if (!match)
@@ -3070,12 +3233,12 @@ async function voidSecretNames(appDir) {
3070
3233
  return names;
3071
3234
  }
3072
3235
  function routeProblem(project) {
3073
- const path = join8(project.appDir, "wrangler.jsonc");
3074
- if (!existsSync8(path))
3236
+ const path = join9(project.appDir, "wrangler.jsonc");
3237
+ if (!existsSync9(path))
3075
3238
  return "wrangler.jsonc is missing";
3076
3239
  let routes = [];
3077
3240
  try {
3078
- routes = parseJsonc(readFileSync3(path, "utf8")).routes ?? [];
3241
+ routes = parseJsonc(readFileSync4(path, "utf8")).routes ?? [];
3079
3242
  } catch {
3080
3243
  return "wrangler.jsonc cannot be parsed";
3081
3244
  }
@@ -3115,7 +3278,7 @@ async function preflight(project, source) {
3115
3278
  function printPreflight(project, result, source) {
3116
3279
  const keys = allEnvKeys(project.manifest);
3117
3280
  if (result.remote === null) {
3118
- console.log(color4.dim(source === "wrangler" ? `Could not read the worker's secrets — not deployed yet, or wrangler is not logged in.
3281
+ console.log(color5.dim(source === "wrangler" ? `Could not read the worker's secrets — not deployed yet, or wrangler is not logged in.
3119
3282
  Anything not in .env.production is reported as missing.
3120
3283
  ` : `Could not read the project's secrets from Void — not logged in, or no linked project.
3121
3284
  Anything not in .env.production is reported as missing.
@@ -3125,37 +3288,37 @@ Anything not in .env.production is reported as missing.
3125
3288
  for (const key of keys) {
3126
3289
  const value = result.present.get(key.key);
3127
3290
  const set = value !== undefined && value !== "" && value !== UNSET;
3128
- console.log(` ${set ? color4.green("set ") : color4.dim("unset")} ${key.key}${key.plaintext ? color4.dim(" (plaintext)") : ""}`);
3291
+ console.log(` ${set ? color5.green("set ") : color5.dim("unset")} ${key.key}${key.plaintext ? color5.dim(" (plaintext)") : ""}`);
3129
3292
  }
3130
3293
  if (result.ready) {
3131
3294
  console.log(`
3132
- ${color4.green("Ready to go live.")}
3295
+ ${color5.green("Ready to go live.")}
3133
3296
  `);
3134
3297
  return;
3135
3298
  }
3136
3299
  console.log(`
3137
- ${color4.red("NOT ready to go live.")}
3300
+ ${color5.red("NOT ready to go live.")}
3138
3301
  `);
3139
3302
  if (result.routeProblem) {
3140
- console.log(`${color4.red("✗")} the worker's hostname
3303
+ console.log(`${color5.red("✗")} the worker's hostname
3141
3304
  ${result.routeProblem}
3142
3305
  `);
3143
3306
  }
3144
3307
  if (result.bareSecrets.length > 0) {
3145
- console.log(`${color4.red("✗")} committed in the clear in ${SECRETS_FILE}: ${result.bareSecrets.join(", ")}
3308
+ console.log(`${color5.red("✗")} committed in the clear in ${SECRETS_FILE}: ${result.bareSecrets.join(", ")}
3146
3309
  ` + ` Run \`dotenvx encrypt -f ${SECRETS_FILE}\` — and treat those values as burned.
3147
3310
  `);
3148
3311
  }
3149
3312
  for (const key of result.missing) {
3150
- console.log(`${color4.red("✗")} ${key.key}`);
3313
+ console.log(`${color5.red("✗")} ${key.key}`);
3151
3314
  console.log(` ${key.breaks}`);
3152
3315
  if (key.where)
3153
- console.log(` ${color4.dim(`from: ${key.where}`)}`);
3316
+ console.log(` ${color5.dim(`from: ${key.where}`)}`);
3154
3317
  console.log("");
3155
3318
  }
3156
3319
  const secrets = result.missing.filter((key) => !key.plaintext);
3157
3320
  if (secrets.length > 0) {
3158
- const inRepo = existsSync8(join8(project.root, SECRETS_FILE));
3321
+ const inRepo = existsSync9(join9(project.root, SECRETS_FILE));
3159
3322
  console.log(inRepo ? `Put each in ${SECRETS_FILE}, which the deploy uploads:` : source === "wrangler" ? "Set each secret on the worker (this also creates the draft worker on a first deploy):" : "Set each secret on the project:");
3160
3323
  for (const key of secrets) {
3161
3324
  console.log(inRepo ? ` dotenvx set ${key.key} '…' -f ${SECRETS_FILE}` : ` ${source === "wrangler" ? "wrangler secret put" : "void secret put"} ${key.key}`);
@@ -3168,17 +3331,17 @@ ${color4.red("NOT ready to go live.")}
3168
3331
  }
3169
3332
 
3170
3333
  // src/deploy/cloudflare.ts
3171
- import { existsSync as existsSync9, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
3172
- import { dirname as dirname6, join as join9 } from "node:path";
3173
- import color5 from "picocolors";
3334
+ import { existsSync as existsSync10, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
3335
+ import { dirname as dirname6, join as join10 } from "node:path";
3336
+ import color6 from "picocolors";
3174
3337
  var fail = (message) => {
3175
3338
  console.error(`
3176
- ${color5.red("✗")} ${message}
3339
+ ${color6.red("✗")} ${message}
3177
3340
  `);
3178
3341
  return 1;
3179
3342
  };
3180
3343
  function readConfig(path) {
3181
- return parseJsonc(readFileSync4(path, "utf8"));
3344
+ return parseJsonc(readFileSync5(path, "utf8"));
3182
3345
  }
3183
3346
  function findBuilder(from) {
3184
3347
  let dir = from;
@@ -3187,8 +3350,8 @@ function findBuilder(from) {
3187
3350
  ["vp", "vp build"],
3188
3351
  ["vite", "vite build"]
3189
3352
  ]) {
3190
- const path = join9(dir, "node_modules", ".bin", bin);
3191
- if (existsSync9(path))
3353
+ const path = join10(dir, "node_modules", ".bin", bin);
3354
+ if (existsSync10(path))
3192
3355
  return { cmd: path, label };
3193
3356
  }
3194
3357
  const parent = dirname6(dir);
@@ -3199,8 +3362,8 @@ function findBuilder(from) {
3199
3362
  }
3200
3363
  async function deployCloudflare(project, opts) {
3201
3364
  const app = project.appDir;
3202
- const configPath = join9(app, "wrangler.jsonc");
3203
- if (!existsSync9(configPath))
3365
+ const configPath = join10(app, "wrangler.jsonc");
3366
+ if (!existsSync10(configPath))
3204
3367
  return fail("wrangler.jsonc is missing — `vc generate` writes it.");
3205
3368
  const bin = findWrangler(app);
3206
3369
  if (!bin)
@@ -3213,16 +3376,16 @@ async function deployCloudflare(project, opts) {
3213
3376
  ${who.raw.trim().split(`
3214
3377
  `).slice(-4).join(`
3215
3378
  `)}`);
3216
- console.log(`${color5.green("✓")} wrangler is logged in`);
3379
+ console.log(`${color6.green("✓")} wrangler is logged in`);
3217
3380
  let config = readConfig(configPath);
3218
3381
  let accountId = config.account_id || process.env["CLOUDFLARE_ACCOUNT_ID"] || "";
3219
3382
  if (!accountId) {
3220
3383
  if (who.accounts.length === 1) {
3221
3384
  accountId = who.accounts[0].id;
3222
- writeFileSync2(configPath, upsertJsonc(readFileSync4(configPath, "utf8"), "account_id", accountId));
3385
+ writeFileSync2(configPath, upsertJsonc(readFileSync5(configPath, "utf8"), "account_id", accountId));
3223
3386
  project.manifest.cloudflare = { ...project.manifest.cloudflare, accountId };
3224
3387
  await writeManifest(project.root, project.manifest);
3225
- console.log(`${color5.green("✓")} pinned the account "${who.accounts[0].name}" in wrangler.jsonc`);
3388
+ console.log(`${color6.green("✓")} pinned the account "${who.accounts[0].name}" in wrangler.jsonc`);
3226
3389
  } else {
3227
3390
  return fail(`the account is not pinned and wrangler sees ${who.accounts.length}. Set account_id in wrangler.jsonc to one of:
3228
3391
  ${who.accounts.map((a) => ` ${a.id} ${a.name}`).join(`
@@ -3234,7 +3397,7 @@ ${who.accounts.map((a) => ` ${a.id} ${a.name}`).join(`
3234
3397
  if (!check.ready) {
3235
3398
  if (!opts.force)
3236
3399
  return fail("refusing to deploy a shop that is not ready for customers. Fix the above, or pass --force for a deliberate partial deploy.");
3237
- console.error(color5.yellow(`--force: deploying a shop that is NOT ready for customers.
3400
+ console.error(color6.yellow(`--force: deploying a shop that is NOT ready for customers.
3238
3401
  `));
3239
3402
  }
3240
3403
  const worker = config.name || project.manifest.shop.domain.split(".")[0];
@@ -3245,15 +3408,15 @@ ${who.accounts.map((a) => ` ${a.id} ${a.name}`).join(`
3245
3408
  return fail("the D1 database is not provisioned. Run once with --provision to create it and record its id.");
3246
3409
  const db = await ensureD1(bin, app, `${worker}-db`);
3247
3410
  const entry = { binding: "DB", database_name: db.name, database_id: db.uuid, migrations_dir: "./db/migrations" };
3248
- writeFileSync2(configPath, upsertJsonc(readFileSync4(configPath, "utf8"), "d1_databases", [entry]));
3411
+ writeFileSync2(configPath, upsertJsonc(readFileSync5(configPath, "utf8"), "d1_databases", [entry]));
3249
3412
  project.manifest.cloudflare = { ...project.manifest.cloudflare, accountId, d1: { name: db.name, id: db.uuid } };
3250
3413
  await writeManifest(project.root, project.manifest);
3251
- console.log(`${color5.green("✓")} D1 "${db.name}" recorded in wrangler.jsonc and voidcommerce.json`);
3414
+ console.log(`${color6.green("✓")} D1 "${db.name}" recorded in wrangler.jsonc and voidcommerce.json`);
3252
3415
  config = readConfig(configPath);
3253
3416
  }
3254
3417
  if (opts.provision) {
3255
3418
  await ensureQueue(bin, app, "commerce");
3256
- console.log(`${color5.green("✓")} queue "commerce"`);
3419
+ console.log(`${color6.green("✓")} queue "commerce"`);
3257
3420
  }
3258
3421
  const builder = findBuilder(app);
3259
3422
  if (!builder)
@@ -3263,10 +3426,10 @@ ${who.accounts.map((a) => ` ${a.id} ${a.name}`).join(`
3263
3426
  const built = await wrangler(builder.cmd, ["build"], app, true);
3264
3427
  if (built.code !== 0)
3265
3428
  return fail(`the build failed (exit ${built.code}).`);
3266
- const emittedPath = join9(app, "dist", "ssr", "wrangler.json");
3267
- if (!existsSync9(emittedPath))
3429
+ const emittedPath = join10(app, "dist", "ssr", "wrangler.json");
3430
+ if (!existsSync10(emittedPath))
3268
3431
  return fail(`the build emitted no ${emittedPath} — is this a Void app on the Cloudflare target?`);
3269
- const emitted = JSON.parse(readFileSync4(emittedPath, "utf8"));
3432
+ const emitted = JSON.parse(readFileSync5(emittedPath, "utf8"));
3270
3433
  const secretKeys = new Set(allEnvKeys(project.manifest).filter((key) => !key.plaintext).map((key) => key.key));
3271
3434
  const scrubbed = [];
3272
3435
  for (const [key, value] of Object.entries(emitted.vars ?? {})) {
@@ -3279,7 +3442,7 @@ ${who.accounts.map((a) => ` ${a.id} ${a.name}`).join(`
3279
3442
  if (!emittedD1 || emittedD1.database_id === "local")
3280
3443
  return fail("the emitted config still carries a placeholder D1 id; wrangler.jsonc's DB binding was not picked up by the build.");
3281
3444
  writeFileSync2(emittedPath, JSON.stringify(emitted, null, 2));
3282
- console.log(`${color5.green("✓")} scrubbed ${scrubbed.length} baked value${scrubbed.length === 1 ? "" : "s"} from the worker's vars${scrubbed.length ? `: ${scrubbed.join(", ")}` : ""}`);
3445
+ console.log(`${color6.green("✓")} scrubbed ${scrubbed.length} baked value${scrubbed.length === 1 ? "" : "s"} from the worker's vars${scrubbed.length ? `: ${scrubbed.join(", ")}` : ""}`);
3283
3446
  console.log(`
3284
3447
  ▸ wrangler d1 migrations apply ${emittedD1.database_name} --remote`);
3285
3448
  const migrated = await wrangler(bin, ["d1", "migrations", "apply", emittedD1.database_name, "--remote"], app, true);
@@ -3287,34 +3450,34 @@ ${who.accounts.map((a) => ` ${a.id} ${a.name}`).join(`
3287
3450
  return fail(`applying migrations failed (exit ${migrated.code}); nothing was deployed.`);
3288
3451
  const secretArgs = [];
3289
3452
  let cleanupSecrets;
3290
- if (existsSync9(join9(project.root, SECRETS_FILE))) {
3453
+ if (existsSync10(join10(project.root, SECRETS_FILE))) {
3291
3454
  const decrypted = await decryptSecrets(project);
3292
3455
  if ("error" in decrypted)
3293
3456
  return fail(`the repository's secrets could not be read: ${decrypted.error}`);
3294
3457
  secretArgs.push("--secrets-file", decrypted.path);
3295
3458
  cleanupSecrets = decrypted.cleanup;
3296
- console.log(`${color5.green("✓")} ${decrypted.names.length} secrets from ${SECRETS_FILE}, uploaded with this version`);
3459
+ console.log(`${color6.green("✓")} ${decrypted.names.length} secrets from ${SECRETS_FILE}, uploaded with this version`);
3297
3460
  }
3298
3461
  console.log(`
3299
3462
  ▸ wrangler deploy -c dist/ssr/wrangler.json`);
3300
- const deployed = await wrangler(bin, ["deploy", "-c", join9("dist", "ssr", "wrangler.json"), ...secretArgs], app, true);
3463
+ const deployed = await wrangler(bin, ["deploy", "-c", join10("dist", "ssr", "wrangler.json"), ...secretArgs], app, true);
3301
3464
  cleanupSecrets?.();
3302
3465
  if (deployed.code !== 0)
3303
3466
  return fail(`wrangler deploy failed (exit ${deployed.code}).`);
3304
3467
  const domain = project.manifest.shop.domain;
3305
3468
  const url = `https://${workerHosts(project.manifest)[0]}`;
3306
3469
  console.log(`
3307
- ${color5.green("Live:")} ${url}`);
3470
+ ${color6.green("Live:")} ${url}`);
3308
3471
  if (hasFrontend(project.manifest.layout))
3309
- console.log(color5.dim("The storefront deploys itself from GitHub Actions on push."));
3472
+ console.log(color6.dim("The storefront deploys itself from GitHub Actions on push."));
3310
3473
  return 0;
3311
3474
  }
3312
3475
 
3313
3476
  // src/import.ts
3314
3477
  import { readFile as readFile3 } from "node:fs/promises";
3315
- import { existsSync as existsSync10 } from "node:fs";
3316
- import { join as join10 } from "node:path";
3317
- import color6 from "picocolors";
3478
+ import { existsSync as existsSync11 } from "node:fs";
3479
+ import { join as join11 } from "node:path";
3480
+ import color7 from "picocolors";
3318
3481
  function describe(counts) {
3319
3482
  if (!counts)
3320
3483
  return "";
@@ -3353,7 +3516,7 @@ async function post(url, key, body) {
3353
3516
  return { ok: response.ok, status: response.status, body: { ...parsed, error: parsed.error ?? parsed.message } };
3354
3517
  }
3355
3518
  async function readJson(path) {
3356
- if (!existsSync10(path))
3519
+ if (!existsSync11(path))
3357
3520
  return null;
3358
3521
  try {
3359
3522
  return JSON.parse(await readFile3(path, "utf8"));
@@ -3381,41 +3544,41 @@ async function importCommand(args) {
3381
3544
  }
3382
3545
  const { url, how } = resolveTarget(project, args);
3383
3546
  if (!dry)
3384
- console.log(`${color6.dim("→")} ${url} ${color6.dim(`(${how})`)}
3547
+ console.log(`${color7.dim("→")} ${url} ${color7.dim(`(${how})`)}
3385
3548
  `);
3386
3549
  if (!dry) {
3387
3550
  let whoami2;
3388
3551
  try {
3389
3552
  whoami2 = await fetch(`${url}/api/auth/system/whoami`, { headers: { "x-system-key": key } });
3390
3553
  } catch (error) {
3391
- console.error(`${color6.red("✗")} could not reach ${url}: ${error instanceof Error ? error.message : String(error)}
3554
+ console.error(`${color7.red("✗")} could not reach ${url}: ${error instanceof Error ? error.message : String(error)}
3392
3555
  ` + ` Is the shop running? \`vc dev\` serves it locally; --local points here.
3393
3556
  `);
3394
3557
  return 1;
3395
3558
  }
3396
3559
  if (!whoami2.ok) {
3397
- console.error(`${color6.red("✗")} the shop refused the system key (HTTP ${whoami2.status}).
3560
+ console.error(`${color7.red("✗")} the shop refused the system key (HTTP ${whoami2.status}).
3398
3561
  ` + ` Is SYSTEM_API_KEY the one this deployment was given?
3399
3562
  `);
3400
3563
  return 1;
3401
3564
  }
3402
- console.log(`${color6.green("✓")} authenticated as the system identity`);
3565
+ console.log(`${color7.green("✓")} authenticated as the system identity`);
3403
3566
  }
3404
3567
  const root = project.root;
3405
- const products = await readJson(join10(root, "data", "catalog.json"));
3406
- const categories = await readJson(join10(root, "data", "categories.json"));
3407
- const faqs = has(project.manifest, "faqs") ? await readJson(join10(root, "content", "faqs.json")) : null;
3408
- const posts = has(project.manifest, "blogs") ? await readJson(join10(root, "content", "posts.json")) : null;
3568
+ const products = await readJson(join11(root, "data", "catalog.json"));
3569
+ const categories = await readJson(join11(root, "data", "categories.json"));
3570
+ const faqs = has(project.manifest, "faqs") ? await readJson(join11(root, "content", "faqs.json")) : null;
3571
+ const posts = has(project.manifest, "blogs") ? await readJson(join11(root, "content", "posts.json")) : null;
3409
3572
  if (!products && !faqs?.length && !posts?.length) {
3410
3573
  console.log(`
3411
- ${color6.yellow("Nothing to import.")} data/catalog.json is absent and the content files are empty.
3574
+ ${color7.yellow("Nothing to import.")} data/catalog.json is absent and the content files are empty.
3412
3575
  ` + `Products go in data/catalog.json as a JSON array; \`vc import --dry-run\` checks it without pushing.
3413
3576
  `);
3414
3577
  return 0;
3415
3578
  }
3416
3579
  if (dry) {
3417
3580
  console.log(`
3418
- ${color6.dim("--dry-run: nothing was pushed.")}`);
3581
+ ${color7.dim("--dry-run: nothing was pushed.")}`);
3419
3582
  console.log(` ${products?.length ?? 0} products, ${categories?.length ?? 0} categories`);
3420
3583
  console.log(` ${faqs?.length ?? 0} FAQ entries, ${posts?.length ?? 0} posts
3421
3584
  `);
@@ -3431,15 +3594,15 @@ ${color6.dim("--dry-run: nothing was pushed.")}`);
3431
3594
  });
3432
3595
  if (result.ok) {
3433
3596
  const report = result.body.report ?? {};
3434
- console.log(`${color6.green("✓")} products: ${describe(report.products)}`);
3597
+ console.log(`${color7.green("✓")} products: ${describe(report.products)}`);
3435
3598
  if (report.categories)
3436
- console.log(`${color6.green("✓")} categories: ${describe(report.categories)}`);
3599
+ console.log(`${color7.green("✓")} categories: ${describe(report.categories)}`);
3437
3600
  if (report.prices)
3438
- console.log(`${color6.green("✓")} prices: ${describe(report.prices)}`);
3601
+ console.log(`${color7.green("✓")} prices: ${describe(report.prices)}`);
3439
3602
  if (report.addons)
3440
- console.log(`${color6.green("✓")} addons: ${describe(report.addons)}`);
3603
+ console.log(`${color7.green("✓")} addons: ${describe(report.addons)}`);
3441
3604
  } else {
3442
- console.error(`${color6.red("✗")} catalogue: ${result.body.error ?? `HTTP ${result.status}`}`);
3605
+ console.error(`${color7.red("✗")} catalogue: ${result.body.error ?? `HTTP ${result.status}`}`);
3443
3606
  failed = true;
3444
3607
  }
3445
3608
  }
@@ -3451,20 +3614,20 @@ ${color6.dim("--dry-run: nothing was pushed.")}`);
3451
3614
  continue;
3452
3615
  const result = await post(`${url}${path}`, key, { entries: rows, posts: rows });
3453
3616
  if (result.ok) {
3454
- console.log(`${color6.green("✓")} ${label}: ${describe(result.body)}`);
3617
+ console.log(`${color7.green("✓")} ${label}: ${describe(result.body)}`);
3455
3618
  } else {
3456
- console.error(`${color6.red("✗")} ${label}: ${result.body.error ?? `HTTP ${result.status}`}`);
3619
+ console.error(`${color7.red("✗")} ${label}: ${result.body.error ?? `HTTP ${result.status}`}`);
3457
3620
  failed = true;
3458
3621
  }
3459
3622
  }
3460
3623
  if (failed) {
3461
3624
  console.error(`
3462
- ${color6.red("Some of it did not land.")} The import is an upsert, so fixing the cause and running it again is safe.
3625
+ ${color7.red("Some of it did not land.")} The import is an upsert, so fixing the cause and running it again is safe.
3463
3626
  `);
3464
3627
  return 1;
3465
3628
  }
3466
3629
  console.log(`
3467
- ${color6.green("Imported.")} It is an upsert, so running it again costs one pass and changes nothing.
3630
+ ${color7.green("Imported.")} It is an upsert, so running it again costs one pass and changes nothing.
3468
3631
  `);
3469
3632
  return 0;
3470
3633
  }
@@ -3485,4 +3648,4 @@ async function importHelp() {
3485
3648
  return 0;
3486
3649
  }
3487
3650
 
3488
- export { renderAuthTs, allEnvKeys, renderEnvTs, renderEnvExample, renderEnvLocal, renderEnvProduction, envSummary, FRONTEND_BRANCH, renderFrontendApiTs, renderFrontendEnvProduction, renderStorefrontPage, renderFrontendWorkflow, runVoid, runInherit, captureVoid, isVoidApp, voidAppsIn, STRICT_APP, VOID_VERSION, strictDependencies, renderVoidPatch, renderViteConfig, renderStrictTsconfig, renderVoidJson, renderDbSchema, renderDbSeed, renderLayoutTsx, renderAppCss, renderIndexServer, INDEX_PAGE, renderDashboardPage, CLIENT_ONLY, renderSignInPage, renderAuthClient, renderContentTs, renderCron, renderQueue, renderLiveStream, renderLiveRoute, renderStrictAppPackageJson, DATA_README, BRANDING_README, MIGRATIONS_README, finishStrict, line, row, box, fullHelp, initHelp, version, findProject, ensureGenerated, DIST_DIR, DIST_BRANCH, distCommand, distHelp, renderDistWorkflow, renderDeployReadme, renderRequirementsTs, renderDomainTs, generate, parseJsonc, upsertJsonc, findWrangler, parseWhoAmI, SECRETS_FILE, PRIVATE_KEY_VAR, secretsCommand, productionEnv, routeProblem, preflight, printPreflight, deployCloudflare, importCommand, importHelp };
3651
+ export { renderAuthTs, allEnvKeys, renderEnvTs, renderEnvExample, renderEnvLocal, renderEnvProduction, envSummary, FRONTEND_BRANCH, renderFrontendApiTs, renderFrontendEnvProduction, renderStorefrontPage, renderFrontendWorkflow, runVoid, runInherit, captureVoid, isVoidApp, voidAppsIn, STRICT_APP, VOID_VERSION, strictDependencies, renderVoidPatch, renderViteConfig, renderStrictTsconfig, renderVoidJson, renderDbSchema, renderDbSeed, renderLayoutTsx, renderAppCss, renderIndexServer, INDEX_PAGE, renderDashboardPage, CLIENT_ONLY, renderSignInPage, renderAuthClient, renderContentTs, renderCron, renderQueue, renderLiveStream, renderLiveRoute, renderStrictAppPackageJson, DATA_README, BRANDING_README, MIGRATIONS_README, finishStrict, line, row, box, fullHelp, initHelp, version, findProject, ensureGenerated, DIST_DIR, DIST_BRANCH, distCommand, distHelp, renderDistWorkflow, renderDeployReadme, renderRequirementsTs, renderDomainTs, generate, parseJsonc, upsertJsonc, findWrangler, parseWhoAmI, keysCommand, SECRETS_FILE, PRIVATE_KEY_VAR, plaintextSecretNames, secretsCommand, productionEnv, routeProblem, preflight, printPreflight, deployCloudflare, importCommand, importHelp };