@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.
package/dist/catalog.js CHANGED
@@ -14,7 +14,8 @@ import {
14
14
  SIGN_IN,
15
15
  TAX,
16
16
  UI
17
- } from "./index-z4qazajn.js";
17
+ } from "./index-844b3qn9.js";
18
+ import"./index-0v6na3yp.js";
18
19
  export {
19
20
  UI,
20
21
  TAX,
package/dist/cli.js CHANGED
@@ -16,7 +16,9 @@ import {
16
16
  importHelp,
17
17
  initHelp,
18
18
  isVoidApp,
19
+ keysCommand,
19
20
  line,
21
+ plaintextSecretNames,
20
22
  preflight,
21
23
  printPreflight,
22
24
  row,
@@ -25,16 +27,17 @@ import {
25
27
  secretsCommand,
26
28
  version,
27
29
  voidAppsIn
28
- } from "./index-84wq4qp8.js";
30
+ } from "./index-yzezvy5h.js";
29
31
  import {
30
32
  LAYOUTS,
31
33
  readManifest,
32
34
  validate,
33
35
  zoneOf
34
- } from "./index-zh3tmj08.js";
36
+ } from "./index-pz6m2hkm.js";
35
37
  import {
36
38
  GROUPS
37
- } from "./index-z4qazajn.js";
39
+ } from "./index-844b3qn9.js";
40
+ import"./index-0v6na3yp.js";
38
41
 
39
42
  // src/deploy/index.ts
40
43
  import color from "picocolors";
@@ -145,6 +148,52 @@ async function secretsHelp() {
145
148
  ], width));
146
149
  return 0;
147
150
  }
151
+ async function keysCliCommand(args) {
152
+ const project = await findProject();
153
+ if (!project) {
154
+ console.error("vc: no voidcommerce.json here.");
155
+ return 1;
156
+ }
157
+ return keysCommand(project.manifest, project.root, args);
158
+ }
159
+ async function keysHelp() {
160
+ const width = 80;
161
+ console.log(box("vc keys", [
162
+ line("The key that encrypts this repository's secrets is DERIVED from", width),
163
+ line("CLOUDFLARE_API_TOKEN, salted with the account id. Nothing is stored.", width),
164
+ line("", width),
165
+ ...row("vc keys", "what this token derives, and whether it matches the repository", width, 2),
166
+ ...row("vc keys --rotate", "re-encrypt under a new token, while the old one still works", width, 2),
167
+ line("", width),
168
+ line("Whoever can deploy the worker can read its secrets, and nobody else can.", width),
169
+ line("The price: rotating the token orphans every encrypted value, a second", width),
170
+ line("admin's token derives a different key, and the build must use the same", width),
171
+ line("token the values were encrypted under. `vc keys` checks the derived", width),
172
+ line("public key against the committed one, so a mismatch is a clear refusal", width),
173
+ line("rather than a decryption error nobody can place.", width)
174
+ ], width));
175
+ return 0;
176
+ }
177
+ async function guardCommand() {
178
+ const project = await findProject();
179
+ if (!project)
180
+ return 0;
181
+ const bare = plaintextSecretNames(project.root);
182
+ if (bare.length === 0)
183
+ return 0;
184
+ console.error(`
185
+ ${color.red("✗ refusing the commit")}: ${bare.length} value${bare.length === 1 ? "" : "s"} in ${SECRETS_FILE} ${bare.length === 1 ? "is" : "are"} not encrypted.
186
+
187
+ ` + ` ${bare.join(`
188
+ `)}
189
+
190
+ ` + ` bunx dotenvx encrypt -f ${SECRETS_FILE}
191
+
192
+ ` + color.dim(` Committing a secret in the clear cannot be undone by a later commit;
193
+ the value stays in the history and must be treated as burned.
194
+ `));
195
+ return 1;
196
+ }
148
197
 
149
198
  // src/init.ts
150
199
  import * as p2 from "@clack/prompts";
@@ -537,6 +586,8 @@ var EXTENDED = {
537
586
  deploy: { run: deployCommand, help: deployHelp },
538
587
  preflight: { run: preflightCommand, help: preflightHelp },
539
588
  secrets: { run: secretsCliCommand, help: secretsHelp },
589
+ keys: { run: keysCliCommand, help: keysHelp },
590
+ guard: { run: guardCommand, help: async () => guardCommand() },
540
591
  dev: { run: appScript("dev"), help: appScriptHelp("dev") },
541
592
  build: { run: appScript("build"), help: appScriptHelp("build") },
542
593
  preview: { run: appScript("preview"), help: appScriptHelp("preview") }
@@ -17,3 +17,12 @@ export declare function preflightHelp(): Promise<number>;
17
17
  /** `vc secrets` — what the repository declares, and whether it is encrypted. */
18
18
  export declare function secretsCliCommand(args: string[]): Promise<number>;
19
19
  export declare function secretsHelp(): Promise<number>;
20
+ /** `vc keys` — what the token derives, and the warning that goes with it. */
21
+ export declare function keysCliCommand(args: string[]): Promise<number>;
22
+ export declare function keysHelp(): Promise<number>;
23
+ /**
24
+ * The pre-commit gate: refuse a commit that would put a secret in the clear.
25
+ *
26
+ * Exit code is the whole interface — a hook cares about nothing else.
27
+ */
28
+ export declare function guardCommand(): Promise<number>;
@@ -0,0 +1,34 @@
1
+ import type { Manifest } from "../manifest";
2
+ /** Where the token comes from, in the order wrangler itself reads them. */
3
+ export declare function apiToken(): string | null;
4
+ /**
5
+ * The private key this token and account derive.
6
+ *
7
+ * The account id is the salt rather than a constant, so the same token used
8
+ * against two accounts does not produce one key — tokens are often scoped to
9
+ * several accounts, and a shared key across them would be a wider blast
10
+ * radius than anyone asked for.
11
+ */
12
+ export declare function derivePrivateKey(token: string, accountId: string): string;
13
+ /** The public key for a private one, via dotenvx's own primitive. */
14
+ export declare function publicKeyFor(privateKey: string): Promise<string | null>;
15
+ /** The public key the repository was actually encrypted under. */
16
+ export declare function committedPublicKey(root: string): string | null;
17
+ export type KeyState = {
18
+ ok: true;
19
+ privateKey: string;
20
+ publicKey: string;
21
+ fresh: boolean;
22
+ } | {
23
+ ok: false;
24
+ reason: string;
25
+ };
26
+ /**
27
+ * The key for this repository, or a refusal that says exactly what is wrong.
28
+ *
29
+ * `fresh` means the repository has no secrets yet, so there is nothing to
30
+ * check the derivation against and any token is as good as another.
31
+ */
32
+ export declare function keyFor(manifest: Manifest, root: string): Promise<KeyState>;
33
+ /** `vc keys` — what the derivation says, without printing anything secret. */
34
+ export declare function keysCommand(manifest: Manifest, root: string, args: string[]): Promise<number>;
@@ -13,8 +13,9 @@ import type { Project } from "../project";
13
13
  * dotenvx closes it. `.env.secrets` holds ciphertext and a public key, and
14
14
  * it is COMMITTED: the key names are readable, the values are not, and a
15
15
  * diff shows when a secret changed without showing what it changed to. The
16
- * private key is the one thing that stays out a single value, set once
17
- * where the deploy runs.
16
+ * private key is not stored at all it is derived from the Cloudflare API
17
+ * token, so whoever can deploy the worker can read its secrets and nobody
18
+ * else can. See `./keys.ts`, which also spells out what that costs.
18
19
  *
19
20
  * ── Why the values still become worker SECRETS ───────────────────────────
20
21
  *
@@ -27,14 +28,19 @@ import type { Project } from "../project";
27
28
  * applies ADDITIVELY, so a secret this file does not name is left alone
28
29
  * rather than deleted.
29
30
  *
30
- * ── The tradeoff, stated ─────────────────────────────────────────────────
31
+ * ── The tradeoffs, stated ────────────────────────────────────────────────
31
32
  *
32
- * Ciphertext in git is permanent. If the private key ever leaks, every
33
- * secret in the history is readable, including ones that were rotated —
34
- * which is not true of `wrangler secret put`, where a rotation genuinely
35
- * retires the old value. Rotating the dotenvx key re-encrypts the present,
36
- * not the past. That is the price of a shop that rebuilds from a checkout,
37
- * and it should be a decision rather than a surprise.
33
+ * Ciphertext in git is permanent. A key that ever leaks reads every secret
34
+ * in the history, including ones that were rotated — which is not true of
35
+ * `wrangler secret put`, where a rotation genuinely retires the old value.
36
+ *
37
+ * And because the key is derived, rotating the API token orphans the lot.
38
+ * `keys.ts` guards that with a public-key check, so the failure is a clear
39
+ * refusal rather than a decryption error nobody can place — but the guard
40
+ * cannot recover values, only explain them.
41
+ *
42
+ * Both are the price of a shop that rebuilds from a checkout, and both
43
+ * should be decisions rather than surprises.
38
44
  */
39
45
  export declare const SECRETS_FILE = ".env.secrets";
40
46
  export declare const PRIVATE_KEY_VAR = "DOTENV_PRIVATE_KEY_SECRETS";
@@ -0,0 +1,35 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
34
+
35
+ export { __toESM, __commonJS, __require };
@@ -1,6 +1,3 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
1
  // src/catalog.ts
5
2
  var SIGN_IN = {
6
3
  id: "signIn",
@@ -586,4 +583,4 @@ var GROUPS = [
586
583
  ];
587
584
  var CHOICES = new Map(GROUPS.flatMap((group) => group.choices.map((choice) => [choice.id, choice])));
588
585
 
589
- export { __require, SIGN_IN, AUTH_PLUGINS, COMMERCE_CATALOG, COMMERCE_SALES, COMMERCE_OPERATIONS, PAYMENT, TAX, CARRIERS, ERP, EMAIL, DNS, CONTENT, UI, GROUPS, CHOICES };
586
+ export { SIGN_IN, AUTH_PLUGINS, COMMERCE_CATALOG, COMMERCE_SALES, COMMERCE_OPERATIONS, PAYMENT, TAX, CARRIERS, ERP, EMAIL, DNS, CONTENT, UI, GROUPS, CHOICES };