@mp-lb/arcnet 0.1.0 → 0.2.1

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.
Files changed (78) hide show
  1. package/README.md +37 -4
  2. package/dist/createCli.d.ts.map +1 -1
  3. package/dist/createCli.js +14 -5
  4. package/dist/createCli.js.map +1 -1
  5. package/dist/secrets/assertNewSource.d.ts +3 -0
  6. package/dist/secrets/assertNewSource.d.ts.map +1 -0
  7. package/dist/secrets/assertNewSource.js +32 -0
  8. package/dist/secrets/assertNewSource.js.map +1 -0
  9. package/dist/secrets/config.d.ts +37 -0
  10. package/dist/secrets/config.d.ts.map +1 -0
  11. package/dist/secrets/config.js +67 -0
  12. package/dist/secrets/config.js.map +1 -0
  13. package/dist/secrets/createSecretsCommands.d.ts +3 -0
  14. package/dist/secrets/createSecretsCommands.d.ts.map +1 -0
  15. package/dist/secrets/createSecretsCommands.js +100 -0
  16. package/dist/secrets/createSecretsCommands.js.map +1 -0
  17. package/dist/secrets/loadSecrets.d.ts +3 -0
  18. package/dist/secrets/loadSecrets.d.ts.map +1 -0
  19. package/dist/secrets/loadSecrets.js +53 -0
  20. package/dist/secrets/loadSecrets.js.map +1 -0
  21. package/dist/secrets/promptPassword.d.ts +2 -0
  22. package/dist/secrets/promptPassword.d.ts.map +1 -0
  23. package/dist/secrets/promptPassword.js +42 -0
  24. package/dist/secrets/promptPassword.js.map +1 -0
  25. package/dist/secrets/provisionSource.d.ts +25 -0
  26. package/dist/secrets/provisionSource.d.ts.map +1 -0
  27. package/dist/secrets/provisionSource.js +141 -0
  28. package/dist/secrets/provisionSource.js.map +1 -0
  29. package/dist/secrets/readRemoteSource.d.ts +7 -0
  30. package/dist/secrets/readRemoteSource.d.ts.map +1 -0
  31. package/dist/secrets/readRemoteSource.js +32 -0
  32. package/dist/secrets/readRemoteSource.js.map +1 -0
  33. package/dist/secrets/runWithSecrets.d.ts +6 -0
  34. package/dist/secrets/runWithSecrets.d.ts.map +1 -0
  35. package/dist/secrets/runWithSecrets.js +59 -0
  36. package/dist/secrets/runWithSecrets.js.map +1 -0
  37. package/dist/secrets/secretCommand.d.ts +2 -0
  38. package/dist/secrets/secretCommand.d.ts.map +1 -0
  39. package/dist/secrets/secretCommand.js +17 -0
  40. package/dist/secrets/secretCommand.js.map +1 -0
  41. package/dist/secrets/selectSecrets.d.ts +3 -0
  42. package/dist/secrets/selectSecrets.d.ts.map +1 -0
  43. package/dist/secrets/selectSecrets.js +18 -0
  44. package/dist/secrets/selectSecrets.js.map +1 -0
  45. package/dist/secrets/syncSecrets.d.ts +10 -0
  46. package/dist/secrets/syncSecrets.d.ts.map +1 -0
  47. package/dist/secrets/syncSecrets.js +43 -0
  48. package/dist/secrets/syncSecrets.js.map +1 -0
  49. package/dist/secrets/wireSecretsWorkflows.d.ts +2 -0
  50. package/dist/secrets/wireSecretsWorkflows.d.ts.map +1 -0
  51. package/dist/secrets/wireSecretsWorkflows.js +48 -0
  52. package/dist/secrets/wireSecretsWorkflows.js.map +1 -0
  53. package/dist/secrets/withDeploymentSecrets.d.ts +2 -0
  54. package/dist/secrets/withDeploymentSecrets.d.ts.map +1 -0
  55. package/dist/secrets/withDeploymentSecrets.js +62 -0
  56. package/dist/secrets/withDeploymentSecrets.js.map +1 -0
  57. package/dist/secrets/writeSecretsWorkflow.d.ts +3 -0
  58. package/dist/secrets/writeSecretsWorkflow.d.ts.map +1 -0
  59. package/dist/secrets/writeSecretsWorkflow.js +60 -0
  60. package/dist/secrets/writeSecretsWorkflow.js.map +1 -0
  61. package/docs/public/commands.md +85 -0
  62. package/docs/public/llms-full.txt +122 -4
  63. package/package.json +2 -1
  64. package/src/createCli.ts +29 -8
  65. package/src/secrets/assertNewSource.ts +40 -0
  66. package/src/secrets/config.ts +91 -0
  67. package/src/secrets/createSecretsCommands.ts +206 -0
  68. package/src/secrets/loadSecrets.ts +69 -0
  69. package/src/secrets/promptPassword.ts +46 -0
  70. package/src/secrets/provisionSource.ts +238 -0
  71. package/src/secrets/readRemoteSource.ts +73 -0
  72. package/src/secrets/runWithSecrets.ts +71 -0
  73. package/src/secrets/secretCommand.ts +26 -0
  74. package/src/secrets/selectSecrets.ts +33 -0
  75. package/src/secrets/syncSecrets.ts +60 -0
  76. package/src/secrets/wireSecretsWorkflows.ts +57 -0
  77. package/src/secrets/withDeploymentSecrets.ts +83 -0
  78. package/src/secrets/writeSecretsWorkflow.ts +83 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeSecretsWorkflow.js","sourceRoot":"","sources":["../../src/secrets/writeSecretsWorkflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,MAAqB,EACrB,IAAY,EACZ,UAAkB,EAClB,EAAE;IACF,MAAM,IAAI,GAAG,cAAc,CACzB,IAAI,EACJ,gDAAgD,CACjD,CAAC;IAEF,MAAM,MAAM,GACV,6EAA6E,CAAC;IAEhF,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QACpE,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;IACJ,IAAI,UAAU,KAAK,sBAAsB;QACvC,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;IACJ,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9C,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAC5B,MAAM,CAAC,OAAO;SACX,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;QACnB,MAAM,CAAC,MAAM;QACb,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC;SACD,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,oBAAoB,GAAG,KAAK,CAAC,CAAC,CACrD,CAAC;IAEF,aAAa,CACX,IAAI,EACJ,MAAM;QACJ,SAAS,CAAC;YACR,IAAI,EAAE,yBAAyB;YAC/B,EAAE,EAAE;gBACF,iBAAiB,EAAE,EAAE;gBACrB,IAAI,EAAE;oBACJ,QAAQ,EAAE,CAAC,MAAM,CAAC;oBAClB,KAAK,EAAE;wBACL,YAAY;wBACZ,gDAAgD;wBAChD,cAAc;wBACd,gBAAgB;qBACjB;iBACF;aACF;YACD,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;YACjC,IAAI,EAAE;gBACJ,QAAQ,EAAE;oBACR,EAAE,EAAE,yBAAyB,MAAM,CAAC,UAAU,sCAAsC;oBACpF,SAAS,EAAE,eAAe;oBAC1B,iBAAiB,EAAE,EAAE;oBACrB,KAAK,EAAE;wBACL;4BACE,IAAI,EAAE,qBAAqB;4BAC3B,IAAI,EAAE,EAAE,qBAAqB,EAAE,KAAK,EAAE;yBACvC;wBACD,EAAE,IAAI,EAAE,sBAAsB,EAAE;wBAChC,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,EAAE;wBAC/D,EAAE,GAAG,EAAE,iDAAiD,EAAE;wBAC1D;4BACE,IAAI,EAAE,2CAA2C;4BACjD,GAAG,EAAE,4BAA4B;4BACjC,GAAG;yBACJ;qBACF;iBACF;aACF;SACF,CAAC,CACL,CAAC;IAEF,oBAAoB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC,CAAC"}
@@ -12,6 +12,83 @@ Generated from the CLI command tree.
12
12
  | `--jsonl` | Render structured completion events |
13
13
  | `--noninteractive` | Run without prompts (all commands are noninteractive) |
14
14
 
15
+ ## `arcnet env`
16
+
17
+ Load ordered encrypted Forum sources and select environment values.
18
+
19
+ ### `arcnet env check`
20
+
21
+ Validate decryption and mappings; report key names only.
22
+
23
+ | Option | Description |
24
+ | --- | --- |
25
+ | `--config <file>` | Secrets source configuration |
26
+ | `--consumer <name>` | Named mapping in the secrets configuration |
27
+ | `--live` | Fetch current ciphertext through the installed fm CLI instead of snapshots |
28
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
29
+
30
+ ### `arcnet env export`
31
+
32
+ Write selected plaintext JSON to stdout for an explicit pipeline.
33
+
34
+ | Option | Description |
35
+ | --- | --- |
36
+ | `--config <file>` | Secrets source configuration |
37
+ | `--consumer <name>` | Named mapping in the secrets configuration |
38
+ | `--live` | Fetch current ciphertext through the installed fm CLI instead of snapshots |
39
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
40
+
41
+ ### `arcnet env run <command...>`
42
+
43
+ Run a command with selected keys only; withhold child output.
44
+
45
+ | Option | Description |
46
+ | --- | --- |
47
+ | `--config <file>` | Secrets source configuration |
48
+ | `--consumer <name>` | Named mapping in the secrets configuration |
49
+ | `--live` | Fetch current ciphertext through the installed fm CLI instead of snapshots |
50
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
51
+
52
+ ## `arcnet secrets`
53
+
54
+ Attach Forum files, provision GitHub keys and refresh encrypted snapshots.
55
+
56
+ ### `arcnet secrets add`
57
+
58
+ Attach an existing Forum file and provision its CI key.
59
+
60
+ | Option | Description |
61
+ | --- | --- |
62
+ | `--name <name>` | Unique source name; sources merge in installation order |
63
+ | `--store <workspace/store>` | Forum store |
64
+ | `--path <path>` | Encrypted file path in Forum |
65
+ | `--key-env <name>` | GitHub repository secret / environment variable for this file's key |
66
+ | `--repository <owner/repo>` | GitHub repository receiving the key |
67
+ | `--config <file>` | Secrets source configuration |
68
+ | `--existing-key` | Reuse an existing GitHub secret without creating a recipient |
69
+
70
+ ### `arcnet secrets init`
71
+
72
+ Create a Forum secrets file, prompt for your password and provision its CI key.
73
+
74
+ | Option | Description |
75
+ | --- | --- |
76
+ | `--name <name>` | Unique source name; sources merge in installation order |
77
+ | `--store <workspace/store>` | Forum store |
78
+ | `--path <path>` | Encrypted file path in Forum |
79
+ | `--key-env <name>` | GitHub repository secret / environment variable for this file's key |
80
+ | `--repository <owner/repo>` | GitHub repository receiving the key |
81
+ | `--config <file>` | Secrets source configuration |
82
+ | `--input <file>` | Initial plaintext JSON file; defaults to an empty object |
83
+
84
+ ### `arcnet secrets sync`
85
+
86
+ Refresh encrypted snapshots from Forum and regenerate validation CI; no decryption.
87
+
88
+ | Option | Description |
89
+ | --- | --- |
90
+ | `--config <file>` | Secrets source configuration |
91
+
15
92
  ## `arcnet version`
16
93
 
17
94
  Show the installed package version.
@@ -37,10 +114,18 @@ Validate project configuration offline.
37
114
 
38
115
  Plan deployment (Terraform contacts providers; static Vercel stays offline).
39
116
 
117
+ | Option | Description |
118
+ | --- | --- |
119
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
120
+
40
121
  ## `arcnet deploy`
41
122
 
42
123
  Deploy the approved project.
43
124
 
125
+ | Option | Description |
126
+ | --- | --- |
127
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
128
+
44
129
  ## `arcnet preview`
45
130
 
46
131
  Deploy static Vercel sites to preview.
@@ -2,13 +2,11 @@
2
2
 
3
3
  ArcNet's TypeScript CLI for installing deployment configuration and deploying projects. The package owns shared runners, validation, workflow templates and the Vercel Terraform module; projects keep configuration and application-specific Dockerfiles.
4
4
 
5
- **Release status:** implementation and checks are complete; the first npm publication is blocked by registry authentication. The commands below target the pending `0.1.0` release.
6
-
7
5
  From an existing project with Zap, GitHub, and pinned pnpm:
8
6
 
9
7
  ```sh
10
- pnpm dlx @mp-lb/arcnet@0.1.0 setup hello-world --dry-run
11
- pnpm dlx @mp-lb/arcnet@0.1.0 setup hello-world
8
+ pnpm dlx @mp-lb/arcnet@0.2.0 setup hello-world --dry-run
9
+ pnpm dlx @mp-lb/arcnet@0.2.0 setup hello-world
12
10
  pnpm install
13
11
  pnpm exec arcnet check
14
12
  ```
@@ -19,6 +17,41 @@ Setup pins this package in the existing root manifest. Commit the resulting lock
19
17
 
20
18
  Zap Arc remains the pinned Terraform engine dependency. Environment bootstrap, health checks and teardown are not implemented by this package yet. The static Vercel adapter retains its existing static-only and explicit-project-reference limitations.
21
19
 
20
+ ## Layered secrets
21
+
22
+ `arcnet secrets add` attaches an existing Forum encrypted JSON file. `arcnet secrets init` creates one. Both register a file-specific decryption key in the selected GitHub repository, save an encrypted snapshot, and generate a validation workflow. Setup prompts for your Forum encryption password locally; it never prints the password or recipient key. Use `--existing-key` when the GitHub secret already exists. The local `fm` and `gh` CLIs must be authenticated; the selected Forum parent directory must exist.
23
+
24
+ Use `/projects/<project-slug>/secrets.json.enc` for project files in the company Forum store.
25
+
26
+ The project keeps only `.arcnet/secrets.json`, encrypted snapshots under `.arcnet/secrets/`, and workflow references. Sources merge left to right, with later values winning. Each source names its own key environment variable. JSON must be an object; unused structured values are allowed, while selected environment values must be nonempty strings. Consumer mappings select and rename fields; an array of source names accepts aliases only when their values agree.
27
+
28
+ ```json
29
+ {
30
+ "version": 1,
31
+ "repository": "example/hello-world",
32
+ "sources": [
33
+ { "name": "standard", "store": "example/company", "path": "/admin/standard.json.enc", "keyEnv": "STANDARD_SECRETS_KEY" },
34
+ { "name": "project", "store": "example/company", "path": "/projects/hello-world/secrets.json.enc", "keyEnv": "PROJECT_SECRETS_KEY" }
35
+ ],
36
+ "consumers": {
37
+ "publish": { "NODE_AUTH_TOKEN": ["NPM_TOKEN", "NODE_AUTH_TOKEN"] },
38
+ "deploy": { "VERCEL_TOKEN": "VERCEL_API_TOKEN" }
39
+ }
40
+ }
41
+ ```
42
+
43
+ `secrets sync` fetches ciphertext and adds snapshot identity, revision and checksum to this configuration. Commit config and ciphertext together. CI uses the reviewed snapshots and needs only each file's decryption key, not a Forum login. Sync after editing or rotating a source; obsolete snapshots can be removed after checking that no configuration references them. Setup/sync also wire source-key references into workflow steps that call ArcNet's env or deployment commands.
44
+
45
+ For live loading, omit a snapshot or select `--live`: the installed `fm` CLI fetches each source using its local login or the source's optional `tokenEnv` access credential. A decryption recipient is **not** a Forum read token. Live CI therefore needs an installed, authenticated `fm` as well as the independent decryption keys. Generated CI intentionally uses snapshots.
46
+
47
+ `arcnet env check` reports source names and selected key names, never values. `arcnet env export` explicitly emits selected plaintext JSON. `arcnet env run` gives a subprocess only the selected values and a small execution environment; it removes file keys, isolates temporary HOME/cache paths, and withholds subprocess output. Keep npm publishing mappings scoped to publishing credentials. The command reference documents the exact options.
48
+
49
+ For the first local Terraform plan and deploy, GitHub's file keys cannot be read back. Run `arcnet plan --password` or `arcnet deploy --password` in your terminal: ArcNet prompts once for your Forum encryption password and decrypts the reviewed snapshots locally. `arcnet env check --password` validates them the same way. CI still uses the independent file keys and rejects password mode.
50
+
51
+ When `.arcnet/secrets.json` exists, deployment commands automatically load it. The `deploy` consumer selects operator credentials; Terraform application values are selected using the existing service env lists in `zap.yaml`. No duplicated application map is required. Offline project checks remain offline. Secrets commands in GitHub Actions require the configured repository's main branch.
52
+
53
+ Provisioning modifies Forum and GitHub separately. A failed GitHub upload leaves an unused Forum recipient; rerun `secrets add` to create/upload another and remove the unused recipient later. If upload succeeded but snapshot/config writing failed, attach the file with `--existing-key`. Existing sources are not silently rotated on reruns. Provision while nobody else is editing that file: the current fm write interface does not offer atomic revision-conditional writes, though ArcNet checks identity/revision before updating and refuses occupied init paths. GitHub secret names are repository-scoped; environment-level overrides take precedence in jobs that use an environment.
54
+
22
55
  # ArcNet command reference
23
56
 
24
57
  Generated from the CLI command tree.
@@ -33,6 +66,83 @@ Generated from the CLI command tree.
33
66
  | `--jsonl` | Render structured completion events |
34
67
  | `--noninteractive` | Run without prompts (all commands are noninteractive) |
35
68
 
69
+ ## `arcnet env`
70
+
71
+ Load ordered encrypted Forum sources and select environment values.
72
+
73
+ ### `arcnet env check`
74
+
75
+ Validate decryption and mappings; report key names only.
76
+
77
+ | Option | Description |
78
+ | --- | --- |
79
+ | `--config <file>` | Secrets source configuration |
80
+ | `--consumer <name>` | Named mapping in the secrets configuration |
81
+ | `--live` | Fetch current ciphertext through the installed fm CLI instead of snapshots |
82
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
83
+
84
+ ### `arcnet env export`
85
+
86
+ Write selected plaintext JSON to stdout for an explicit pipeline.
87
+
88
+ | Option | Description |
89
+ | --- | --- |
90
+ | `--config <file>` | Secrets source configuration |
91
+ | `--consumer <name>` | Named mapping in the secrets configuration |
92
+ | `--live` | Fetch current ciphertext through the installed fm CLI instead of snapshots |
93
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
94
+
95
+ ### `arcnet env run <command...>`
96
+
97
+ Run a command with selected keys only; withhold child output.
98
+
99
+ | Option | Description |
100
+ | --- | --- |
101
+ | `--config <file>` | Secrets source configuration |
102
+ | `--consumer <name>` | Named mapping in the secrets configuration |
103
+ | `--live` | Fetch current ciphertext through the installed fm CLI instead of snapshots |
104
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
105
+
106
+ ## `arcnet secrets`
107
+
108
+ Attach Forum files, provision GitHub keys and refresh encrypted snapshots.
109
+
110
+ ### `arcnet secrets add`
111
+
112
+ Attach an existing Forum file and provision its CI key.
113
+
114
+ | Option | Description |
115
+ | --- | --- |
116
+ | `--name <name>` | Unique source name; sources merge in installation order |
117
+ | `--store <workspace/store>` | Forum store |
118
+ | `--path <path>` | Encrypted file path in Forum |
119
+ | `--key-env <name>` | GitHub repository secret / environment variable for this file's key |
120
+ | `--repository <owner/repo>` | GitHub repository receiving the key |
121
+ | `--config <file>` | Secrets source configuration |
122
+ | `--existing-key` | Reuse an existing GitHub secret without creating a recipient |
123
+
124
+ ### `arcnet secrets init`
125
+
126
+ Create a Forum secrets file, prompt for your password and provision its CI key.
127
+
128
+ | Option | Description |
129
+ | --- | --- |
130
+ | `--name <name>` | Unique source name; sources merge in installation order |
131
+ | `--store <workspace/store>` | Forum store |
132
+ | `--path <path>` | Encrypted file path in Forum |
133
+ | `--key-env <name>` | GitHub repository secret / environment variable for this file's key |
134
+ | `--repository <owner/repo>` | GitHub repository receiving the key |
135
+ | `--config <file>` | Secrets source configuration |
136
+ | `--input <file>` | Initial plaintext JSON file; defaults to an empty object |
137
+
138
+ ### `arcnet secrets sync`
139
+
140
+ Refresh encrypted snapshots from Forum and regenerate validation CI; no decryption.
141
+
142
+ | Option | Description |
143
+ | --- | --- |
144
+ | `--config <file>` | Secrets source configuration |
145
+
36
146
  ## `arcnet version`
37
147
 
38
148
  Show the installed package version.
@@ -58,10 +168,18 @@ Validate project configuration offline.
58
168
 
59
169
  Plan deployment (Terraform contacts providers; static Vercel stays offline).
60
170
 
171
+ | Option | Description |
172
+ | --- | --- |
173
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
174
+
61
175
  ## `arcnet deploy`
62
176
 
63
177
  Deploy the approved project.
64
178
 
179
+ | Option | Description |
180
+ | --- | --- |
181
+ | `--password` | Prompt for the Forum encryption password locally instead of using file keys |
182
+
65
183
  ## `arcnet preview`
66
184
 
67
185
  Deploy static Vercel sites to preview.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mp-lb/arcnet",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "ArcNet project installation and deployment CLI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,6 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
+ "@mp-lb/forum-secrets": "0.2.1",
37
38
  "@mp-lb/zapper": "0.21.0",
38
39
  "commander": "14.0.3",
39
40
  "yaml": "2.8.1",
package/src/createCli.ts CHANGED
@@ -9,6 +9,8 @@ import { renderResult } from "./renderResult.js";
9
9
  import { runTerraform } from "./runTerraform.js";
10
10
  import { runVercel } from "./runVercel.js";
11
11
  import { setup } from "./setup.js";
12
+ import { createSecretsCommands } from "./secrets/createSecretsCommands.js";
13
+ import { withDeploymentSecrets } from "./secrets/withDeploymentSecrets.js";
12
14
 
13
15
  export const createCli = () => {
14
16
  const cli = new Command("arcnet")
@@ -31,6 +33,8 @@ export const createCli = () => {
31
33
  process.stdout.write(renderResult(result, cli.opts()) + "\n");
32
34
  };
33
35
 
36
+ createSecretsCommands(cli, output);
37
+
34
38
  cli
35
39
  .command("version")
36
40
  .description("Show the installed package version.")
@@ -66,6 +70,11 @@ export const createCli = () => {
66
70
  create: "Create one explicitly approved Vercel project.",
67
71
  })) {
68
72
  const command = cli.command(name).description(description);
73
+ if (name === "plan" || name === "deploy")
74
+ command.option(
75
+ "--password",
76
+ "Prompt for the Forum encryption password locally instead of using file keys",
77
+ );
69
78
  if (name === "create") command.argument("<site>", "Configured site name");
70
79
  command.action(async (site: unknown) => {
71
80
  const root = realpathSync(cli.opts().root);
@@ -77,21 +86,33 @@ export const createCli = () => {
77
86
  ).mode;
78
87
 
79
88
  const machine = Boolean(cli.opts().json || cli.opts().jsonl);
80
- if (mode === "vercel")
81
- output(
82
- await runVercel(
89
+
90
+ const execute = async () => {
91
+ if (mode === "vercel")
92
+ return runVercel(
83
93
  name,
84
94
  root,
85
95
  typeof site === "string" ? site : undefined,
86
96
  machine,
87
- ),
88
- );
89
- else if (name === "check" || name === "plan" || name === "deploy")
90
- output(runTerraform(name, root, machine));
91
- else
97
+ );
98
+ if (name === "check" || name === "plan" || name === "deploy")
99
+ return runTerraform(name, root, machine);
92
100
  throw new Error(
93
101
  `${name} is only supported for static Vercel references.`,
94
102
  );
103
+ };
104
+
105
+ output(
106
+ await (name === "check" || (name === "plan" && mode === "vercel")
107
+ ? execute()
108
+ : withDeploymentSecrets(
109
+ root,
110
+ mode,
111
+ execute,
112
+ Boolean(command.opts().password),
113
+ Boolean(cli.opts().noninteractive),
114
+ )),
115
+ );
95
116
  });
96
117
  }
97
118
 
@@ -0,0 +1,40 @@
1
+ import { posix } from "node:path";
2
+ import type { SecretSource } from "./config.js";
3
+ import { secretCommand } from "./secretCommand.js";
4
+
5
+ // fm's write API can overwrite without --force, so its default is not create-only.
6
+ export const assertNewSource = (source: SecretSource, root: string) => {
7
+ const listing = secretCommand(
8
+ "fm",
9
+ [
10
+ "file",
11
+ "ls",
12
+ posix.dirname(source.path),
13
+ "--store",
14
+ source.store,
15
+ "--json",
16
+ ],
17
+ root,
18
+ );
19
+
20
+ let names: string[];
21
+
22
+ try {
23
+ names = listing
24
+ .trim()
25
+ .split("\n")
26
+ .filter(Boolean)
27
+ .map((line) => {
28
+ const entry = JSON.parse(line) as { message?: unknown };
29
+ if (typeof entry.message !== "string") throw new Error();
30
+ return entry.message;
31
+ });
32
+ } catch {
33
+ throw new Error("Cannot verify that the new Forum path is unused.");
34
+ }
35
+
36
+ if (names.includes(posix.basename(source.path)))
37
+ throw new Error(
38
+ "Forum file already exists; use secrets add to attach it without replacing its contents.",
39
+ );
40
+ };
@@ -0,0 +1,91 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { z } from "zod";
3
+ import { safeTargetPath } from "../safeTargetPath.js";
4
+
5
+ export const envName = z.string().regex(/^[A-Z_][A-Z0-9_]*$/);
6
+ export const sourceSchema = z
7
+ .object({
8
+ name: z.string().regex(/^[a-z][a-z0-9-]*$/),
9
+ store: z.string().regex(/^[\w-]+\/[\w-]+$/),
10
+ path: z
11
+ .string()
12
+ .startsWith("/")
13
+ .endsWith(".enc")
14
+ .refine(
15
+ (path) =>
16
+ !/[\r\n\0]/.test(path) &&
17
+ path
18
+ .split("/")
19
+ .slice(1)
20
+ .every((part) => part && part !== "." && part !== ".."),
21
+ ),
22
+ keyEnv: envName,
23
+ tokenEnv: envName.optional(),
24
+ snapshot: z
25
+ .object({
26
+ file: z.string(),
27
+ fileId: z.string().min(1),
28
+ revision: z.number().int().nonnegative(),
29
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
30
+ })
31
+ .strict()
32
+ .optional(),
33
+ })
34
+ .strict();
35
+ export const secretsConfigSchema = z
36
+ .object({
37
+ version: z.literal(1),
38
+ repository: z.string().regex(/^[\w.-]+\/[\w.-]+$/),
39
+ sources: z.array(sourceSchema).min(1),
40
+ consumers: z
41
+ .record(
42
+ z.string(),
43
+ z.record(
44
+ z.string().min(1),
45
+ z.union([envName, z.array(envName).min(1)]),
46
+ ),
47
+ )
48
+ .default({}),
49
+ })
50
+ .strict()
51
+ .superRefine((config, ctx) => {
52
+ if (
53
+ new Set(config.sources.map((s) => s.name)).size !== config.sources.length
54
+ )
55
+ ctx.addIssue({ code: "custom", message: "Source names must be unique." });
56
+ if (
57
+ new Set(config.sources.map((s) => s.keyEnv)).size !==
58
+ config.sources.length
59
+ )
60
+ ctx.addIssue({
61
+ code: "custom",
62
+ message: "Each source needs its own key environment name.",
63
+ });
64
+ for (const mapping of Object.values(config.consumers))
65
+ for (const name of Object.keys(mapping))
66
+ if (
67
+ !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) &&
68
+ name !== "pnpm_config_//registry.npmjs.org/:_authToken"
69
+ )
70
+ ctx.addIssue({
71
+ code: "custom",
72
+ message: "Invalid output environment name.",
73
+ });
74
+ });
75
+ export type SecretsConfig = z.infer<typeof secretsConfigSchema>;
76
+ export type SecretSource = z.infer<typeof sourceSchema>;
77
+
78
+ export const readSecretsConfig = (
79
+ root: string,
80
+ file = ".arcnet/secrets.json",
81
+ ): SecretsConfig => {
82
+ try {
83
+ return secretsConfigSchema.parse(
84
+ JSON.parse(readFileSync(safeTargetPath(root, file), "utf8")),
85
+ );
86
+ } catch {
87
+ throw new Error(
88
+ "Missing or invalid secrets configuration (values omitted).",
89
+ );
90
+ }
91
+ };