pinnace 0.0.0 → 0.1.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/LICENSE +661 -0
- package/README.md +15 -0
- package/dist/car/car-build.d.ts +26 -0
- package/dist/car/car-build.d.ts.map +1 -0
- package/dist/car/car-build.js +113 -0
- package/dist/car/car-build.js.map +1 -0
- package/dist/ci/ci-emit.d.ts +116 -0
- package/dist/ci/ci-emit.d.ts.map +1 -0
- package/dist/ci/ci-emit.js +190 -0
- package/dist/ci/ci-emit.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +17 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/run.d.ts +78 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +506 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/config/config-resolution.d.ts +186 -0
- package/dist/config/config-resolution.d.ts.map +1 -0
- package/dist/config/config-resolution.js +137 -0
- package/dist/config/config-resolution.js.map +1 -0
- package/dist/deploy/deploy.d.ts +121 -0
- package/dist/deploy/deploy.d.ts.map +1 -0
- package/dist/deploy/deploy.js +150 -0
- package/dist/deploy/deploy.js.map +1 -0
- package/dist/derive/ipns-key-derivation.d.ts +37 -0
- package/dist/derive/ipns-key-derivation.d.ts.map +1 -0
- package/dist/derive/ipns-key-derivation.js +130 -0
- package/dist/derive/ipns-key-derivation.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/node/node-commands.d.ts +115 -0
- package/dist/node/node-commands.d.ts.map +1 -0
- package/dist/node/node-commands.js +227 -0
- package/dist/node/node-commands.js.map +1 -0
- package/dist/provision/cloud-init.d.ts +137 -0
- package/dist/provision/cloud-init.d.ts.map +1 -0
- package/dist/provision/cloud-init.js +390 -0
- package/dist/provision/cloud-init.js.map +1 -0
- package/dist/publisher/key-import.d.ts +105 -0
- package/dist/publisher/key-import.d.ts.map +1 -0
- package/dist/publisher/key-import.js +76 -0
- package/dist/publisher/key-import.js.map +1 -0
- package/dist/publisher/record-sequence.d.ts +90 -0
- package/dist/publisher/record-sequence.d.ts.map +1 -0
- package/dist/publisher/record-sequence.js +250 -0
- package/dist/publisher/record-sequence.js.map +1 -0
- package/dist/rpc/kubo-rpc-client.d.ts +154 -0
- package/dist/rpc/kubo-rpc-client.d.ts.map +1 -0
- package/dist/rpc/kubo-rpc-client.js +207 -0
- package/dist/rpc/kubo-rpc-client.js.map +1 -0
- package/dist/rpc/mock-kubo.d.ts +87 -0
- package/dist/rpc/mock-kubo.d.ts.map +1 -0
- package/dist/rpc/mock-kubo.js +129 -0
- package/dist/rpc/mock-kubo.js.map +1 -0
- package/dist/site/site-management.d.ts +126 -0
- package/dist/site/site-management.d.ts.map +1 -0
- package/dist/site/site-management.js +123 -0
- package/dist/site/site-management.js.map +1 -0
- package/dist/status/status-report.d.ts +145 -0
- package/dist/status/status-report.d.ts.map +1 -0
- package/dist/status/status-report.js +192 -0
- package/dist/status/status-report.js.map +1 -0
- package/package.json +45 -2
- package/src/car/car-build.ts +135 -0
- package/src/ci/ci-emit.ts +284 -0
- package/src/cli/bin.ts +20 -0
- package/src/cli/run.ts +654 -0
- package/src/config/config-resolution.ts +264 -0
- package/src/deploy/deploy.ts +250 -0
- package/src/derive/ipns-key-derivation.ts +173 -0
- package/src/index.ts +146 -0
- package/src/node/node-commands.ts +391 -0
- package/src/provision/cloud-init.ts +551 -0
- package/src/publisher/key-import.ts +141 -0
- package/src/publisher/record-sequence.ts +336 -0
- package/src/rpc/kubo-rpc-client.ts +281 -0
- package/src/rpc/mock-kubo.ts +194 -0
- package/src/site/site-management.ts +241 -0
- package/src/status/status-report.ts +291 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The result of building a CAR: its raw bytes and its authoritative root CID. */
|
|
2
|
+
export interface BuiltCar {
|
|
3
|
+
/** The complete CAR file bytes (header carries {@link BuiltCar.rootCid}). */
|
|
4
|
+
readonly carBytes: Uint8Array;
|
|
5
|
+
/** The site's UnixFS directory root CID, as the LAST encoder block. */
|
|
6
|
+
readonly rootCid: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Build a CAR for a site source directory, entirely in-process.
|
|
10
|
+
*
|
|
11
|
+
* @param sourceDir the site's built output directory (its CONTENTS become the
|
|
12
|
+
* CAR root — the directory itself is NOT wrapped in an extra segment).
|
|
13
|
+
* @returns the CAR bytes plus the authoritative root CID (the last block the
|
|
14
|
+
* directory encoder emits, mirrored into the CAR header).
|
|
15
|
+
* @throws if no files are found under `sourceDir` (nothing to deploy).
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildCar(sourceDir: string): Promise<BuiltCar>;
|
|
18
|
+
/**
|
|
19
|
+
* Build a CAR for `sourceDir` and write it to `outPath`.
|
|
20
|
+
*
|
|
21
|
+
* A thin convenience over {@link buildCar} for the deploy path that persists a
|
|
22
|
+
* CAR to disk before importing it into each node. Returns the same
|
|
23
|
+
* {@link BuiltCar} (its `carBytes` are exactly the bytes written).
|
|
24
|
+
*/
|
|
25
|
+
export declare function writeCar(sourceDir: string, outPath: string): Promise<BuiltCar>;
|
|
26
|
+
//# sourceMappingURL=car-build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"car-build.d.ts","sourceRoot":"","sources":["../../src/car/car-build.ts"],"names":[],"mappings":"AAmCA,kFAAkF;AAClF,MAAM,WAAW,QAAQ;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqDnE;AAED;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACb,OAAO,CAAC,QAAQ,CAAC,CAInB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process **CAR** (Content Addressable aRchive) builder — the default and
|
|
3
|
+
* primary deploy artifact (CONTEXT.md `CAR`; spec user stories 4-6).
|
|
4
|
+
*
|
|
5
|
+
* Given a site SOURCE DIRECTORY, this builds a CAR whose root is a real UnixFS
|
|
6
|
+
* DIRECTORY preserving the site structure (`index.html`, `assets/...`). The DAG
|
|
7
|
+
* is built entirely IN-PROCESS via the `ipfs-car` library (no external CLI, no
|
|
8
|
+
* output scraping), so the root CID is authoritative and the build reproducible.
|
|
9
|
+
*
|
|
10
|
+
* The scheme (ported, not copied, from the reference prototype
|
|
11
|
+
* `~/searches/ipfs-hetzner/deploy-car.mjs`):
|
|
12
|
+
*
|
|
13
|
+
* 1. `filesFromPaths([sourceDir])` yields the directory's files with
|
|
14
|
+
* SITE-RELATIVE paths (`index.html`, `assets/s.css`) and NO wrapping
|
|
15
|
+
* segment. We pass them straight in: the CAR root IS the site dir. (An
|
|
16
|
+
* earlier prototype wrongly stripped a leading segment; that bug is fixed
|
|
17
|
+
* here and guarded by a test asserting `index.html` sits at the root.)
|
|
18
|
+
* 2. Pipe the files through `createDirectoryEncoderStream`, buffering every
|
|
19
|
+
* emitted block and capturing the LAST block's CID as the directory root
|
|
20
|
+
* (authoritative — captured from the encoder, NEVER scraped from text).
|
|
21
|
+
* 3. Re-encode the buffered blocks with `new CAREncoderStream([rootCid])` so
|
|
22
|
+
* the CAR HEADER carries the root. That is what makes
|
|
23
|
+
* `dag/import?pin-roots=true` pin the site root on every node.
|
|
24
|
+
*
|
|
25
|
+
* The build BUFFERS blocks in memory (fine for normal static sites; streaming
|
|
26
|
+
* very large sites is explicitly out of scope for v1 — see the spec).
|
|
27
|
+
*/
|
|
28
|
+
import { writeFile } from 'node:fs/promises';
|
|
29
|
+
import { createDirectoryEncoderStream, CAREncoderStream, } from 'ipfs-car';
|
|
30
|
+
import { filesFromPaths } from 'files-from-path';
|
|
31
|
+
/**
|
|
32
|
+
* Build a CAR for a site source directory, entirely in-process.
|
|
33
|
+
*
|
|
34
|
+
* @param sourceDir the site's built output directory (its CONTENTS become the
|
|
35
|
+
* CAR root — the directory itself is NOT wrapped in an extra segment).
|
|
36
|
+
* @returns the CAR bytes plus the authoritative root CID (the last block the
|
|
37
|
+
* directory encoder emits, mirrored into the CAR header).
|
|
38
|
+
* @throws if no files are found under `sourceDir` (nothing to deploy).
|
|
39
|
+
*/
|
|
40
|
+
export async function buildCar(sourceDir) {
|
|
41
|
+
// filesFromPaths([sourceDir]) already yields SITE-RELATIVE paths
|
|
42
|
+
// ("index.html", "assets/s.css"). Do NOT strip a leading segment.
|
|
43
|
+
const files = await filesFromPaths([sourceDir]);
|
|
44
|
+
if (files.length === 0) {
|
|
45
|
+
throw new Error(`no files found under ${sourceDir}`);
|
|
46
|
+
}
|
|
47
|
+
// Pass 1: encode the directory, buffering blocks and capturing the root CID
|
|
48
|
+
// as the LAST emitted block. The header roots are patched in pass 2, so we
|
|
49
|
+
// encode with an empty root list here and discard the resulting stream.
|
|
50
|
+
const blocks = [];
|
|
51
|
+
let rootBlock;
|
|
52
|
+
await createDirectoryEncoderStream(files)
|
|
53
|
+
.pipeThrough(new TransformStream({
|
|
54
|
+
transform(block, controller) {
|
|
55
|
+
// The directory root is the LAST block the encoder emits.
|
|
56
|
+
rootBlock = block;
|
|
57
|
+
blocks.push(block);
|
|
58
|
+
controller.enqueue(block);
|
|
59
|
+
},
|
|
60
|
+
}))
|
|
61
|
+
.pipeThrough(new CAREncoderStream([]))
|
|
62
|
+
.pipeTo(new WritableStream());
|
|
63
|
+
if (!rootBlock) {
|
|
64
|
+
// Unreachable given files.length > 0, but keeps the root authoritative.
|
|
65
|
+
throw new Error(`encoder produced no blocks for ${sourceDir}`);
|
|
66
|
+
}
|
|
67
|
+
const rootCid = rootBlock.cid.toString();
|
|
68
|
+
// Pass 2: re-encode the SAME buffered blocks WITH the known root in the CAR
|
|
69
|
+
// header, so `dag/import?pin-roots=true` pins the site root.
|
|
70
|
+
const chunks = [];
|
|
71
|
+
const pending = blocks.slice();
|
|
72
|
+
const source = new ReadableStream({
|
|
73
|
+
pull(controller) {
|
|
74
|
+
const next = pending.shift();
|
|
75
|
+
if (next)
|
|
76
|
+
controller.enqueue(next);
|
|
77
|
+
else
|
|
78
|
+
controller.close();
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
await source.pipeThrough(new CAREncoderStream([rootBlock.cid])).pipeTo(new WritableStream({
|
|
82
|
+
write(chunk) {
|
|
83
|
+
chunks.push(chunk);
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
86
|
+
return { carBytes: concatChunks(chunks), rootCid };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build a CAR for `sourceDir` and write it to `outPath`.
|
|
90
|
+
*
|
|
91
|
+
* A thin convenience over {@link buildCar} for the deploy path that persists a
|
|
92
|
+
* CAR to disk before importing it into each node. Returns the same
|
|
93
|
+
* {@link BuiltCar} (its `carBytes` are exactly the bytes written).
|
|
94
|
+
*/
|
|
95
|
+
export async function writeCar(sourceDir, outPath) {
|
|
96
|
+
const built = await buildCar(sourceDir);
|
|
97
|
+
await writeFile(outPath, built.carBytes);
|
|
98
|
+
return built;
|
|
99
|
+
}
|
|
100
|
+
/** Concatenate CAR stream chunks into one contiguous byte array. */
|
|
101
|
+
function concatChunks(chunks) {
|
|
102
|
+
let total = 0;
|
|
103
|
+
for (const chunk of chunks)
|
|
104
|
+
total += chunk.length;
|
|
105
|
+
const out = new Uint8Array(total);
|
|
106
|
+
let offset = 0;
|
|
107
|
+
for (const chunk of chunks) {
|
|
108
|
+
out.set(chunk, offset);
|
|
109
|
+
offset += chunk.length;
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=car-build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"car-build.js","sourceRoot":"","sources":["../../src/car/car-build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **CI emitter** behind the `CIProvider` seam (CONTEXT.md `CI provider
|
|
3
|
+
* seam`). `install-ci` asks the core for a deploy pipeline: an emitter WRITES a
|
|
4
|
+
* deploy workflow for a CI system and REPORTS the repo secrets/vars the
|
|
5
|
+
* operator must set. GitHub Actions is the FIRST (and, in v1, only)
|
|
6
|
+
* implementation; other CI systems are Out of Scope but slot in behind this
|
|
7
|
+
* same seam later (spec "Out of Scope"; user stories 16, 17).
|
|
8
|
+
*
|
|
9
|
+
* This module is PURE: {@link emitCi} takes a plain input and returns the
|
|
10
|
+
* workflow file (path + contents) plus the secrets/vars report as data. It does
|
|
11
|
+
* NOT touch the filesystem or the network — the CLI wrapper (`cli-command-
|
|
12
|
+
* wrapper` task) is what writes the file to `.github/workflows/` and prints the
|
|
13
|
+
* report. Keeping the emit pure lets it be snapshot-tested as a string with no
|
|
14
|
+
* fixtures (test-first policy) and reused as a TypeScript API (CONTEXT.md `core
|
|
15
|
+
* vs cli`).
|
|
16
|
+
*
|
|
17
|
+
* BEHAVIOUR PORTED (not copied) from the reference GitHub Action
|
|
18
|
+
* `~/searches/ipfs-hetzner/github-workflow.yml`: the shape is checkout ->
|
|
19
|
+
* setup-node -> install -> build -> a `deploy` step reading the multi-node env
|
|
20
|
+
* contract (`IPFS_API`/`IPFS_TOKEN` comma-separated, publisher first),
|
|
21
|
+
* `SITE_NAME`, `SITE_MODE` -> a run-summary. Two deliberate CORRECTIONS vs the
|
|
22
|
+
* reference: (1) the deploy step invokes `pinnace deploy` (this tool now OWNS
|
|
23
|
+
* deploy) instead of a copied `deploy-car.mjs` script — one implementation, no
|
|
24
|
+
* bash/TS drift; (2) the same env contract carries the MULTI-NODE + per-site
|
|
25
|
+
* `mode` semantics (same CID to all nodes; the publisher publishes the record,
|
|
26
|
+
* replicas mirror) which the deploy core (`deploy-multi-target` +
|
|
27
|
+
* `publisher-replica-model` tasks) enforces — the workflow just passes the env
|
|
28
|
+
* through.
|
|
29
|
+
*/
|
|
30
|
+
/** The CI systems pinnace can emit for. v1 = GitHub Actions ONLY. */
|
|
31
|
+
export type CiSystem = 'github';
|
|
32
|
+
/** The CI systems, in a stable order (help text / iteration / validation). */
|
|
33
|
+
export declare const CI_SYSTEMS: readonly CiSystem[];
|
|
34
|
+
/** One required repo secret or variable the operator must set for the pipeline. */
|
|
35
|
+
export interface RequiredCiSetting {
|
|
36
|
+
/** The env/secret/var NAME as referenced in the workflow (e.g. `IPFS_API`). */
|
|
37
|
+
name: string;
|
|
38
|
+
/**
|
|
39
|
+
* `secret` (masked, e.g. tokens) or `var` (plain repo variable, e.g. URLs /
|
|
40
|
+
* names). GitHub stores these separately; the report tells the operator
|
|
41
|
+
* which panel each belongs in.
|
|
42
|
+
*/
|
|
43
|
+
kind: 'secret' | 'var';
|
|
44
|
+
/** A human-readable note (what it is, format, an example). */
|
|
45
|
+
description: string;
|
|
46
|
+
/**
|
|
47
|
+
* True when the value is a COMMA-SEPARATED list, one per target node with
|
|
48
|
+
* the PUBLISHER FIRST (the multi-node contract). `IPFS_API`/`IPFS_TOKEN` are
|
|
49
|
+
* multi-node; `SITE_NAME`/`SITE_MODE` are single values.
|
|
50
|
+
*/
|
|
51
|
+
multiNode: boolean;
|
|
52
|
+
/** An example value, shown in the report to make the format concrete. */
|
|
53
|
+
example: string;
|
|
54
|
+
}
|
|
55
|
+
/** Inputs to {@link emitCi}: which system + how the site builds. */
|
|
56
|
+
export interface EmitCiInput {
|
|
57
|
+
/** Which CI system to emit for (v1: `github`). */
|
|
58
|
+
system: CiSystem;
|
|
59
|
+
/** The build command that produces the static output dir (e.g. `npm run build`). */
|
|
60
|
+
buildCommand: string;
|
|
61
|
+
/** The directory the build outputs the static site to (e.g. `dist`). */
|
|
62
|
+
outputDir: string;
|
|
63
|
+
/** The branch to deploy on push (default `main`). */
|
|
64
|
+
branch?: string;
|
|
65
|
+
/** The Node.js version the workflow sets up (default `20`). */
|
|
66
|
+
nodeVersion?: string;
|
|
67
|
+
}
|
|
68
|
+
/** A single emitted file: where it goes + what it contains. */
|
|
69
|
+
export interface EmittedFile {
|
|
70
|
+
/** The repo-relative path to write the file at. */
|
|
71
|
+
path: string;
|
|
72
|
+
/** The full file contents. */
|
|
73
|
+
contents: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* What an emitter returns: the workflow file plus the secrets/vars the operator
|
|
77
|
+
* must set. `secrets` and `vars` are split so the CLI can tell the operator
|
|
78
|
+
* exactly which GitHub settings panel each value belongs in.
|
|
79
|
+
*/
|
|
80
|
+
export interface EmittedCi {
|
|
81
|
+
/** Which system this was emitted for (echoed for the caller). */
|
|
82
|
+
system: CiSystem;
|
|
83
|
+
/** The deploy workflow file (path + contents). */
|
|
84
|
+
workflow: EmittedFile;
|
|
85
|
+
/** The required masked SECRETS (Settings -> Secrets). */
|
|
86
|
+
secrets: RequiredCiSetting[];
|
|
87
|
+
/** The required repo VARIABLES (Settings -> Variables). */
|
|
88
|
+
vars: RequiredCiSetting[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The `CIProvider` seam: one method that emits a deploy pipeline + its required
|
|
92
|
+
* settings for a given input. v1 has a single implementation
|
|
93
|
+
* ({@link githubCiProvider}); adding GitLab CI / others later means adding
|
|
94
|
+
* another provider here and an entry in {@link CI_SYSTEMS} — deploy/publish
|
|
95
|
+
* logic is untouched (spec user story 21).
|
|
96
|
+
*/
|
|
97
|
+
export interface CIProvider {
|
|
98
|
+
/** The system this provider emits for. */
|
|
99
|
+
readonly system: CiSystem;
|
|
100
|
+
/** Emit the workflow + secrets/vars report for the given input. */
|
|
101
|
+
emit(input: EmitCiInput): EmittedCi;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The GitHub Actions provider — the first implementation of {@link CIProvider}.
|
|
105
|
+
* Emits the deploy workflow to the conventional GitHub path and reports the
|
|
106
|
+
* secrets/vars split into the two GitHub settings panels.
|
|
107
|
+
*/
|
|
108
|
+
export declare const githubCiProvider: CIProvider;
|
|
109
|
+
/**
|
|
110
|
+
* Emit a deploy pipeline for the requested CI system: dispatches to the
|
|
111
|
+
* matching {@link CIProvider} and returns its workflow + secrets/vars report.
|
|
112
|
+
* Throws LOUDLY on an unknown/unimplemented system (the seam exists so callers
|
|
113
|
+
* can add systems later, but v1 only ships `github`) — never a silent no-op.
|
|
114
|
+
*/
|
|
115
|
+
export declare function emitCi(input: EmitCiInput): EmittedCi;
|
|
116
|
+
//# sourceMappingURL=ci-emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-emit.d.ts","sourceRoot":"","sources":["../../src/ci/ci-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,qEAAqE;AACrE,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC;AAEhC,8EAA8E;AAC9E,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAAe,CAAC;AAE1D,mFAAmF;AACnF,MAAM,WAAW,iBAAiB;IACjC,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IACvB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC3B,kDAAkD;IAClD,MAAM,EAAE,QAAQ,CAAC;IACjB,oFAAoF;IACpF,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC3B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,iEAAiE;IACjE,MAAM,EAAE,QAAQ,CAAC;IACjB,kDAAkD;IAClD,QAAQ,EAAE,WAAW,CAAC;IACtB,yDAAyD;IACzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,2DAA2D;IAC3D,IAAI,EAAE,iBAAiB,EAAE,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,mEAAmE;IACnE,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;CACpC;AAsID;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,UAa9B,CAAC;AAOF;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAUpD"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **CI emitter** behind the `CIProvider` seam (CONTEXT.md `CI provider
|
|
3
|
+
* seam`). `install-ci` asks the core for a deploy pipeline: an emitter WRITES a
|
|
4
|
+
* deploy workflow for a CI system and REPORTS the repo secrets/vars the
|
|
5
|
+
* operator must set. GitHub Actions is the FIRST (and, in v1, only)
|
|
6
|
+
* implementation; other CI systems are Out of Scope but slot in behind this
|
|
7
|
+
* same seam later (spec "Out of Scope"; user stories 16, 17).
|
|
8
|
+
*
|
|
9
|
+
* This module is PURE: {@link emitCi} takes a plain input and returns the
|
|
10
|
+
* workflow file (path + contents) plus the secrets/vars report as data. It does
|
|
11
|
+
* NOT touch the filesystem or the network — the CLI wrapper (`cli-command-
|
|
12
|
+
* wrapper` task) is what writes the file to `.github/workflows/` and prints the
|
|
13
|
+
* report. Keeping the emit pure lets it be snapshot-tested as a string with no
|
|
14
|
+
* fixtures (test-first policy) and reused as a TypeScript API (CONTEXT.md `core
|
|
15
|
+
* vs cli`).
|
|
16
|
+
*
|
|
17
|
+
* BEHAVIOUR PORTED (not copied) from the reference GitHub Action
|
|
18
|
+
* `~/searches/ipfs-hetzner/github-workflow.yml`: the shape is checkout ->
|
|
19
|
+
* setup-node -> install -> build -> a `deploy` step reading the multi-node env
|
|
20
|
+
* contract (`IPFS_API`/`IPFS_TOKEN` comma-separated, publisher first),
|
|
21
|
+
* `SITE_NAME`, `SITE_MODE` -> a run-summary. Two deliberate CORRECTIONS vs the
|
|
22
|
+
* reference: (1) the deploy step invokes `pinnace deploy` (this tool now OWNS
|
|
23
|
+
* deploy) instead of a copied `deploy-car.mjs` script — one implementation, no
|
|
24
|
+
* bash/TS drift; (2) the same env contract carries the MULTI-NODE + per-site
|
|
25
|
+
* `mode` semantics (same CID to all nodes; the publisher publishes the record,
|
|
26
|
+
* replicas mirror) which the deploy core (`deploy-multi-target` +
|
|
27
|
+
* `publisher-replica-model` tasks) enforces — the workflow just passes the env
|
|
28
|
+
* through.
|
|
29
|
+
*/
|
|
30
|
+
/** The CI systems, in a stable order (help text / iteration / validation). */
|
|
31
|
+
export const CI_SYSTEMS = ['github'];
|
|
32
|
+
/**
|
|
33
|
+
* The multi-node + per-site-mode env contract, shared by the reported
|
|
34
|
+
* secrets/vars and the workflow's deploy step (single source so they never
|
|
35
|
+
* drift). Order is stable: publisher-first multi-node inputs, then the site
|
|
36
|
+
* name/mode. `IPFS_TOKEN` is the sole secret; the rest are plain vars.
|
|
37
|
+
*/
|
|
38
|
+
const IPFS_API_SETTING = {
|
|
39
|
+
name: 'IPFS_API',
|
|
40
|
+
kind: 'var',
|
|
41
|
+
multiNode: true,
|
|
42
|
+
description: 'Kubo RPC endpoint(s). One URL, or comma-separated for MULTIPLE nodes ' +
|
|
43
|
+
'(publisher first, replicas after). Same CID lands on every node.',
|
|
44
|
+
example: 'https://ipfs-a.you.com,https://ipfs-b.you.com',
|
|
45
|
+
};
|
|
46
|
+
const IPFS_TOKEN_SETTING = {
|
|
47
|
+
name: 'IPFS_TOKEN',
|
|
48
|
+
kind: 'secret',
|
|
49
|
+
multiNode: true,
|
|
50
|
+
description: 'Bearer token(s) guarding the RPC API. One token, or comma-separated ' +
|
|
51
|
+
'tokens in the SAME order as IPFS_API (publisher first).',
|
|
52
|
+
example: 'tokenA,tokenB',
|
|
53
|
+
};
|
|
54
|
+
const SITE_NAME_SETTING = {
|
|
55
|
+
name: 'SITE_NAME',
|
|
56
|
+
kind: 'var',
|
|
57
|
+
multiNode: false,
|
|
58
|
+
description: 'The site name / ENS name this deploy targets.',
|
|
59
|
+
example: 'mysite.eth',
|
|
60
|
+
};
|
|
61
|
+
const SITE_MODE_SETTING = {
|
|
62
|
+
name: 'SITE_MODE',
|
|
63
|
+
kind: 'var',
|
|
64
|
+
multiNode: false,
|
|
65
|
+
description: "Per-site mode: 'ipfs' (immutable, ENS ipfs://<cid> per deploy) or " +
|
|
66
|
+
"'ipns' (mutable, publisher publishes the record, replicas mirror). " +
|
|
67
|
+
"Defaults to 'ipns'.",
|
|
68
|
+
example: 'ipns',
|
|
69
|
+
};
|
|
70
|
+
/** All settings in report order (drives both the report split and validation). */
|
|
71
|
+
const ALL_SETTINGS = [
|
|
72
|
+
IPFS_API_SETTING,
|
|
73
|
+
IPFS_TOKEN_SETTING,
|
|
74
|
+
SITE_NAME_SETTING,
|
|
75
|
+
SITE_MODE_SETTING,
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* Render the GitHub Actions deploy workflow. Ports the reference Action's shape
|
|
79
|
+
* (checkout -> setup-node -> install -> build -> deploy reading the env
|
|
80
|
+
* contract -> run summary), but the deploy step calls `pinnace deploy` (this
|
|
81
|
+
* tool owns deploy) and the env carries the multi-node + per-site-mode
|
|
82
|
+
* semantics the deploy core enforces. Deterministic: same input -> byte-
|
|
83
|
+
* identical output (snapshot-locked).
|
|
84
|
+
*/
|
|
85
|
+
function renderGithubWorkflow(input) {
|
|
86
|
+
const branch = input.branch ?? 'main';
|
|
87
|
+
const nodeVersion = input.nodeVersion ?? '20';
|
|
88
|
+
const buildCommand = input.buildCommand;
|
|
89
|
+
const outputDir = input.outputDir;
|
|
90
|
+
return `# =============================================================================
|
|
91
|
+
# pinnace: deploy a static site to your self-hosted IPFS node(s) on push.
|
|
92
|
+
#
|
|
93
|
+
# Generated by \`pinnace install-ci --system github\`. Edit BUILD/OUTPUT below
|
|
94
|
+
# for your stack; the deploy step is owned by the \`pinnace\` CLI.
|
|
95
|
+
#
|
|
96
|
+
# Set these in your repo (Settings -> Secrets and variables -> Actions):
|
|
97
|
+
# IPFS_API (variable) one URL, OR comma-separated for MULTIPLE nodes,
|
|
98
|
+
# publisher first (e.g. https://ipfs-a.you.com,https://ipfs-b.you.com)
|
|
99
|
+
# IPFS_TOKEN (secret) matching token(s), comma-separated in the SAME order
|
|
100
|
+
# SITE_NAME (variable) e.g. mysite.eth
|
|
101
|
+
# SITE_MODE (variable) ipfs | ipns (default ipns)
|
|
102
|
+
#
|
|
103
|
+
# Multiple nodes serve the SAME CID (redundancy). In ipns mode the PUBLISHER
|
|
104
|
+
# (first in IPFS_API) publishes the signed record; replicas mirror it.
|
|
105
|
+
# =============================================================================
|
|
106
|
+
name: Deploy to IPFS
|
|
107
|
+
|
|
108
|
+
on:
|
|
109
|
+
push:
|
|
110
|
+
branches: [${branch}]
|
|
111
|
+
workflow_dispatch: {}
|
|
112
|
+
|
|
113
|
+
concurrency:
|
|
114
|
+
group: pinnace-deploy
|
|
115
|
+
cancel-in-progress: true
|
|
116
|
+
|
|
117
|
+
jobs:
|
|
118
|
+
deploy:
|
|
119
|
+
runs-on: ubuntu-latest
|
|
120
|
+
steps:
|
|
121
|
+
- uses: actions/checkout@v4
|
|
122
|
+
|
|
123
|
+
- uses: actions/setup-node@v4
|
|
124
|
+
with:
|
|
125
|
+
node-version: ${nodeVersion}
|
|
126
|
+
cache: npm
|
|
127
|
+
|
|
128
|
+
- name: Install deps
|
|
129
|
+
run: npm ci
|
|
130
|
+
|
|
131
|
+
- name: Build
|
|
132
|
+
run: ${buildCommand}
|
|
133
|
+
|
|
134
|
+
- name: Deploy to IPFS node(s)
|
|
135
|
+
id: deploy
|
|
136
|
+
env:
|
|
137
|
+
IPFS_API: \${{ vars.IPFS_API }}
|
|
138
|
+
IPFS_TOKEN: \${{ secrets.IPFS_TOKEN }}
|
|
139
|
+
SITE_NAME: \${{ vars.SITE_NAME }}
|
|
140
|
+
SITE_MODE: \${{ vars.SITE_MODE || 'ipns' }}
|
|
141
|
+
run: |
|
|
142
|
+
npx pinnace deploy --mode "$SITE_MODE" "${outputDir}" "$SITE_NAME"
|
|
143
|
+
|
|
144
|
+
- name: Summary
|
|
145
|
+
if: always()
|
|
146
|
+
run: |
|
|
147
|
+
echo "### IPFS deploy" >> "$GITHUB_STEP_SUMMARY"
|
|
148
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
149
|
+
echo "- CID: \\\`\${{ steps.deploy.outputs.cid }}\\\`" >> "$GITHUB_STEP_SUMMARY"
|
|
150
|
+
echo "- IPNS: \\\`\${{ steps.deploy.outputs.ipns }}\\\`" >> "$GITHUB_STEP_SUMMARY"
|
|
151
|
+
echo "- Preview: https://\${{ steps.deploy.outputs.cid }}.ipfs.dweb.link/" >> "$GITHUB_STEP_SUMMARY"
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The GitHub Actions provider — the first implementation of {@link CIProvider}.
|
|
156
|
+
* Emits the deploy workflow to the conventional GitHub path and reports the
|
|
157
|
+
* secrets/vars split into the two GitHub settings panels.
|
|
158
|
+
*/
|
|
159
|
+
export const githubCiProvider = {
|
|
160
|
+
system: 'github',
|
|
161
|
+
emit(input) {
|
|
162
|
+
return {
|
|
163
|
+
system: 'github',
|
|
164
|
+
workflow: {
|
|
165
|
+
path: '.github/workflows/pinnace-deploy.yml',
|
|
166
|
+
contents: renderGithubWorkflow(input),
|
|
167
|
+
},
|
|
168
|
+
secrets: ALL_SETTINGS.filter((s) => s.kind === 'secret'),
|
|
169
|
+
vars: ALL_SETTINGS.filter((s) => s.kind === 'var'),
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
/** The provider registry (system -> provider). v1 has a single entry. */
|
|
174
|
+
const PROVIDERS = {
|
|
175
|
+
github: githubCiProvider,
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Emit a deploy pipeline for the requested CI system: dispatches to the
|
|
179
|
+
* matching {@link CIProvider} and returns its workflow + secrets/vars report.
|
|
180
|
+
* Throws LOUDLY on an unknown/unimplemented system (the seam exists so callers
|
|
181
|
+
* can add systems later, but v1 only ships `github`) — never a silent no-op.
|
|
182
|
+
*/
|
|
183
|
+
export function emitCi(input) {
|
|
184
|
+
const provider = PROVIDERS[input.system];
|
|
185
|
+
if (!provider) {
|
|
186
|
+
throw new Error(`unsupported CI system '${input.system}'; v1 emits only ${CI_SYSTEMS.join(', ')} (other systems are Out of Scope but the CIProvider seam exists)`);
|
|
187
|
+
}
|
|
188
|
+
return provider.emit(input);
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=ci-emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-emit.js","sourceRoot":"","sources":["../../src/ci/ci-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,UAAU,GAAwB,CAAC,QAAQ,CAAC,CAAC;AA4E1D;;;;;GAKG;AACH,MAAM,gBAAgB,GAAsB;IAC3C,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,KAAK;IACX,SAAS,EAAE,IAAI;IACf,WAAW,EACV,uEAAuE;QACvE,kEAAkE;IACnE,OAAO,EAAE,+CAA+C;CACxD,CAAC;AAEF,MAAM,kBAAkB,GAAsB;IAC7C,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE,IAAI;IACf,WAAW,EACV,sEAAsE;QACtE,yDAAyD;IAC1D,OAAO,EAAE,eAAe;CACxB,CAAC;AAEF,MAAM,iBAAiB,GAAsB;IAC5C,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,KAAK;IACX,SAAS,EAAE,KAAK;IAChB,WAAW,EAAE,+CAA+C;IAC5D,OAAO,EAAE,YAAY;CACrB,CAAC;AAEF,MAAM,iBAAiB,GAAsB;IAC5C,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,KAAK;IACX,SAAS,EAAE,KAAK;IAChB,WAAW,EACV,oEAAoE;QACpE,qEAAqE;QACrE,qBAAqB;IACtB,OAAO,EAAE,MAAM;CACf,CAAC;AAEF,kFAAkF;AAClF,MAAM,YAAY,GAAiC;IAClD,gBAAgB;IAChB,kBAAkB;IAClB,iBAAiB;IACjB,iBAAiB;CACjB,CAAC;AAEF;;;;;;;GAOG;AACH,SAAS,oBAAoB,CAAC,KAAkB;IAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC;IACtC,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,IAAI,CAAC;IAC9C,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;IACxC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IAElC,OAAO;;;;;;;;;;;;;;;;;;;;iBAoBS,MAAM;;;;;;;;;;;;;;;0BAeG,WAAW;;;;;;;eAOtB,YAAY;;;;;;;;;;oDAUyB,SAAS;;;;;;;;;;CAU5D,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAe;IAC3C,MAAM,EAAE,QAAQ;IAChB,IAAI,CAAC,KAAkB;QACtB,OAAO;YACN,MAAM,EAAE,QAAQ;YAChB,QAAQ,EAAE;gBACT,IAAI,EAAE,sCAAsC;gBAC5C,QAAQ,EAAE,oBAAoB,CAAC,KAAK,CAAC;aACrC;YACD,OAAO,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;YACxD,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC;SAClD,CAAC;IACH,CAAC;CACD,CAAC;AAEF,yEAAyE;AACzE,MAAM,SAAS,GAAiC;IAC/C,MAAM,EAAE,gBAAgB;CACxB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,KAAkB;IACxC,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACd,0BAA0B,KAAK,CAAC,MAAM,oBAAoB,UAAU,CAAC,IAAI,CACxE,IAAI,CACJ,kEAAkE,CACnE,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":""}
|
package/dist/cli/bin.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `pinnace` CLI bin — a thin wrapper over the library core.
|
|
4
|
+
*
|
|
5
|
+
* It parses/formats only; ALL behaviour lives in the core (imported from
|
|
6
|
+
* `../index.js`). It wires the client verbs (provision, deploy, install-ci,
|
|
7
|
+
* status, derive) over the injectable `RunContext` seam in `./run.ts`; the
|
|
8
|
+
* on-box `pinnace node <verb>` subcommands share this same binary but are wired
|
|
9
|
+
* by their own task. Config/env/core are all injectable there so the dispatch
|
|
10
|
+
* is unit-tested without a process or the operator's real env/config.
|
|
11
|
+
*/
|
|
12
|
+
import { run } from './run.js';
|
|
13
|
+
run(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
|
|
14
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=bin.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type ProvisionInput, type ProvisionResult } from '../provision/cloud-init.js';
|
|
2
|
+
import { type DeployInput, type DeployResult } from '../deploy/deploy.js';
|
|
3
|
+
import { type EmitCiInput, type EmittedCi } from '../ci/ci-emit.js';
|
|
4
|
+
import { type StatusReportInput, type StatusReport } from '../status/status-report.js';
|
|
5
|
+
import { type DeriveIpnsInput } from '../derive/ipns-key-derivation.js';
|
|
6
|
+
import { type PinnaceConfigFile, type EnvRecord } from '../config/config-resolution.js';
|
|
7
|
+
/**
|
|
8
|
+
* The core functions the client verbs dispatch to. This seam is what makes the
|
|
9
|
+
* CLI a THIN wrapper AND independently testable: production wires the real core
|
|
10
|
+
* ({@link DEFAULT_DEPS}); tests inject recording stubs and assert each verb
|
|
11
|
+
* calls the RIGHT function with the correctly-resolved arguments (rather than
|
|
12
|
+
* re-testing the core through the CLI).
|
|
13
|
+
*/
|
|
14
|
+
export interface ClientDeps {
|
|
15
|
+
/** `provision` -> the cloud-init generator. */
|
|
16
|
+
provision(input: ProvisionInput): ProvisionResult;
|
|
17
|
+
/** `deploy` -> the multi-target CAR deploy. */
|
|
18
|
+
deploy(input: DeployInput): Promise<DeployResult>;
|
|
19
|
+
/** `install-ci` -> the CI workflow emitter. */
|
|
20
|
+
emitCi(input: EmitCiInput): EmittedCi;
|
|
21
|
+
/** `status` -> the per-site status report. */
|
|
22
|
+
statusReport(input: StatusReportInput): Promise<StatusReport>;
|
|
23
|
+
/** `derive` -> the master + site `id` -> IPNS id derivation (no deploy). */
|
|
24
|
+
deriveIpnsId(input: DeriveIpnsInput): string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The CLI run context: the injectable env/config/output/core seams. Everything
|
|
28
|
+
* is optional; omitted fields default to the real process environment, a real
|
|
29
|
+
* `pinnace.json` read, `console` sinks, and the real core. Tests pass explicit
|
|
30
|
+
* in-memory values to stay hermetic.
|
|
31
|
+
*/
|
|
32
|
+
export interface RunContext {
|
|
33
|
+
/** The env layer (defaults to `process.env`). Tests pass an in-memory record. */
|
|
34
|
+
env?: EnvRecord;
|
|
35
|
+
/**
|
|
36
|
+
* Load the parsed config file (defaults to reading `./pinnace.json`, or an
|
|
37
|
+
* empty config if absent). Tests pass an in-memory object.
|
|
38
|
+
*
|
|
39
|
+
* `path` is the operator's explicit `--config <path>` when given, else
|
|
40
|
+
* `undefined` (the `./pinnace.json` default). The default loader treats an
|
|
41
|
+
* ABSENT default file as a benign empty config, but an explicitly-named path
|
|
42
|
+
* that is missing / unreadable / invalid JSON must THROW so `run()` can fail
|
|
43
|
+
* loud naming that path (an operator-named file is a claim it exists).
|
|
44
|
+
*/
|
|
45
|
+
loadConfigFile?: (path?: string) => PinnaceConfigFile;
|
|
46
|
+
/** The core functions to dispatch to (defaults to the real core). */
|
|
47
|
+
deps?: ClientDeps;
|
|
48
|
+
/** stdout sink (defaults to `console.log`). */
|
|
49
|
+
out?: (line: string) => void;
|
|
50
|
+
/** stderr sink (defaults to `console.error`). */
|
|
51
|
+
err?: (line: string) => void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* An explicitly-named `--config <path>` could not be read or parsed. Names the
|
|
55
|
+
* path so `run()` can emit a loud, operator-actionable error (exit 1) instead
|
|
56
|
+
* of silently resolving to an empty config.
|
|
57
|
+
*/
|
|
58
|
+
export declare class ConfigLoadError extends Error {
|
|
59
|
+
readonly path: string;
|
|
60
|
+
readonly kind: 'read' | 'parse';
|
|
61
|
+
constructor(path: string, kind: 'read' | 'parse', cause?: unknown);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Dispatch a pinnace CLI invocation. Returns the process exit code.
|
|
65
|
+
*
|
|
66
|
+
* Routes the client verbs (provision/deploy/install-ci/status/derive), the
|
|
67
|
+
* on-box `node` namespace, and the `site` namespace. A missing command is a
|
|
68
|
+
* benign no-op (exit 0); an UNKNOWN command is loud (exit 1) so the surface is
|
|
69
|
+
* an explicit allow-list, not a silent catch-all.
|
|
70
|
+
*
|
|
71
|
+
* A GLOBAL `--config <path>` flag may appear BEFORE the command; it is consumed
|
|
72
|
+
* here (stripped from the per-verb argv) and threaded into config loading via
|
|
73
|
+
* the {@link RunContext.loadConfigFile} seam. With no `--config`, the default
|
|
74
|
+
* `./pinnace.json` is read and its absence is benign; an explicitly-named path
|
|
75
|
+
* that is missing/unreadable/invalid JSON fails loud (names the path, exit 1).
|
|
76
|
+
*/
|
|
77
|
+
export declare function run(argv: readonly string[], context?: RunContext): Promise<number>;
|
|
78
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AA0BA,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEN,KAAK,WAAW,EAChB,KAAK,YAAY,EAEjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEN,KAAK,WAAW,EAChB,KAAK,SAAS,EAEd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAEN,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEN,KAAK,eAAe,EACpB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAKN,KAAK,iBAAiB,EACtB,KAAK,SAAS,EAId,MAAM,gCAAgC,CAAC;AAExC;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IAC1B,+CAA+C;IAC/C,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAAC;IAClD,+CAA+C;IAC/C,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAClD,+CAA+C;IAC/C,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IACtC,8CAA8C;IAC9C,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9D,4EAA4E;IAC5E,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC;CAC7C;AAWD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,iFAAiF;IACjF,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,iBAAiB,CAAC;IACtD,qEAAqE;IACrE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,+CAA+C;IAC/C,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,iDAAiD;IACjD,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AA8CD;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IAExC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;gBADtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,OAAO,EAC/B,KAAK,CAAC,EAAE,OAAO;CAMhB;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,GAAG,CACxB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,UAAe,GACtB,OAAO,CAAC,MAAM,CAAC,CAkDjB"}
|