pinnace 0.2.0 → 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/README.md CHANGED
@@ -26,7 +26,9 @@ Requires Node >= 22.
26
26
 
27
27
  ## Configuration + secrets
28
28
 
29
- Every setting resolves **CLI arg > env (via `ldenv`) > `pinnace.json`**.
29
+ Every setting resolves **CLI arg > exported env > `.env.local` > `.env` > `pinnace.json`**.
30
+
31
+ On startup the `pinnace` bin auto-loads `.env` then `.env.local` from the current directory into the environment (via `ldenv`), so a global install (`npm install -g pinnace`) picks up your secrets with no wrapper: just drop them in `.env.local` and run `pinnace …`. Loading is silent and cwd-only (never a home/global location), and it only AUGMENTS the environment: a value you exported explicitly still wins over the file (`.env.local` overrides `.env`, both sit below an exported var and above `pinnace.json`).
30
32
 
31
33
  `pinnace.json` holds only NON-secret structure (commit-safe):
32
34
 
@@ -46,13 +48,13 @@ Every setting resolves **CLI arg > env (via `ldenv`) > `pinnace.json`**.
46
48
  Secrets are **env-only, never in the config file** (structurally: the resolver has no file path for them). Each host's bearer token is read from `PINNACE_HOST_<NAME>_TOKEN` (the name upper-cased), and the master from `PINNACE_MASTER`:
47
49
 
48
50
  ```sh
49
- # .env.local (git-ignored) — the ONLY place secrets live
51
+ # .env.local (git-ignored) — the ONLY place secrets live; auto-loaded from cwd
50
52
  PINNACE_MASTER=<your high-entropy master secret>
51
53
  PINNACE_HOST_PUBLISHER_TOKEN=<publisher bearer token>
52
54
  PINNACE_HOST_REPLICA_TOKEN=<replica bearer token>
53
55
  ```
54
56
 
55
- A missing token fails loud (naming the exact env var), never a silent empty token. Point the CLI at a config anywhere with `--config <path>`.
57
+ This `.env.local` is loaded automatically from the directory you run `pinnace` in (no `export` needed); an explicitly exported value still takes precedence over it. A missing token fails loud (naming the exact env var), never a silent empty token. Point the CLI at a config anywhere with `--config <path>`.
56
58
 
57
59
  ## The end-to-end setup
58
60
 
package/dist/cli/bin.js CHANGED
@@ -8,9 +8,15 @@
8
8
  * on-box `pinnace node <verb>` subcommands share this same binary but are wired
9
9
  * by their own task. Config/env/core are all injectable there so the dispatch
10
10
  * is unit-tested without a process or the operator's real env/config.
11
+ *
12
+ * Startup: the bin calls the {@link main} shim (`./startup.ts`), which loads the
13
+ * cwd's `.env` / `.env.local` into `process.env` via `ldenv` BEFORE `run()`
14
+ * reads env — so a global `npm install -g pinnace` gets the env-only secrets
15
+ * model with no `pnpm ldenv` dev wrapper. The pure `run(argv, {env})` path stays
16
+ * hermetic (the dotenv load lives in the shim, not in `run()`).
11
17
  */
12
- import { run } from './run.js';
13
- run(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
18
+ import { main } from './startup.js';
19
+ main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
14
20
  console.error(err instanceof Error ? err.message : String(err));
15
21
  process.exit(1);
16
22
  });
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AACH,OAAO,EAAC,GAAG,EAAC,MAAM,UAAU,CAAC;AAE7B,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9B,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,GAAG,EAAE,EAAE;IACP,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CACD,CAAC"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAC,IAAI,EAAC,MAAM,cAAc,CAAC;AAElC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC/B,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,GAAG,EAAE,EAAE;IACP,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CACD,CAAC"}
@@ -0,0 +1,19 @@
1
+ /** Injectable seams for the startup shim (defaulted to the real ones). */
2
+ export interface StartupOptions {
3
+ /**
4
+ * Load `.env` / `.env.local` from the cwd into `process.env`. Defaults to
5
+ * `ldenv`'s `loadEnv` (reads `.env` then `.env.local`, local overriding,
6
+ * never overriding an already-exported value). Injected only for tests.
7
+ */
8
+ loadDotEnv?: () => void;
9
+ }
10
+ /**
11
+ * The CLI startup entry: load the cwd's `.env` / `.env.local` into
12
+ * `process.env` (so a global install gets the env-only secrets), THEN dispatch
13
+ * `run()` over the process argv. Returns the process exit code.
14
+ *
15
+ * This is what the `pinnace` bin calls. It keeps the `loadEnv()` side effect
16
+ * out of the pure `run()` path so `run(argv, {env})` stays hermetic.
17
+ */
18
+ export declare function main(argv?: readonly string[], options?: StartupOptions): Promise<number>;
19
+ //# sourceMappingURL=startup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../../src/cli/startup.ts"],"names":[],"mappings":"AA+BA,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,IAAI,CACzB,IAAI,GAAE,SAAS,MAAM,EAA0B,EAC/C,OAAO,GAAE,cAAmB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAMjB"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The `pinnace` CLI STARTUP shim: the thin process-entry glue that runs BEFORE
3
+ * the pure {@link run} dispatch. Its whole job is to make the env-only secrets
4
+ * model (CONTEXT.md `master key` / `token`) ergonomic for EVERY user, including
5
+ * a global `npm install -g pinnace`, by loading `.env` / `.env.local` from the
6
+ * cwd into `process.env` at startup, so no `pnpm ldenv` dev wrapper is needed.
7
+ *
8
+ * WHY a shim (and not inside {@link run}): `run(argv, {env})` is pure and
9
+ * hermetically unit-tested with an INJECTED `env` record; it must never read a
10
+ * real `.env` / touch a real cwd. So the ONE impure, filesystem-touching step
11
+ * (`ldenv`'s {@link loadEnv}) lives HERE, in the bin/startup layer, in front of
12
+ * `run()`. Config resolution + {@link resolveMasterSecret} read `process.env`,
13
+ * so `loadEnv()` must have mutated it BEFORE `run()` runs.
14
+ *
15
+ * PRECEDENCE (preserved, not changed). `loadEnv()` reads `.env` then
16
+ * `.env.local` (local overriding default) and MUTATES `process.env`, but it
17
+ * does NOT override a value ALREADY set in `process.env`. So dotenv slots in
18
+ * BELOW an explicitly-exported env var and ABOVE `pinnace.json`, keeping the
19
+ * documented "env > file" rule intact. The effective chain becomes:
20
+ * **CLI arg > exported `process.env` > `.env.local` > `.env` > `pinnace.json`**.
21
+ * The master + host tokens stay env-only: `.env.local` is merely a FILE SOURCE
22
+ * for those env vars, never a `pinnace.json` field.
23
+ *
24
+ * The `loadEnv` call is injected (defaulting to the real `ldenv` one) purely so
25
+ * the shim is unit-testable; production always uses the real `ldenv.loadEnv`,
26
+ * which is cwd-based only (never a global/home location), silent, and only
27
+ * AUGMENTS env — an already-exported value is left untouched.
28
+ */
29
+ import { loadEnv } from 'ldenv';
30
+ import { run } from './run.js';
31
+ /**
32
+ * The CLI startup entry: load the cwd's `.env` / `.env.local` into
33
+ * `process.env` (so a global install gets the env-only secrets), THEN dispatch
34
+ * `run()` over the process argv. Returns the process exit code.
35
+ *
36
+ * This is what the `pinnace` bin calls. It keeps the `loadEnv()` side effect
37
+ * out of the pure `run()` path so `run(argv, {env})` stays hermetic.
38
+ */
39
+ export async function main(argv = process.argv.slice(2), options = {}) {
40
+ const loadDotEnv = options.loadDotEnv ?? (() => void loadEnv());
41
+ // Load the cwd's dotenv files BEFORE run() reads process.env. This only
42
+ // augments process.env; an already-exported value still wins.
43
+ loadDotEnv();
44
+ return run(argv);
45
+ }
46
+ //# sourceMappingURL=startup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup.js","sourceRoot":"","sources":["../../src/cli/startup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAC,OAAO,EAAC,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAC,GAAG,EAAC,MAAM,UAAU,CAAC;AAY7B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CACzB,OAA0B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAC/C,UAA0B,EAAE;IAE5B,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;IAChE,wEAAwE;IACxE,8DAA8D;IAC9D,UAAU,EAAE,CAAC;IACb,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;AAClB,CAAC"}
@@ -10,7 +10,7 @@ const DEFAULT_KUBO_VERSION = 'v0.38.1';
10
10
  * the same agent. Overridable per-box via {@link ProvisionInput.pinnaceVersion}.
11
11
  * `pinnace@0.1.0` is the first published release (npm, public, OIDC provenance).
12
12
  */
13
- const DEFAULT_PINNACE_VERSION = '0.2.0';
13
+ const DEFAULT_PINNACE_VERSION = '0.3.0';
14
14
  /**
15
15
  * The pinned Node.js major the box installs via NodeSource (`setup_<this>.x`).
16
16
  * Node 22 is a current active LTS; Node 20 (the old literal) is the OLDEST LTS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinnace",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "AGPL-3.0-only",
5
5
  "description": "Self-host a static website on IPFS across one or more self-owned Kubo nodes.",
6
6
  "repository": {
@@ -27,7 +27,8 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "files-from-path": "^1.1.4",
30
- "ipfs-car": "^3.1.0"
30
+ "ipfs-car": "^3.1.0",
31
+ "ldenv": "^0.3.16"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@ipld/car": "^5.4.6",
package/src/cli/bin.ts CHANGED
@@ -8,10 +8,16 @@
8
8
  * on-box `pinnace node <verb>` subcommands share this same binary but are wired
9
9
  * by their own task. Config/env/core are all injectable there so the dispatch
10
10
  * is unit-tested without a process or the operator's real env/config.
11
+ *
12
+ * Startup: the bin calls the {@link main} shim (`./startup.ts`), which loads the
13
+ * cwd's `.env` / `.env.local` into `process.env` via `ldenv` BEFORE `run()`
14
+ * reads env — so a global `npm install -g pinnace` gets the env-only secrets
15
+ * model with no `pnpm ldenv` dev wrapper. The pure `run(argv, {env})` path stays
16
+ * hermetic (the dotenv load lives in the shim, not in `run()`).
11
17
  */
12
- import {run} from './run.js';
18
+ import {main} from './startup.js';
13
19
 
14
- run(process.argv.slice(2)).then(
20
+ main(process.argv.slice(2)).then(
15
21
  (code) => process.exit(code),
16
22
  (err) => {
17
23
  console.error(err instanceof Error ? err.message : String(err));
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The `pinnace` CLI STARTUP shim: the thin process-entry glue that runs BEFORE
3
+ * the pure {@link run} dispatch. Its whole job is to make the env-only secrets
4
+ * model (CONTEXT.md `master key` / `token`) ergonomic for EVERY user, including
5
+ * a global `npm install -g pinnace`, by loading `.env` / `.env.local` from the
6
+ * cwd into `process.env` at startup, so no `pnpm ldenv` dev wrapper is needed.
7
+ *
8
+ * WHY a shim (and not inside {@link run}): `run(argv, {env})` is pure and
9
+ * hermetically unit-tested with an INJECTED `env` record; it must never read a
10
+ * real `.env` / touch a real cwd. So the ONE impure, filesystem-touching step
11
+ * (`ldenv`'s {@link loadEnv}) lives HERE, in the bin/startup layer, in front of
12
+ * `run()`. Config resolution + {@link resolveMasterSecret} read `process.env`,
13
+ * so `loadEnv()` must have mutated it BEFORE `run()` runs.
14
+ *
15
+ * PRECEDENCE (preserved, not changed). `loadEnv()` reads `.env` then
16
+ * `.env.local` (local overriding default) and MUTATES `process.env`, but it
17
+ * does NOT override a value ALREADY set in `process.env`. So dotenv slots in
18
+ * BELOW an explicitly-exported env var and ABOVE `pinnace.json`, keeping the
19
+ * documented "env > file" rule intact. The effective chain becomes:
20
+ * **CLI arg > exported `process.env` > `.env.local` > `.env` > `pinnace.json`**.
21
+ * The master + host tokens stay env-only: `.env.local` is merely a FILE SOURCE
22
+ * for those env vars, never a `pinnace.json` field.
23
+ *
24
+ * The `loadEnv` call is injected (defaulting to the real `ldenv` one) purely so
25
+ * the shim is unit-testable; production always uses the real `ldenv.loadEnv`,
26
+ * which is cwd-based only (never a global/home location), silent, and only
27
+ * AUGMENTS env — an already-exported value is left untouched.
28
+ */
29
+ import {loadEnv} from 'ldenv';
30
+ import {run} from './run.js';
31
+
32
+ /** Injectable seams for the startup shim (defaulted to the real ones). */
33
+ export interface StartupOptions {
34
+ /**
35
+ * Load `.env` / `.env.local` from the cwd into `process.env`. Defaults to
36
+ * `ldenv`'s `loadEnv` (reads `.env` then `.env.local`, local overriding,
37
+ * never overriding an already-exported value). Injected only for tests.
38
+ */
39
+ loadDotEnv?: () => void;
40
+ }
41
+
42
+ /**
43
+ * The CLI startup entry: load the cwd's `.env` / `.env.local` into
44
+ * `process.env` (so a global install gets the env-only secrets), THEN dispatch
45
+ * `run()` over the process argv. Returns the process exit code.
46
+ *
47
+ * This is what the `pinnace` bin calls. It keeps the `loadEnv()` side effect
48
+ * out of the pure `run()` path so `run(argv, {env})` stays hermetic.
49
+ */
50
+ export async function main(
51
+ argv: readonly string[] = process.argv.slice(2),
52
+ options: StartupOptions = {},
53
+ ): Promise<number> {
54
+ const loadDotEnv = options.loadDotEnv ?? (() => void loadEnv());
55
+ // Load the cwd's dotenv files BEFORE run() reads process.env. This only
56
+ // augments process.env; an already-exported value still wins.
57
+ loadDotEnv();
58
+ return run(argv);
59
+ }
@@ -164,7 +164,7 @@ const DEFAULT_KUBO_VERSION = 'v0.38.1';
164
164
  * the same agent. Overridable per-box via {@link ProvisionInput.pinnaceVersion}.
165
165
  * `pinnace@0.1.0` is the first published release (npm, public, OIDC provenance).
166
166
  */
167
- const DEFAULT_PINNACE_VERSION = '0.2.0';
167
+ const DEFAULT_PINNACE_VERSION = '0.3.0';
168
168
  /**
169
169
  * The pinned Node.js major the box installs via NodeSource (`setup_<this>.x`).
170
170
  * Node 22 is a current active LTS; Node 20 (the old literal) is the OLDEST LTS