@saastemly/voidcommerce 0.21.0 → 0.22.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.
package/dist/catalog.d.ts CHANGED
@@ -24,6 +24,19 @@ export interface EnvKey {
24
24
  dev?: string | undefined;
25
25
  /** Where the real value comes from. */
26
26
  where?: string | undefined;
27
+ /**
28
+ * Needed to DEPLOY the shop, not to run it.
29
+ *
30
+ * The Cloudflare credentials are the case. They belong in `.env.secrets`
31
+ * so a push carries them, and preflight must insist on them — but the
32
+ * worker has no business seeing one. Left unmarked they ended up in three
33
+ * wrong places at once: `env.ts`, so the worker refused to boot without a
34
+ * deploy token; `.env`, where wrangler read `CLOUDFLARE_ACCOUNT_ID=unset`
35
+ * and addressed `accounts/unset/...`; and `--secrets-file`, which would
36
+ * have uploaded the deploy credential INTO the worker for any code
37
+ * running there to read.
38
+ */
39
+ deployOnly?: boolean | undefined;
27
40
  }
28
41
  export interface Choice {
29
42
  id: string;
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  version,
26
26
  voidAppsIn,
27
27
  wrangler
28
- } from "./index-pghh8nnq.js";
28
+ } from "./index-2kqz80nx.js";
29
29
  import {
30
30
  LOCAL_KEY_FILE,
31
31
  PRIVATE_KEY_VAR,
@@ -40,7 +40,7 @@ import {
40
40
  run,
41
41
  secretValue,
42
42
  secretsCommand
43
- } from "./index-940vpkwa.js";
43
+ } from "./index-t1ggktg8.js";
44
44
  import {
45
45
  LAYOUTS,
46
46
  oneOrigin,
@@ -13,7 +13,7 @@ import {
13
13
  renderEnvTs,
14
14
  secretValue,
15
15
  unsetSecretNames
16
- } from "./index-940vpkwa.js";
16
+ } from "./index-t1ggktg8.js";
17
17
  import {
18
18
  MANIFEST_FILE,
19
19
  has,
@@ -1383,7 +1383,7 @@ import color from "picocolors";
1383
1383
  // package.json
1384
1384
  var package_default = {
1385
1385
  name: "@saastemly/voidcommerce",
1386
- version: "0.21.0",
1386
+ version: "0.22.0",
1387
1387
  description: "Void, with a shop in it. `vc init` walks you through Better Auth, betterCommerce and every plugin; everything else passes through to `void`.",
1388
1388
  type: "module",
1389
1389
  license: "MIT",
@@ -118,12 +118,14 @@ var BASE = [
118
118
  {
119
119
  key: "CLOUDFLARE_API_TOKEN",
120
120
  breaks: "nothing deploys: no worker, no database, no migrations",
121
- where: "dash.cloudflare.com/profile/api-tokens → Create Token → Custom token"
121
+ where: "dash.cloudflare.com/profile/api-tokens → Create Token → Custom token",
122
+ deployOnly: true
122
123
  },
123
124
  {
124
125
  key: "CLOUDFLARE_ACCOUNT_ID",
125
126
  breaks: "wrangler cannot tell which account to deploy into, and refuses rather than guessing",
126
- where: "the Cloudflare dashboard sidebar, or `vc secrets set` it after the first provision"
127
+ where: "the Cloudflare dashboard sidebar, or read from the token on first deploy",
128
+ deployOnly: true
127
129
  },
128
130
  {
129
131
  key: "SHOP_DOMAIN",
@@ -174,7 +176,7 @@ function allEnvKeys(manifest) {
174
176
  }
175
177
  var NUMERIC = new Set(["SHIPPING_DOMESTIC_MINOR", "SHIPPING_FREE_FROM_MINOR"]);
176
178
  function renderEnvTs(manifest) {
177
- const keys = allEnvKeys(manifest);
179
+ const keys = allEnvKeys(manifest).filter((key) => !key.deployOnly);
178
180
  const lines = keys.map((key) => {
179
181
  const helper = NUMERIC.has(key.key) ? "number()" : "string()";
180
182
  const doc = [` /** ${key.breaks}${key.where ? ` — from: ${key.where}` : ""} */`];
@@ -203,7 +205,7 @@ ${lines.join(`
203
205
  `;
204
206
  }
205
207
  function renderEnvExample(manifest) {
206
- const keys = allEnvKeys(manifest);
208
+ const keys = allEnvKeys(manifest).filter((key) => !key.deployOnly);
207
209
  return [
208
210
  "# Every key is required. Copy to .env for local development.",
209
211
  "# `unset` is the one value that reads as absent — for a credential you do not have yet.",
@@ -215,7 +217,7 @@ function renderEnvExample(manifest) {
215
217
  }
216
218
  var MANIFEST_OWNED_ENV = new Set(["SHOP_DOMAIN", "SHOP_ZONE", "EMAIL_FROM", "GITHUB_PAGES_HOST"]);
217
219
  function renderEnvLocal(manifest) {
218
- const keys = allEnvKeys(manifest);
220
+ const keys = allEnvKeys(manifest).filter((key) => !key.deployOnly);
219
221
  const local = {
220
222
  SHOP_DOMAIN: `${manifest.shop.domain.split(".")[0]}.test`,
221
223
  GITHUB_PAGES_HOST: manifest.shop.pagesHost ?? "",
@@ -370,7 +372,7 @@ async function decryptSecrets(project) {
370
372
  const root = project.root;
371
373
  if (!existsSync2(join2(root, SECRETS_FILE)))
372
374
  return { error: `${SECRETS_FILE} does not exist — \`vc secrets init\` writes one` };
373
- const { localPrivateKey } = await import("./keys-mewxnkwg.js");
375
+ const { localPrivateKey } = await import("./keys-1h4zpvd7.js");
374
376
  const privateKey = process.env[PRIVATE_KEY_VAR] || localPrivateKey(root) || "";
375
377
  if (!privateKey) {
376
378
  return {
@@ -382,7 +384,7 @@ async function decryptSecrets(project) {
382
384
  }
383
385
  const expected = committedPublicKey(root);
384
386
  if (expected) {
385
- const { publicKeyFor } = await import("./keys-mewxnkwg.js");
387
+ const { publicKeyFor } = await import("./keys-1h4zpvd7.js");
386
388
  const derived = await publicKeyFor(privateKey);
387
389
  if (derived && derived.toLowerCase() !== expected.toLowerCase()) {
388
390
  return {
@@ -402,8 +404,13 @@ async function decryptSecrets(project) {
402
404
  if (result.code !== 0)
403
405
  return { error: `dotenvx could not decrypt ${SECRETS_FILE}: ${result.out.trim().split(`
404
406
  `).slice(-2).join(" ")}` };
407
+ const deployOnly = new Set(allEnvKeys(project.manifest).filter((key) => key.deployOnly).map((key) => key.key));
405
408
  const lines = result.out.split(`
406
- `).filter((line) => /^\s*[A-Z][A-Z0-9_]*\s*=/.test(line) && !line.trimStart().startsWith("DOTENV_"));
409
+ `).filter((line) => {
410
+ if (!/^\s*[A-Z][A-Z0-9_]*\s*=/.test(line) || line.trimStart().startsWith("DOTENV_"))
411
+ return false;
412
+ return !deployOnly.has(line.slice(0, line.indexOf("=")).trim());
413
+ });
407
414
  const names = lines.map((line) => line.slice(0, line.indexOf("=")).trim());
408
415
  const path = join2(tmpdir(), `vc-secrets-${process.pid}-${Date.now()}.env`);
409
416
  writeFileSync(path, `${lines.join(`
@@ -725,7 +732,7 @@ vc: no ${SECRETS_FILE} here — \`vc secrets --init\` starts a fresh one.
725
732
  `);
726
733
  return 1;
727
734
  }
728
- const { localPrivateKey, publicKeyFor, generateKeypair } = await import("./keys-mewxnkwg.js");
735
+ const { localPrivateKey, publicKeyFor, generateKeypair } = await import("./keys-1h4zpvd7.js");
729
736
  const current = committedPublicKey(root);
730
737
  const mine = process.env[PRIVATE_KEY_VAR] || localPrivateKey(root);
731
738
  if (mine && current && (await publicKeyFor(mine))?.toLowerCase() === current.toLowerCase()) {
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ import {
52
52
  routeProblem,
53
53
  strictDependencies,
54
54
  upsertJsonc
55
- } from "./index-pghh8nnq.js";
55
+ } from "./index-2kqz80nx.js";
56
56
  import {
57
57
  allEnvKeys,
58
58
  envSummary,
@@ -60,7 +60,7 @@ import {
60
60
  renderEnvLocal,
61
61
  renderEnvProduction,
62
62
  renderEnvTs
63
- } from "./index-940vpkwa.js";
63
+ } from "./index-t1ggktg8.js";
64
64
  import {
65
65
  LAYOUTS,
66
66
  MANIFEST_FILE,
@@ -8,7 +8,7 @@ import {
8
8
  localPrivateKey,
9
9
  provisionKey,
10
10
  publicKeyFor
11
- } from "./index-940vpkwa.js";
11
+ } from "./index-t1ggktg8.js";
12
12
  import"./index-30y19qz5.js";
13
13
  import"./index-xyjhy6kp.js";
14
14
  import"./index-0v6na3yp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saastemly/voidcommerce",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Void, with a shop in it. `vc init` walks you through Better Auth, betterCommerce and every plugin; everything else passes through to `void`.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/catalog.ts CHANGED
@@ -25,6 +25,19 @@ export interface EnvKey {
25
25
  dev?: string | undefined;
26
26
  /** Where the real value comes from. */
27
27
  where?: string | undefined;
28
+ /**
29
+ * Needed to DEPLOY the shop, not to run it.
30
+ *
31
+ * The Cloudflare credentials are the case. They belong in `.env.secrets`
32
+ * so a push carries them, and preflight must insist on them — but the
33
+ * worker has no business seeing one. Left unmarked they ended up in three
34
+ * wrong places at once: `env.ts`, so the worker refused to boot without a
35
+ * deploy token; `.env`, where wrangler read `CLOUDFLARE_ACCOUNT_ID=unset`
36
+ * and addressed `accounts/unset/...`; and `--secrets-file`, which would
37
+ * have uploaded the deploy credential INTO the worker for any code
38
+ * running there to read.
39
+ */
40
+ deployOnly?: boolean | undefined;
28
41
  }
29
42
 
30
43
  export interface Choice {
@@ -242,11 +242,21 @@ export async function decryptSecrets(project: Project): Promise<DecryptedSecrets
242
242
  });
243
243
  if (result.code !== 0) return { error: `dotenvx could not decrypt ${SECRETS_FILE}: ${result.out.trim().split("\n").slice(-2).join(" ")}` };
244
244
 
245
- // Keep only the shop's own keys: dotenvx echoes the public key back, and
246
- // it has no business being a worker secret.
247
- const lines = result.out
248
- .split("\n")
249
- .filter((line) => /^\s*[A-Z][A-Z0-9_]*\s*=/.test(line) && !line.trimStart().startsWith("DOTENV_"));
245
+ /**
246
+ * Keep only the secrets the WORKER needs.
247
+ *
248
+ * dotenvx echoes its own public key back, which has no business being a
249
+ * worker secret. And the deploy-only keys are the important exclusion:
250
+ * `--secrets-file` uploads what it is given, so leaving CLOUDFLARE_API_TOKEN
251
+ * in here would put the credential that can deploy, delete and re-create
252
+ * this shop INSIDE the shop, readable by any code running in the worker.
253
+ * A compromised dependency would have found it there.
254
+ */
255
+ const deployOnly = new Set(allEnvKeys(project.manifest).filter((key) => key.deployOnly).map((key) => key.key));
256
+ const lines = result.out.split("\n").filter((line) => {
257
+ if (!/^\s*[A-Z][A-Z0-9_]*\s*=/.test(line) || line.trimStart().startsWith("DOTENV_")) return false;
258
+ return !deployOnly.has(line.slice(0, line.indexOf("=")).trim());
259
+ });
250
260
  const names = lines.map((line) => line.slice(0, line.indexOf("=")).trim());
251
261
 
252
262
  const path = join(tmpdir(), `vc-secrets-${process.pid}-${Date.now()}.env`);
@@ -29,11 +29,13 @@ const BASE: EnvKey[] = [
29
29
  key: "CLOUDFLARE_API_TOKEN",
30
30
  breaks: "nothing deploys: no worker, no database, no migrations",
31
31
  where: "dash.cloudflare.com/profile/api-tokens → Create Token → Custom token",
32
+ deployOnly: true,
32
33
  },
33
34
  {
34
35
  key: "CLOUDFLARE_ACCOUNT_ID",
35
36
  breaks: "wrangler cannot tell which account to deploy into, and refuses rather than guessing",
36
- where: "the Cloudflare dashboard sidebar, or `vc secrets set` it after the first provision",
37
+ where: "the Cloudflare dashboard sidebar, or read from the token on first deploy",
38
+ deployOnly: true,
37
39
  },
38
40
  {
39
41
  key: "SHOP_DOMAIN",
@@ -90,7 +92,9 @@ export function allEnvKeys(manifest: Manifest): EnvKey[] {
90
92
  const NUMERIC = new Set(["SHIPPING_DOMESTIC_MINOR", "SHIPPING_FREE_FROM_MINOR"]);
91
93
 
92
94
  export function renderEnvTs(manifest: Manifest): string {
93
- const keys = allEnvKeys(manifest);
95
+ // The worker validates what IT needs. A deploy credential here would make
96
+ // the shop refuse to boot without a token it never uses.
97
+ const keys = allEnvKeys(manifest).filter((key) => !key.deployOnly);
94
98
  const lines = keys.map((key) => {
95
99
  const helper = NUMERIC.has(key.key) ? "number()" : "string()";
96
100
  const doc = [` /** ${key.breaks}${key.where ? ` — from: ${key.where}` : ""} */`];
@@ -117,7 +121,7 @@ ${lines.join("\n")}
117
121
  }
118
122
 
119
123
  export function renderEnvExample(manifest: Manifest): string {
120
- const keys = allEnvKeys(manifest);
124
+ const keys = allEnvKeys(manifest).filter((key) => !key.deployOnly);
121
125
  return [
122
126
  "# Every key is required. Copy to .env for local development.",
123
127
  "# `unset` is the one value that reads as absent — for a credential you do not have yet.",
@@ -139,7 +143,9 @@ export function renderEnvExample(manifest: Manifest): string {
139
143
  export const MANIFEST_OWNED_ENV = new Set(["SHOP_DOMAIN", "SHOP_ZONE", "EMAIL_FROM", "GITHUB_PAGES_HOST"]);
140
144
 
141
145
  export function renderEnvLocal(manifest: Manifest): string {
142
- const keys = allEnvKeys(manifest);
146
+ // Not in .env either: wrangler READS .env, so a deploy-only key sitting
147
+ // there as `unset` becomes a literal account id — `accounts/unset/...`.
148
+ const keys = allEnvKeys(manifest).filter((key) => !key.deployOnly);
143
149
  const local: Record<string, string> = {
144
150
  SHOP_DOMAIN: `${manifest.shop.domain.split(".")[0]}.test`,
145
151
  GITHUB_PAGES_HOST: manifest.shop.pagesHost ?? "",