@saastemly/voidcommerce 0.8.0 → 0.9.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/cli.js +2 -2
- package/dist/{index-y6qzjd9h.js → index-0w66yc8y.js} +37 -4
- package/dist/{index-j5xjh6qp.js → index-wykd4zmk.js} +32 -6
- package/dist/index.js +2 -2
- package/dist/{keys-3zjezaxk.js → keys-68c6vmds.js} +1 -1
- package/package.json +1 -1
- package/src/deploy/secrets.ts +26 -4
- package/src/generate/index.ts +53 -2
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
runVoid,
|
|
22
22
|
version,
|
|
23
23
|
voidAppsIn
|
|
24
|
-
} from "./index-
|
|
24
|
+
} from "./index-0w66yc8y.js";
|
|
25
25
|
import {
|
|
26
26
|
LOCAL_KEY_FILE,
|
|
27
27
|
PRIVATE_KEY_VAR,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
setVariable,
|
|
51
51
|
variableNames,
|
|
52
52
|
verifyCloudflareToken
|
|
53
|
-
} from "./index-
|
|
53
|
+
} from "./index-wykd4zmk.js";
|
|
54
54
|
import {
|
|
55
55
|
LAYOUTS,
|
|
56
56
|
oneOrigin,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
renderEnvLocal,
|
|
10
10
|
renderEnvProduction,
|
|
11
11
|
renderEnvTs
|
|
12
|
-
} from "./index-
|
|
12
|
+
} from "./index-wykd4zmk.js";
|
|
13
13
|
import {
|
|
14
14
|
MANIFEST_FILE,
|
|
15
15
|
has,
|
|
@@ -1344,7 +1344,7 @@ import color from "picocolors";
|
|
|
1344
1344
|
// package.json
|
|
1345
1345
|
var package_default = {
|
|
1346
1346
|
name: "@saastemly/voidcommerce",
|
|
1347
|
-
version: "0.
|
|
1347
|
+
version: "0.9.0",
|
|
1348
1348
|
description: "Void, with a shop in it. `vc init` walks you through Better Auth, betterCommerce and every plugin; everything else passes through to `void`.",
|
|
1349
1349
|
type: "module",
|
|
1350
1350
|
license: "MIT",
|
|
@@ -2293,6 +2293,39 @@ async function exists(path) {
|
|
|
2293
2293
|
return false;
|
|
2294
2294
|
}
|
|
2295
2295
|
}
|
|
2296
|
+
async function putEnv(root, file, rendered, result) {
|
|
2297
|
+
const path = join4(root, file);
|
|
2298
|
+
if (!await exists(path)) {
|
|
2299
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
2300
|
+
await writeFile2(path, rendered, "utf8");
|
|
2301
|
+
result.written.push(file);
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
const read = (text) => {
|
|
2305
|
+
const out = new Map;
|
|
2306
|
+
for (const line2 of text.split(`
|
|
2307
|
+
`)) {
|
|
2308
|
+
const match = /^\s*([A-Z][A-Z0-9_]*)\s*=\s*(.*)$/.exec(line2);
|
|
2309
|
+
if (match)
|
|
2310
|
+
out.set(match[1], match[2]);
|
|
2311
|
+
}
|
|
2312
|
+
return out;
|
|
2313
|
+
};
|
|
2314
|
+
const existing = read(await readFile2(path, "utf8"));
|
|
2315
|
+
const wanted = read(rendered);
|
|
2316
|
+
const merged = rendered.split(`
|
|
2317
|
+
`).map((line2) => {
|
|
2318
|
+
const match = /^\s*([A-Z][A-Z0-9_]*)\s*=\s*(.*)$/.exec(line2);
|
|
2319
|
+
if (!match)
|
|
2320
|
+
return line2;
|
|
2321
|
+
const had = existing.get(match[1]);
|
|
2322
|
+
return had !== undefined && had !== "" && had !== "unset" ? `${match[1]}=${had}` : line2;
|
|
2323
|
+
}).join(`
|
|
2324
|
+
`);
|
|
2325
|
+
const dropped = [...existing.keys()].filter((key) => !wanted.has(key) && !key.startsWith("VITE_"));
|
|
2326
|
+
await writeFile2(path, merged, "utf8");
|
|
2327
|
+
result.written.push(dropped.length > 0 ? `${file} ${`(dropped ${dropped.join(", ")})`}` : file);
|
|
2328
|
+
}
|
|
2296
2329
|
async function put(root, file, content, result, mode) {
|
|
2297
2330
|
const path = join4(root, file);
|
|
2298
2331
|
if (mode === "own" && await exists(path)) {
|
|
@@ -2364,7 +2397,7 @@ async function generateApi(root, dir, manifest, result, opts) {
|
|
|
2364
2397
|
await put(root, at(".env.production"), renderEnvProduction(manifest), result, "regenerate");
|
|
2365
2398
|
await put(root, at("lib/deploy/requirements.ts"), renderRequirementsTs(manifest), result, "regenerate");
|
|
2366
2399
|
if (opts.env)
|
|
2367
|
-
await
|
|
2400
|
+
await putEnv(root, at(".env"), renderEnvLocal(manifest), result);
|
|
2368
2401
|
await put(root, at("lib/domain.ts"), renderDomainTs(manifest.layout), result, own);
|
|
2369
2402
|
await put(root, at("lib/payment.ts"), renderPaymentTs(manifest), result, own);
|
|
2370
2403
|
await put(root, at("lib/notifications.ts"), renderNotificationsTs(), result, own);
|
|
@@ -2517,7 +2550,7 @@ bunx vc guard
|
|
|
2517
2550
|
await put(root, ".github/workflows/deploy.yml", renderDistWorkflow(manifest), result, "regenerate");
|
|
2518
2551
|
await retire(root, ".github/workflows/void-dist.yml", result);
|
|
2519
2552
|
await put(root, "DEPLOY.md", renderDeployReadme(manifest, zone(manifest), workerHosts(manifest)), result, "regenerate");
|
|
2520
|
-
await
|
|
2553
|
+
await putEnv(root, ".env", renderEnvLocal(manifest), result);
|
|
2521
2554
|
await put(root, "data/README.md", DATA_README, result, "own");
|
|
2522
2555
|
await put(root, "branding/README.md", BRANDING_README, result, "own");
|
|
2523
2556
|
await put(root, "migrations/README.md", MIGRATIONS_README, result, "own");
|
|
@@ -384,7 +384,7 @@ async function decryptSecrets(project) {
|
|
|
384
384
|
}
|
|
385
385
|
const expected = committedPublicKey(root);
|
|
386
386
|
if (expected) {
|
|
387
|
-
const { publicKeyFor } = await import("./keys-
|
|
387
|
+
const { publicKeyFor } = await import("./keys-68c6vmds.js");
|
|
388
388
|
const derived = await publicKeyFor(privateKey);
|
|
389
389
|
if (derived && derived.toLowerCase() !== expected.toLowerCase()) {
|
|
390
390
|
return {
|
|
@@ -432,7 +432,7 @@ async function secretsCommand(project, args) {
|
|
|
432
432
|
if (args[0] === "set")
|
|
433
433
|
return setSecretValue(project, args.slice(1));
|
|
434
434
|
if (args.includes("--sync"))
|
|
435
|
-
return syncSecrets(project);
|
|
435
|
+
return syncSecrets(project, args.includes("--prune"));
|
|
436
436
|
if (!existsSync2(join2(root, SECRETS_FILE))) {
|
|
437
437
|
console.log(`
|
|
438
438
|
${color.yellow("No " + SECRETS_FILE + " yet.")} Secrets are set by hand with \`wrangler secret put\`,
|
|
@@ -625,7 +625,7 @@ function upsertLine(path, name, value) {
|
|
|
625
625
|
`)}${name}=${value}
|
|
626
626
|
`);
|
|
627
627
|
}
|
|
628
|
-
async function syncSecrets(project) {
|
|
628
|
+
async function syncSecrets(project, prune) {
|
|
629
629
|
const root = project.root;
|
|
630
630
|
const path = join2(root, SECRETS_FILE);
|
|
631
631
|
if (!existsSync2(path)) {
|
|
@@ -656,13 +656,39 @@ ${color.green("+")} ${missing.length} key${missing.length === 1 ? "" : "s"} adde
|
|
|
656
656
|
${color.green("✓")} every secret this shop needs is already declared.
|
|
657
657
|
`);
|
|
658
658
|
}
|
|
659
|
-
if (extra.length > 0) {
|
|
659
|
+
if (extra.length > 0 && prune) {
|
|
660
|
+
const body = readFileSync(path, "utf8");
|
|
661
|
+
const kept = [];
|
|
662
|
+
const lines = body.split(`
|
|
663
|
+
`);
|
|
664
|
+
for (let i = 0;i < lines.length; i++) {
|
|
665
|
+
const match = /^\s*([A-Z][A-Z0-9_]*)\s*=/.exec(lines[i] ?? "");
|
|
666
|
+
if (match && extra.includes(match[1])) {
|
|
667
|
+
while (kept.length > 0 && (kept[kept.length - 1].startsWith("#") || kept[kept.length - 1].trim() === ""))
|
|
668
|
+
kept.pop();
|
|
669
|
+
if (lines[i + 1]?.trim() === "")
|
|
670
|
+
i++;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
kept.push(lines[i]);
|
|
674
|
+
}
|
|
675
|
+
writeFileSync(path, `${kept.join(`
|
|
676
|
+
`).replace(/\n{3,}/g, `
|
|
677
|
+
|
|
678
|
+
`).replace(/\n*$/, "")}
|
|
679
|
+
`);
|
|
680
|
+
console.log(`${color.red("-")} ${extra.length} key${extra.length === 1 ? "" : "s"} removed from ${SECRETS_FILE}: ${extra.join(", ")}
|
|
681
|
+
|
|
682
|
+
` + color.dim(` Their ciphertext is still in git history, so nothing is unrecoverable —
|
|
683
|
+
but nothing in this shop reads them any more.
|
|
684
|
+
`));
|
|
685
|
+
} else if (extra.length > 0) {
|
|
660
686
|
console.log(`${color.yellow("!")} ${extra.length} declared key${extra.length === 1 ? " is" : "s are"} no longer required by this shop:
|
|
661
687
|
${extra.join(`
|
|
662
688
|
`)}
|
|
663
689
|
|
|
664
|
-
` + color.dim(` Left alone
|
|
665
|
-
|
|
690
|
+
` + color.dim(` Left alone: deleting a value you may still want is not this command's
|
|
691
|
+
call to make by default. \`vc secrets --sync --prune\` makes it.
|
|
666
692
|
`));
|
|
667
693
|
}
|
|
668
694
|
return 0;
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
routeProblem,
|
|
52
52
|
strictDependencies,
|
|
53
53
|
upsertJsonc
|
|
54
|
-
} from "./index-
|
|
54
|
+
} from "./index-0w66yc8y.js";
|
|
55
55
|
import {
|
|
56
56
|
allEnvKeys,
|
|
57
57
|
envSummary,
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
renderEnvLocal,
|
|
60
60
|
renderEnvProduction,
|
|
61
61
|
renderEnvTs
|
|
62
|
-
} from "./index-
|
|
62
|
+
} from "./index-wykd4zmk.js";
|
|
63
63
|
import {
|
|
64
64
|
LAYOUTS,
|
|
65
65
|
MANIFEST_FILE,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saastemly/voidcommerce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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/deploy/secrets.ts
CHANGED
|
@@ -250,7 +250,7 @@ export async function secretsCommand(project: Project, args: string[]): Promise<
|
|
|
250
250
|
|
|
251
251
|
if (args.includes("--init")) return initSecrets(project);
|
|
252
252
|
if (args[0] === "set") return setSecretValue(project, args.slice(1));
|
|
253
|
-
if (args.includes("--sync")) return syncSecrets(project);
|
|
253
|
+
if (args.includes("--sync")) return syncSecrets(project, args.includes("--prune"));
|
|
254
254
|
|
|
255
255
|
if (!existsSync(join(root, SECRETS_FILE))) {
|
|
256
256
|
console.log(
|
|
@@ -482,7 +482,7 @@ function upsertLine(path: string, name: string, value: string): void {
|
|
|
482
482
|
* claims — the value may still be wanted, and it is not this command's to
|
|
483
483
|
* destroy. Extras are reported instead.
|
|
484
484
|
*/
|
|
485
|
-
async function syncSecrets(project: Project): Promise<number> {
|
|
485
|
+
async function syncSecrets(project: Project, prune: boolean): Promise<number> {
|
|
486
486
|
const root = project.root;
|
|
487
487
|
const path = join(root, SECRETS_FILE);
|
|
488
488
|
if (!existsSync(path)) {
|
|
@@ -505,11 +505,33 @@ async function syncSecrets(project: Project): Promise<number> {
|
|
|
505
505
|
console.log(`\n${color.green("✓")} every secret this shop needs is already declared.\n`);
|
|
506
506
|
}
|
|
507
507
|
|
|
508
|
-
if (extra.length > 0) {
|
|
508
|
+
if (extra.length > 0 && prune) {
|
|
509
|
+
// Deleting a value is the operator's call, and --prune is them making
|
|
510
|
+
// it. The whole entry goes — its comment line too — so the file reads
|
|
511
|
+
// as though the key was never required.
|
|
512
|
+
const body = readFileSync(path, "utf8");
|
|
513
|
+
const kept: string[] = [];
|
|
514
|
+
const lines = body.split("\n");
|
|
515
|
+
for (let i = 0; i < lines.length; i++) {
|
|
516
|
+
const match = /^\s*([A-Z][A-Z0-9_]*)\s*=/.exec(lines[i] ?? "");
|
|
517
|
+
if (match && extra.includes(match[1]!)) {
|
|
518
|
+
// Drop the comment that introduces it, and the blank line after.
|
|
519
|
+
while (kept.length > 0 && (kept[kept.length - 1]!.startsWith("#") || kept[kept.length - 1]!.trim() === "")) kept.pop();
|
|
520
|
+
if (lines[i + 1]?.trim() === "") i++;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
kept.push(lines[i]!);
|
|
524
|
+
}
|
|
525
|
+
writeFileSync(path, `${kept.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n*$/, "")}\n`);
|
|
526
|
+
console.log(
|
|
527
|
+
`${color.red("-")} ${extra.length} key${extra.length === 1 ? "" : "s"} removed from ${SECRETS_FILE}: ${extra.join(", ")}\n\n` +
|
|
528
|
+
color.dim(` Their ciphertext is still in git history, so nothing is unrecoverable —\n but nothing in this shop reads them any more.\n`),
|
|
529
|
+
);
|
|
530
|
+
} else if (extra.length > 0) {
|
|
509
531
|
console.log(
|
|
510
532
|
`${color.yellow("!")} ${extra.length} declared key${extra.length === 1 ? " is" : "s are"} no longer required by this shop:\n` +
|
|
511
533
|
` ${extra.join("\n ")}\n\n` +
|
|
512
|
-
color.dim(
|
|
534
|
+
color.dim(` Left alone: deleting a value you may still want is not this command's\n call to make by default. \`vc secrets --sync --prune\` makes it.\n`),
|
|
513
535
|
);
|
|
514
536
|
}
|
|
515
537
|
return 0;
|
package/src/generate/index.ts
CHANGED
|
@@ -89,6 +89,57 @@ async function exists(path: string): Promise<boolean> {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Write a dotenv file that RECONCILES rather than freezes.
|
|
94
|
+
*
|
|
95
|
+
* `.env` used to be written once and never touched, so a shop that swapped a
|
|
96
|
+
* carrier or a payment rail kept the old keys forever and never gained the
|
|
97
|
+
* new ones. devprints ended up carrying five Adyen keys and a Resend key for
|
|
98
|
+
* a shop that uses neither, while missing two it needed. That is the same
|
|
99
|
+
* owned-file drift that shipped a `.gitignore` without `.env.keys` and an
|
|
100
|
+
* api.ts pointing at another shop's domain.
|
|
101
|
+
*
|
|
102
|
+
* So: the template decides which keys EXIST, and the file on disk decides
|
|
103
|
+
* what they are SET TO. A value a person put there is always kept; a key the
|
|
104
|
+
* manifest no longer declares goes, and is reported rather than vanishing.
|
|
105
|
+
*/
|
|
106
|
+
async function putEnv(root: string, file: string, rendered: string, result: GenerateResult) {
|
|
107
|
+
const path = join(root, file);
|
|
108
|
+
if (!(await exists(path))) {
|
|
109
|
+
await mkdir(dirname(path), { recursive: true });
|
|
110
|
+
await writeFile(path, rendered, "utf8");
|
|
111
|
+
result.written.push(file);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const read = (text: string): Map<string, string> => {
|
|
116
|
+
const out = new Map<string, string>();
|
|
117
|
+
for (const line of text.split("\n")) {
|
|
118
|
+
const match = /^\s*([A-Z][A-Z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
119
|
+
if (match) out.set(match[1]!, match[2]!);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
};
|
|
123
|
+
const existing = read(await readFile(path, "utf8"));
|
|
124
|
+
const wanted = read(rendered);
|
|
125
|
+
|
|
126
|
+
const merged = rendered
|
|
127
|
+
.split("\n")
|
|
128
|
+
.map((line) => {
|
|
129
|
+
const match = /^\s*([A-Z][A-Z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
130
|
+
if (!match) return line;
|
|
131
|
+
const had = existing.get(match[1]!);
|
|
132
|
+
// A real value the operator set beats the template's placeholder.
|
|
133
|
+
// `unset` and empty are placeholders, not answers.
|
|
134
|
+
return had !== undefined && had !== "" && had !== "unset" ? `${match[1]}=${had}` : line;
|
|
135
|
+
})
|
|
136
|
+
.join("\n");
|
|
137
|
+
|
|
138
|
+
const dropped = [...existing.keys()].filter((key) => !wanted.has(key) && !key.startsWith("VITE_"));
|
|
139
|
+
await writeFile(path, merged, "utf8");
|
|
140
|
+
result.written.push(dropped.length > 0 ? `${file} ${`(dropped ${dropped.join(", ")})`}` : file);
|
|
141
|
+
}
|
|
142
|
+
|
|
92
143
|
async function put(root: string, file: string, content: string, result: GenerateResult, mode: "regenerate" | "own") {
|
|
93
144
|
const path = join(root, file);
|
|
94
145
|
if (mode === "own" && (await exists(path))) {
|
|
@@ -182,7 +233,7 @@ async function generateApi(root: string, dir: string, manifest: Manifest, result
|
|
|
182
233
|
await put(root, at(".env.production"), renderEnvProduction(manifest), result, "regenerate");
|
|
183
234
|
await put(root, at("lib/deploy/requirements.ts"), renderRequirementsTs(manifest), result, "regenerate");
|
|
184
235
|
|
|
185
|
-
if (opts.env) await
|
|
236
|
+
if (opts.env) await putEnv(root, at(".env"), renderEnvLocal(manifest), result);
|
|
186
237
|
await put(root, at("lib/domain.ts"), renderDomainTs(manifest.layout), result, own);
|
|
187
238
|
await put(root, at("lib/payment.ts"), renderPaymentTs(manifest), result, own);
|
|
188
239
|
await put(root, at("lib/notifications.ts"), renderNotificationsTs(), result, own);
|
|
@@ -395,7 +446,7 @@ bunx vc guard
|
|
|
395
446
|
// build. Two workflows on the same push would deploy the shop twice.
|
|
396
447
|
await retire(root, ".github/workflows/void-dist.yml", result);
|
|
397
448
|
await put(root, "DEPLOY.md", renderDeployReadme(manifest, zone(manifest), workerHosts(manifest)), result, "regenerate");
|
|
398
|
-
await
|
|
449
|
+
await putEnv(root, ".env", renderEnvLocal(manifest), result);
|
|
399
450
|
await put(root, "data/README.md", DATA_README, result, "own");
|
|
400
451
|
await put(root, "branding/README.md", BRANDING_README, result, "own");
|
|
401
452
|
await put(root, "migrations/README.md", MIGRATIONS_README, result, "own");
|