@saastemly/voidcommerce 0.2.2 → 0.3.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 CHANGED
@@ -1,4 +1,6 @@
1
1
  import {
2
+ PRIVATE_KEY_VAR,
3
+ SECRETS_FILE,
2
4
  box,
3
5
  captureVoid,
4
6
  deployCloudflare,
@@ -20,9 +22,10 @@ import {
20
22
  row,
21
23
  runInherit,
22
24
  runVoid,
25
+ secretsCommand,
23
26
  version,
24
27
  voidAppsIn
25
- } from "./index-sbchkpa6.js";
28
+ } from "./index-84wq4qp8.js";
26
29
  import {
27
30
  LAYOUTS,
28
31
  readManifest,
@@ -113,6 +116,35 @@ async function preflightHelp() {
113
116
  ], width));
114
117
  return 0;
115
118
  }
119
+ async function secretsCliCommand(args) {
120
+ const project = await findProject();
121
+ if (!project) {
122
+ console.error("vc: no voidcommerce.json here.");
123
+ return 1;
124
+ }
125
+ return secretsCommand(project, args);
126
+ }
127
+ async function secretsHelp() {
128
+ const width = 80;
129
+ console.log(box("vc secrets", [
130
+ line(`Secrets that live in the repository, encrypted, in ${SECRETS_FILE}.`, width),
131
+ line("", width),
132
+ ...row("vc secrets", "what this shop needs, and whether it is declared and encrypted", width, 2),
133
+ ...row("vc secrets --init", `write ${SECRETS_FILE} with every required key as \`unset\``, width, 2),
134
+ ...row(`dotenvx set KEY '…' -f ${SECRETS_FILE}`, "set one, without decrypting the file", width, 2),
135
+ ...row(`dotenvx encrypt -f ${SECRETS_FILE}`, "encrypt anything still in the clear", width, 2),
136
+ line("", width),
137
+ line("The file is COMMITTED: values are ciphertext, key names are not, so a", width),
138
+ line("diff shows which secret changed without showing what it changed to.", width),
139
+ line(`The private key stays out — .env.keys locally, and ${PRIVATE_KEY_VAR}`, width),
140
+ line("as one build variable where the deploy runs.", width),
141
+ line("", width),
142
+ line("Tradeoff worth knowing: ciphertext in git is permanent, so a leaked key", width),
143
+ line("exposes rotated secrets too. `wrangler secret put` does not have that", width),
144
+ line("property, and stays available for anything you would rather keep out.", width)
145
+ ], width));
146
+ return 0;
147
+ }
116
148
 
117
149
  // src/init.ts
118
150
  import * as p2 from "@clack/prompts";
@@ -504,6 +536,7 @@ var EXTENDED = {
504
536
  import: { run: importCommand, help: importHelp },
505
537
  deploy: { run: deployCommand, help: deployHelp },
506
538
  preflight: { run: preflightCommand, help: preflightHelp },
539
+ secrets: { run: secretsCliCommand, help: secretsHelp },
507
540
  dev: { run: appScript("dev"), help: appScriptHelp("dev") },
508
541
  build: { run: appScript("build"), help: appScriptHelp("build") },
509
542
  preview: { run: appScript("preview"), help: appScriptHelp("preview") }
@@ -16,7 +16,9 @@ import type { Project } from "../project";
16
16
  * secret-class key and every `unset` is removed from dist/ssr/wrangler.json
17
17
  * before it is uploaded; the worker reads those from `wrangler secret`.
18
18
  * 6. apply the committed migrations to the remote D1
19
- * 7. wrangler deploy, on exactly the config that was scrubbed
19
+ * 7. decrypt the repository's own secrets, if it keeps any
20
+ * 8. wrangler deploy, on exactly the config that was scrubbed, carrying
21
+ * those secrets up with the version
20
22
  */
21
23
  export interface CloudflareOptions {
22
24
  provision: boolean;
@@ -14,3 +14,6 @@ export declare function preflightCommand(args: string[]): Promise<number>;
14
14
  /** void's deploy help, then what vc adds. */
15
15
  export declare function deployHelp(): Promise<number>;
16
16
  export declare function preflightHelp(): Promise<number>;
17
+ /** `vc secrets` — what the repository declares, and whether it is encrypted. */
18
+ export declare function secretsCliCommand(args: string[]): Promise<number>;
19
+ export declare function secretsHelp(): Promise<number>;
@@ -17,6 +17,8 @@ export interface Preflight {
17
17
  remote: Set<string> | null;
18
18
  missing: EnvKey[];
19
19
  routeProblem: string | null;
20
+ /** Names in `.env.secrets` whose value was committed unencrypted. */
21
+ bareSecrets: string[];
20
22
  ready: boolean;
21
23
  }
22
24
  export type SecretSource = "wrangler" | "void";
@@ -0,0 +1,68 @@
1
+ import type { Project } from "../project";
2
+ /**
3
+ * Secrets that live in the repository, encrypted.
4
+ *
5
+ * ── The problem this solves ──────────────────────────────────────────────
6
+ *
7
+ * A shop's secrets were set by hand, one `wrangler secret put` at a time,
8
+ * on a machine that happened to have them. Nothing in the repository said
9
+ * what the values WERE, only what they were called, so the shop could not
10
+ * be rebuilt from a checkout and a new deploy target needed somebody to
11
+ * remember. That is the last manual step between "push" and "live".
12
+ *
13
+ * dotenvx closes it. `.env.secrets` holds ciphertext and a public key, and
14
+ * it is COMMITTED: the key names are readable, the values are not, and a
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.
18
+ *
19
+ * ── Why the values still become worker SECRETS ───────────────────────────
20
+ *
21
+ * The lazy version of this would decrypt at build time and let the values
22
+ * land in the worker's `vars`. That undoes the thing we built the scrub
23
+ * step for: `vars` are plaintext in the deployed configuration and readable
24
+ * by anyone with dashboard access. So the plaintext exists only inside the
25
+ * build, in a temp file, and goes up through `wrangler deploy
26
+ * --secrets-file`, which stores them as real secrets and — importantly —
27
+ * applies ADDITIVELY, so a secret this file does not name is left alone
28
+ * rather than deleted.
29
+ *
30
+ * ── The tradeoff, stated ─────────────────────────────────────────────────
31
+ *
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.
38
+ */
39
+ export declare const SECRETS_FILE = ".env.secrets";
40
+ export declare const PRIVATE_KEY_VAR = "DOTENV_PRIVATE_KEY_SECRETS";
41
+ /** dotenvx, from the project's own install rather than a global one. */
42
+ export declare function findDotenvx(from: string): string | null;
43
+ /**
44
+ * The names a `.env.secrets` declares, read WITHOUT decrypting.
45
+ *
46
+ * dotenvx leaves keys in plaintext and encrypts only values, so preflight
47
+ * can say "all five are declared" on a machine that cannot read any of them.
48
+ */
49
+ export declare function declaredSecretNames(root: string): Set<string>;
50
+ /** Is a declared value actually encrypted, or was it committed in the clear? */
51
+ export declare function plaintextSecretNames(root: string): string[];
52
+ export interface DecryptedSecrets {
53
+ /** A temp file in `.env` format, for `wrangler deploy --secrets-file`. */
54
+ path: string;
55
+ names: string[];
56
+ cleanup: () => void;
57
+ }
58
+ /**
59
+ * Decrypt into a temp file the deploy can hand to wrangler.
60
+ *
61
+ * Written to disk rather than piped because that is what `--secrets-file`
62
+ * takes, and removed by `cleanup()` whatever happens next.
63
+ */
64
+ export declare function decryptSecrets(project: Project): Promise<DecryptedSecrets | {
65
+ error: string;
66
+ }>;
67
+ /** `vc secrets` — what the repository declares, and whether it is readable here. */
68
+ export declare function secretsCommand(project: Project, args: string[]): Promise<number>;