@parity/product-deploy 0.7.28-rc.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 +201 -0
- package/README.md +233 -0
- package/assets/environments.json +313 -0
- package/bin/bulletin-bootstrap +84 -0
- package/bin/bulletin-deploy +429 -0
- package/dist/bug-report.d.ts +29 -0
- package/dist/bug-report.js +27 -0
- package/dist/chunk-2VAUMZB2.js +284 -0
- package/dist/chunk-43HLT335.js +232 -0
- package/dist/chunk-5VZQ2KSU.js +231 -0
- package/dist/chunk-ADNBLFDP.js +225 -0
- package/dist/chunk-BMAEWZYV.js +24 -0
- package/dist/chunk-C2TS5MER.js +64 -0
- package/dist/chunk-DNXH4QTI.js +2336 -0
- package/dist/chunk-FZWJV5AD.js +231 -0
- package/dist/chunk-GZD2UFLR.js +8 -0
- package/dist/chunk-HOTQDYHD.js +219 -0
- package/dist/chunk-IDYGYIMH.js +207 -0
- package/dist/chunk-KHVTYIIX.js +146 -0
- package/dist/chunk-KJH2T5TQ.js +172 -0
- package/dist/chunk-KOSF5FDO.js +49 -0
- package/dist/chunk-LZJMVPYW.js +156 -0
- package/dist/chunk-MFTODIIT.js +725 -0
- package/dist/chunk-MMAZFJDG.js +91 -0
- package/dist/chunk-NF2FL4ZO.js +164 -0
- package/dist/chunk-OITUIM2E.js +524 -0
- package/dist/chunk-P6CHOMN3.js +2368 -0
- package/dist/chunk-QMYW3D6E.js +316 -0
- package/dist/chunk-QTZNULSH.js +185 -0
- package/dist/chunk-RI3ZLNPN.js +71 -0
- package/dist/chunk-S7EM5VMW.js +108 -0
- package/dist/chunk-T7EEVWNU.js +32 -0
- package/dist/chunk-UPWEOGLQ.js +37 -0
- package/dist/chunk-ZOC4GITL.js +13 -0
- package/dist/chunk-ZYVGHDMU.js +117 -0
- package/dist/chunk-probe.d.ts +37 -0
- package/dist/chunk-probe.js +18 -0
- package/dist/chunker.d.ts +8 -0
- package/dist/chunker.js +10 -0
- package/dist/deploy.d.ts +299 -0
- package/dist/deploy.js +96 -0
- package/dist/dotns.d.ts +506 -0
- package/dist/dotns.js +101 -0
- package/dist/environments.d.ts +104 -0
- package/dist/environments.js +23 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +8 -0
- package/dist/gh-pages-mirror.d.ts +76 -0
- package/dist/gh-pages-mirror.js +30 -0
- package/dist/incremental-stats.d.ts +69 -0
- package/dist/incremental-stats.js +10 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +146 -0
- package/dist/manifest/byte-budget.d.ts +46 -0
- package/dist/manifest/byte-budget.js +14 -0
- package/dist/manifest/config-load.d.ts +36 -0
- package/dist/manifest/config-load.js +10 -0
- package/dist/manifest/publish.d.ts +54 -0
- package/dist/manifest/publish.js +23 -0
- package/dist/manifest/schema.d.ts +29 -0
- package/dist/manifest/schema.js +10 -0
- package/dist/manifest/types.d.ts +90 -0
- package/dist/manifest/types.js +6 -0
- package/dist/manifest-embed.d.ts +18 -0
- package/dist/manifest-embed.js +9 -0
- package/dist/manifest-fetch.d.ts +32 -0
- package/dist/manifest-fetch.js +21 -0
- package/dist/manifest-roundtrip.d.ts +15 -0
- package/dist/manifest-roundtrip.js +55 -0
- package/dist/manifest.d.ts +44 -0
- package/dist/manifest.js +20 -0
- package/dist/memory-report.d.ts +95 -0
- package/dist/memory-report.js +17 -0
- package/dist/merkle.d.ts +50 -0
- package/dist/merkle.js +33 -0
- package/dist/personhood/bind-paid-alias.d.ts +43 -0
- package/dist/personhood/bind-paid-alias.js +10 -0
- package/dist/personhood/bind-personal-id.d.ts +55 -0
- package/dist/personhood/bind-personal-id.js +12 -0
- package/dist/personhood/bootstrap.d.ts +85 -0
- package/dist/personhood/bootstrap.js +245 -0
- package/dist/personhood/claim-pgas.d.ts +61 -0
- package/dist/personhood/claim-pgas.js +12 -0
- package/dist/personhood/constants.d.ts +23 -0
- package/dist/personhood/constants.js +22 -0
- package/dist/personhood/encoding.d.ts +49 -0
- package/dist/personhood/encoding.js +24 -0
- package/dist/personhood/hashing.d.ts +4 -0
- package/dist/personhood/hashing.js +8 -0
- package/dist/personhood/member-key.d.ts +12 -0
- package/dist/personhood/member-key.js +10 -0
- package/dist/personhood/people-client.d.ts +14 -0
- package/dist/personhood/people-client.js +48 -0
- package/dist/personhood/reprove.d.ts +43 -0
- package/dist/personhood/reprove.js +225 -0
- package/dist/pool.d.ts +51 -0
- package/dist/pool.js +30 -0
- package/dist/run-state.d.ts +22 -0
- package/dist/run-state.js +20 -0
- package/dist/telemetry.d.ts +56 -0
- package/dist/telemetry.js +71 -0
- package/dist/version-check.d.ts +38 -0
- package/dist/version-check.js +30 -0
- package/docs/bootstrap.md +49 -0
- package/docs/e2e-bootstrap.md +154 -0
- package/docs/telemetry.md +62 -0
- package/docs/testing.md +44 -0
- package/package.json +82 -0
- package/tools/release-retry-wrapper.mjs +74 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { deploy, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, NonRetryableError, EXIT_CODE_NO_RETRY, isConnectionError, unpublish } from "../dist/deploy.js";
|
|
4
|
+
import { VERSION, setDeployAttribute, captureWarning, closeTelemetry, setRunStateActive, markRelaunchOomHintShown } from "../dist/telemetry.js";
|
|
5
|
+
import { handleFailedDeploy, handlePreflightVersionCheck, fetchVersionInfo, preReleaseWarning, checkNodeVersion } from "../dist/version-check.js";
|
|
6
|
+
import { setDeployContext, installLogCapture, buildCliFlagsSummary } from "../dist/bug-report.js";
|
|
7
|
+
import { loadRunState, writeRunState, shouldSkipStaleWarning, shouldShowOomHint, probablyOomRssMb } from "../dist/run-state.js";
|
|
8
|
+
import { loadEnvironments, listEnvironments, formatEnvironmentTable, DEFAULT_ENV_ID } from "../dist/environments.js";
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
|
|
11
|
+
// Install early so anything printed during flag parsing / preflight is
|
|
12
|
+
// available to the bug-report log tail.
|
|
13
|
+
installLogCapture();
|
|
14
|
+
|
|
15
|
+
// Preflight: fail fast on unsupported Node.js versions.
|
|
16
|
+
{
|
|
17
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
18
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
19
|
+
const nodeErr = checkNodeVersion(pkg.engines?.node ?? ">=22", process.version);
|
|
20
|
+
if (nodeErr) { console.error(`Error: ${nodeErr}`); process.exit(1); }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
|
|
25
|
+
const flags = {};
|
|
26
|
+
const positional = [];
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
if (args[i] === "--bootstrap") { flags.removedBootstrap = true; }
|
|
29
|
+
else if (args[i] === "--pool-size") { flags.poolSize = parseInt(args[++i], 10); }
|
|
30
|
+
else if (args[i] === "--mnemonic") { flags.mnemonic = args[++i]; }
|
|
31
|
+
else if (args[i] === "--derivation-path") { flags.derivationPath = args[++i]; }
|
|
32
|
+
else if (args[i] === "--rpc") { flags.rpc = args[++i]; }
|
|
33
|
+
else if (args[i] === "--env") { flags.env = args[++i]; }
|
|
34
|
+
else if (args[i] === "--list-environments") { flags.listEnvironments = true; }
|
|
35
|
+
else if (args[i] === "--password") { flags.password = args[++i]; }
|
|
36
|
+
else if (args[i] === "--js-merkle") { flags.jsMerkle = true; }
|
|
37
|
+
else if (args[i] === "--input-car") { flags.inputCar = args[++i]; }
|
|
38
|
+
else if (args[i] === "--tag") { flags.tag = args[++i]; }
|
|
39
|
+
else if (args[i] === "--config") { flags.config = args[++i]; }
|
|
40
|
+
else if (args[i] === "--gh-pages-mirror") { flags.ghPagesMirror = true; }
|
|
41
|
+
else if (args[i] === "--allow-large-deploy") { flags.allowLargeDeploy = true; }
|
|
42
|
+
else if (args[i] === "--reproducible") { flags.reproducibleSource = "commit"; }
|
|
43
|
+
else if (args[i].startsWith("--reproducible=")) { flags.reproducibleSource = args[i].slice("--reproducible=".length); }
|
|
44
|
+
else if (args[i] === "--dump-car") { flags.dumpCar = true; }
|
|
45
|
+
else if (args[i].startsWith("--dump-car=")) { flags.dumpCar = args[i].slice("--dump-car=".length); }
|
|
46
|
+
else if (args[i] === "--publish") { flags.publish = true; }
|
|
47
|
+
else if (args[i] === "--unpublish") { flags.unpublish = true; }
|
|
48
|
+
else if (args[i] === "--fail-on-publish-error") { flags.failOnPublishError = true; }
|
|
49
|
+
else if (args[i] === "--version" || args[i] === "-V") { flags.version = true; }
|
|
50
|
+
else if (args[i] === "--help" || args[i] === "-h") { flags.help = true; }
|
|
51
|
+
else { positional.push(args[i]); }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (flags.publish && flags.unpublish) {
|
|
55
|
+
console.error("Error: --publish and --unpublish are mutually exclusive.");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (flags.version) {
|
|
60
|
+
console.log(`bulletin-deploy v${VERSION}`);
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (flags.removedBootstrap) {
|
|
65
|
+
console.error("Error: --bootstrap was removed from bulletin-deploy. Use bulletin-bootstrap instead.");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// --list-environments: print the environments table and exit. Composes with
|
|
70
|
+
// nothing else (deploy positional args are ignored).
|
|
71
|
+
if (flags.listEnvironments) {
|
|
72
|
+
try {
|
|
73
|
+
const { doc } = await loadEnvironments();
|
|
74
|
+
console.log(formatEnvironmentTable(listEnvironments(doc)));
|
|
75
|
+
process.exit(0);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error(`Error: failed to load environments: ${e?.message ?? e}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (flags.unpublish) {
|
|
83
|
+
if (!flags.mnemonic && !process.env.MNEMONIC) {
|
|
84
|
+
console.error("Error: --unpublish requires --mnemonic (or MNEMONIC env var).");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
const [domain] = positional;
|
|
88
|
+
if (!domain) {
|
|
89
|
+
console.error("Error: --unpublish requires a domain (e.g. my-app.dot)");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const result = await unpublish(domain, {
|
|
94
|
+
mnemonic: flags.mnemonic,
|
|
95
|
+
derivationPath: flags.derivationPath,
|
|
96
|
+
rpc: flags.rpc,
|
|
97
|
+
env: flags.env,
|
|
98
|
+
});
|
|
99
|
+
console.log(`Domain: ${result.domainName}`);
|
|
100
|
+
console.log(`Status: ${result.status}`);
|
|
101
|
+
process.exit(0);
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.error(`Unpublish failed:`, e?.message ?? e);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// `product` subcommand. Only `validate` is wired today. `publish` and `resolve` arrive in later phases.
|
|
109
|
+
if (positional[0] === "product") {
|
|
110
|
+
const verb = positional[1];
|
|
111
|
+
if (verb === "validate") {
|
|
112
|
+
try {
|
|
113
|
+
const { loadProductConfig, pessimisticSizePreflight, getTextRecordBudgetBytes } =
|
|
114
|
+
await import("../dist/index.js");
|
|
115
|
+
const explicitPath = positional[2];
|
|
116
|
+
const { config, sourcePath } = explicitPath
|
|
117
|
+
? await loadProductConfig({ path: explicitPath })
|
|
118
|
+
: await loadProductConfig();
|
|
119
|
+
console.log(`✓ Loaded ${sourcePath}`);
|
|
120
|
+
console.log(`✓ Schema ${config.domain} (${config.executables.length} executable${config.executables.length === 1 ? "" : "s"})`);
|
|
121
|
+
const budget = getTextRecordBudgetBytes();
|
|
122
|
+
const report = pessimisticSizePreflight(config, budget);
|
|
123
|
+
for (const check of report.checks) {
|
|
124
|
+
const tag = check.ok ? "✓" : "✗";
|
|
125
|
+
console.log(`${tag} ${check.key.padEnd(40)} ${check.bytes} B / ${check.budget} B`);
|
|
126
|
+
}
|
|
127
|
+
if (!report.ok) {
|
|
128
|
+
console.error("Pessimistic size preflight failed — at least one manifest exceeds the dotNS text-record budget.");
|
|
129
|
+
process.exit(EXIT_CODE_NO_RETRY);
|
|
130
|
+
}
|
|
131
|
+
console.log("Validate complete.");
|
|
132
|
+
process.exit(0);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error(`Error: ${err?.message ?? err}`);
|
|
135
|
+
process.exit(err instanceof NonRetryableError ? EXIT_CODE_NO_RETRY : 1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (verb === "publish" || verb === "resolve") {
|
|
139
|
+
console.error(`'product ${verb}' is not implemented yet (planned for a later phase).`);
|
|
140
|
+
process.exit(2);
|
|
141
|
+
}
|
|
142
|
+
console.error(`Unknown product subcommand: ${verb ?? "(none)"}. Try: bulletin-deploy product validate [<config-path>]`);
|
|
143
|
+
process.exit(2);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (flags.publish && !flags.mnemonic && !process.env.MNEMONIC) {
|
|
147
|
+
console.error("Error: --publish requires --mnemonic (or MNEMONIC env var).");
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (flags.help || positional.length === 0) {
|
|
152
|
+
console.log(`bulletin-deploy v${VERSION}
|
|
153
|
+
|
|
154
|
+
Usage:
|
|
155
|
+
bulletin-deploy <build-dir> <domain.dot> Deploy an app
|
|
156
|
+
|
|
157
|
+
Options:
|
|
158
|
+
--env <id> Target environment from environments.json (default: paseo-next-v2).
|
|
159
|
+
Drives both the bulletin RPC and the asset-hub RPC used
|
|
160
|
+
by DotNS. See --list-environments for valid ids.
|
|
161
|
+
--list-environments Print the environments table and exit.
|
|
162
|
+
--mnemonic "..." DotNS owner mnemonic (or set MNEMONIC env var)
|
|
163
|
+
--derivation-path "..." Optional Substrate-style path applied to --mnemonic (e.g. //deploy/3)
|
|
164
|
+
--rpc wss://... Override the bulletin RPC for the chosen --env (or set BULLETIN_RPC).
|
|
165
|
+
Precedence: --rpc > BULLETIN_RPC > --env's bulletin endpoint.
|
|
166
|
+
--pool-size N Number of pool accounts (default: 10)
|
|
167
|
+
--password "..." Encrypt SPA content (users will be prompted to decrypt)
|
|
168
|
+
--js-merkle Use pure-JS merkleization (no IPFS Kubo binary required)
|
|
169
|
+
--input-car <path> Deploy a pre-built CAR file; skips directory scan and merkleization.
|
|
170
|
+
Usage: bulletin-deploy --input-car <file.car> <domain.dot>
|
|
171
|
+
--dump-car[=<path>] Save the pre-upload CAR file to disk. Default path: <buildDir>.bulletin.car.
|
|
172
|
+
Override path with =<path>. Also settable via BULLETIN_DEPLOY_DUMP_CAR env var.
|
|
173
|
+
--tag "..." Label deploy in telemetry (or set DEPLOY_TAG env var); see Telemetry in README
|
|
174
|
+
--config <path> Explicit path to a bulletin-deploy.config.ts (default: walk up from
|
|
175
|
+
<build-dir> looking for bulletin-deploy.config.{ts,js,mjs}). When a
|
|
176
|
+
config is found, deploy ALSO writes the manifest + executable text
|
|
177
|
+
records on <domain> and its app/widget/worker subnames.
|
|
178
|
+
--gh-pages-mirror After deploy, push the CAR to the current repo's gh-pages branch
|
|
179
|
+
at bulletin/<domain>.dot.car (opt-in; also set GH_PAGES_MIRROR=1)
|
|
180
|
+
--publish After deploy, list <domain> in the on-chain Publisher
|
|
181
|
+
registry (Publisher.publish). Only takes effect on envs
|
|
182
|
+
with a deployed Publisher (currently: paseo-next-v2).
|
|
183
|
+
Requires --mnemonic (the signer must own the label).
|
|
184
|
+
--unpublish Standalone mode: removes <domain> from the Publisher
|
|
185
|
+
registry (Publisher.unpublish). Skips the deploy.
|
|
186
|
+
Usage: bulletin-deploy --unpublish <domain.dot>
|
|
187
|
+
Requires --mnemonic. Mutually exclusive with --publish.
|
|
188
|
+
--fail-on-publish-error
|
|
189
|
+
Exit non-zero if --publish fails after a successful deploy.
|
|
190
|
+
Default: non-fatal (warning logged, exit 0).
|
|
191
|
+
--version Show version
|
|
192
|
+
--help Show this help`);
|
|
193
|
+
process.exit(0);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const rcWarning = preReleaseWarning(VERSION);
|
|
197
|
+
if (rcWarning) console.error(rcWarning);
|
|
198
|
+
|
|
199
|
+
// Fire in background immediately; await just before deploy() to stay non-blocking during flag-parse.
|
|
200
|
+
const _versionCheckPromise = process.env.BULLETIN_DEPLOY_UPDATE_CHECK !== "0"
|
|
201
|
+
? fetchVersionInfo()
|
|
202
|
+
: Promise.resolve(null);
|
|
203
|
+
|
|
204
|
+
// ── Crash capture (issue #154) ───────────────────────────────────
|
|
205
|
+
// Only wire crash capture for actual deploy runs — skip for --help / --version
|
|
206
|
+
// (which exit above).
|
|
207
|
+
if (!flags.help && !flags.version) {
|
|
208
|
+
// Sanitised argv — positional args + presence-only flag summary. Never
|
|
209
|
+
// puts a mnemonic/password/RPC/derivation-path on disk, even if the user
|
|
210
|
+
// passes one on the command line.
|
|
211
|
+
const sanitizedArgv = [
|
|
212
|
+
...positional,
|
|
213
|
+
...buildCliFlagsSummary(flags).split(" ").filter(Boolean),
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
// Check if the previous run left a stale "running" or "crashed" marker
|
|
217
|
+
// (i.e. this process is a relaunch after a SIGKILL/OOM). Print a hint
|
|
218
|
+
// BEFORE resetting state so the user sees what happened.
|
|
219
|
+
const prev = loadRunState();
|
|
220
|
+
if (prev && (prev.status === "running" || prev.status === "crashed") && !shouldSkipStaleWarning(prev)) {
|
|
221
|
+
if (shouldShowOomHint(prev)) {
|
|
222
|
+
const peak = prev.lastPeakRssMb ?? "unknown";
|
|
223
|
+
const stage = prev.lastStage ?? "unknown";
|
|
224
|
+
const threshold = probablyOomRssMb();
|
|
225
|
+
console.error("");
|
|
226
|
+
console.error(` Warning: previous deploy did not exit cleanly (peak RSS ${peak} MB at stage "${stage}", threshold ${threshold} MB).`);
|
|
227
|
+
console.error(` This looks like an out-of-memory kill. Retry with a larger heap:`);
|
|
228
|
+
console.error(` NODE_OPTIONS='--max-old-space-size=8192' bulletin-deploy ...`);
|
|
229
|
+
console.error("");
|
|
230
|
+
markRelaunchOomHintShown();
|
|
231
|
+
} else if (prev.status === "crashed" && prev.reason) {
|
|
232
|
+
console.error(` Previous deploy exited via ${prev.reason}. Continuing.`);
|
|
233
|
+
} else if (prev.status === "running") {
|
|
234
|
+
console.error(` Previous deploy did not exit cleanly. Continuing.`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Reset the state file to reflect THIS run. Any previous status (crashed,
|
|
239
|
+
// succeeded, etc.) is overwritten — we already surfaced the warning above.
|
|
240
|
+
writeRunState({
|
|
241
|
+
status: "running",
|
|
242
|
+
pid: process.pid,
|
|
243
|
+
startedAt: Date.now(),
|
|
244
|
+
endedAt: undefined,
|
|
245
|
+
toolVersion: VERSION,
|
|
246
|
+
argv: sanitizedArgv,
|
|
247
|
+
lastPeakRssMb: null,
|
|
248
|
+
lastStage: null,
|
|
249
|
+
reason: undefined,
|
|
250
|
+
});
|
|
251
|
+
setRunStateActive(true);
|
|
252
|
+
|
|
253
|
+
// ── Signal / uncaught handlers ──────────────────────────────
|
|
254
|
+
// Guard against re-entry: if a handler itself throws (e.g. Sentry close
|
|
255
|
+
// rejects), unhandledRejection must not recursively call finalize.
|
|
256
|
+
let finalizing = false;
|
|
257
|
+
const finalize = async (reason, exitCode) => {
|
|
258
|
+
if (finalizing) return;
|
|
259
|
+
finalizing = true;
|
|
260
|
+
try {
|
|
261
|
+
setDeployAttribute("deploy.status", "killed");
|
|
262
|
+
setDeployAttribute("deploy.killed", reason);
|
|
263
|
+
setDeployAttribute("deploy.sad", "true");
|
|
264
|
+
captureWarning(`deploy process terminated: ${reason}`);
|
|
265
|
+
} catch { /* telemetry best-effort */ }
|
|
266
|
+
try { await closeTelemetry(1000); } catch { /* ignore */ }
|
|
267
|
+
try {
|
|
268
|
+
writeRunState({ status: "crashed", endedAt: Date.now(), reason });
|
|
269
|
+
} catch { /* fs best-effort */ }
|
|
270
|
+
process.exit(exitCode);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Connection errors propagate from polkadot-api's internal subscriptions
|
|
274
|
+
// (chainHead etc.) when the WS halts mid-call. They're recoverable via
|
|
275
|
+
// storeChunkedContent's reconnect path; don't exit. The retry-budget
|
|
276
|
+
// circuit breaker (#271) bounds repeat occurrences — if too many fire in
|
|
277
|
+
// 30s, the deploy bails cleanly with "Retry budget exhausted" instead of
|
|
278
|
+
// looping forever. Issue #278 has the full diagnosis.
|
|
279
|
+
const handleUnhandled = (e, kind) => {
|
|
280
|
+
const msgStr = e instanceof Error ? (e.message ?? "") : String(e);
|
|
281
|
+
// Debug breadcrumb — gated by env so it doesn't add stderr noise in
|
|
282
|
+
// production but helps when running E2E fault-injection scenarios.
|
|
283
|
+
if (process.env.BULLETIN_DEPLOY_DEBUG_HANDLERS === "1") {
|
|
284
|
+
console.error(`[handleUnhandled ${kind}] match=${isConnectionError(e)} type=${e?.constructor?.name ?? typeof e} msg=${msgStr.slice(0, 200)}`);
|
|
285
|
+
}
|
|
286
|
+
if (isConnectionError(e)) {
|
|
287
|
+
try {
|
|
288
|
+
captureWarning(`Suppressed ${kind} connection error`, { msg: msgStr.slice(0, 200) });
|
|
289
|
+
} catch { /* telemetry best-effort */ }
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
try { setDeployAttribute("deploy.error", msgStr.slice(0, 200)); } catch {}
|
|
293
|
+
finalize(kind, 2);
|
|
294
|
+
};
|
|
295
|
+
process.on("uncaughtException", (e) => handleUnhandled(e, "uncaught"));
|
|
296
|
+
process.on("unhandledRejection", (e) => handleUnhandled(e, "unhandled"));
|
|
297
|
+
// POSIX exit codes: 128 + signal number. Signal exits (130/SIGINT,
|
|
298
|
+
// 143/SIGTERM, 129/SIGHUP) indicate cancellation or environment teardown
|
|
299
|
+
// and are retryable from the consumer's perspective. Contrast with
|
|
300
|
+
// NonRetryableError exits (code 78, POSIX EX_CONFIG) which callers must
|
|
301
|
+
// NOT retry — see EXIT_CODE_NO_RETRY in src/errors.ts.
|
|
302
|
+
process.on("SIGINT", () => finalize("SIGINT", 130));
|
|
303
|
+
process.on("SIGTERM", () => finalize("SIGTERM", 143));
|
|
304
|
+
process.on("SIGHUP", () => finalize("SIGHUP", 129));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
let buildDir, domain;
|
|
309
|
+
if (flags.inputCar) {
|
|
310
|
+
// --input-car mode: positional[0] is the domain; no build directory needed.
|
|
311
|
+
[domain] = positional;
|
|
312
|
+
if (!flags.inputCar) { console.error("Error: --input-car requires a path argument"); process.exit(1); }
|
|
313
|
+
if (!domain) { console.error("Error: domain required (e.g. my-app.dot)"); process.exit(1); }
|
|
314
|
+
if (!fs.existsSync(flags.inputCar)) { console.error(`Error: ${flags.inputCar} does not exist`); process.exit(1); }
|
|
315
|
+
buildDir = flags.inputCar; // passed as `content` to deploy(); inputCar branch handles it
|
|
316
|
+
} else {
|
|
317
|
+
[buildDir, domain] = positional;
|
|
318
|
+
if (!buildDir) { console.error("Error: build directory required"); process.exit(1); }
|
|
319
|
+
if (!domain) { console.error("Error: domain required (e.g. my-app.dot)"); process.exit(1); }
|
|
320
|
+
if (!fs.existsSync(buildDir)) { console.error(`Error: ${buildDir} does not exist`); process.exit(1); }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const effectiveRpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
|
|
324
|
+
const deployTag = flags.tag ?? process.env.DEPLOY_TAG;
|
|
325
|
+
const ci = process.env.GITHUB_ACTIONS === "true" ? {
|
|
326
|
+
runUrl: process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
|
|
327
|
+
? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}${process.env.GITHUB_RUN_ATTEMPT ? `/attempts/${process.env.GITHUB_RUN_ATTEMPT}` : ""}`
|
|
328
|
+
: undefined,
|
|
329
|
+
workflow: process.env.GITHUB_WORKFLOW,
|
|
330
|
+
job: process.env.GITHUB_JOB,
|
|
331
|
+
sha: process.env.GITHUB_SHA,
|
|
332
|
+
} : undefined;
|
|
333
|
+
setDeployContext({
|
|
334
|
+
domain,
|
|
335
|
+
rpc: effectiveRpc,
|
|
336
|
+
repo: process.env.GITHUB_REPOSITORY,
|
|
337
|
+
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME,
|
|
338
|
+
signerMode: flags.mnemonic ? "direct" : "pool",
|
|
339
|
+
deployTag,
|
|
340
|
+
cliFlags: buildCliFlagsSummary(flags),
|
|
341
|
+
ci,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Await the background version check started at startup.
|
|
345
|
+
const _versionInfo = await _versionCheckPromise;
|
|
346
|
+
if (handlePreflightVersionCheck(_versionInfo) === "abort") process.exit(1);
|
|
347
|
+
|
|
348
|
+
const result = await deploy(buildDir, domain, {
|
|
349
|
+
mnemonic: flags.mnemonic,
|
|
350
|
+
derivationPath: flags.derivationPath,
|
|
351
|
+
rpc: flags.rpc,
|
|
352
|
+
env: flags.env,
|
|
353
|
+
poolSize: flags.poolSize,
|
|
354
|
+
password: flags.password,
|
|
355
|
+
jsMerkle: flags.jsMerkle,
|
|
356
|
+
inputCar: flags.inputCar,
|
|
357
|
+
tag: flags.tag,
|
|
358
|
+
ghPagesMirror: flags.ghPagesMirror,
|
|
359
|
+
allowLargeDeploy: flags.allowLargeDeploy,
|
|
360
|
+
reproducibleSource: flags.reproducibleSource,
|
|
361
|
+
dumpCar: flags.dumpCar,
|
|
362
|
+
publish: flags.publish,
|
|
363
|
+
failOnPublishError: flags.failOnPublishError,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const output = process.env.GITHUB_OUTPUT;
|
|
367
|
+
if (output) {
|
|
368
|
+
fs.appendFileSync(output, `cid=${result.cid}\n`);
|
|
369
|
+
fs.appendFileSync(output, `domain=${result.domainName}\n`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
console.log(`CID: ${result.cid}`);
|
|
373
|
+
console.log(`Domain: ${result.domainName}`);
|
|
374
|
+
|
|
375
|
+
// Opt-in manifest publish on top of the legacy contenthash deploy.
|
|
376
|
+
//
|
|
377
|
+
// Walks up from <build-dir> (or honours --config) looking for
|
|
378
|
+
// bulletin-deploy.config.{ts,js,mjs}. When found, writes the root + per-
|
|
379
|
+
// executable text records, otherwise the legacy flow returns unchanged.
|
|
380
|
+
{
|
|
381
|
+
const { tryLoadProductConfig, publishManifest } = await import("../dist/index.js");
|
|
382
|
+
const path = await import("node:path");
|
|
383
|
+
const buildDirAbs = path.resolve(buildDir);
|
|
384
|
+
const loaded = await tryLoadProductConfig(
|
|
385
|
+
flags.config ? { path: flags.config } : { cwd: buildDirAbs, walkUp: true },
|
|
386
|
+
);
|
|
387
|
+
if (loaded) {
|
|
388
|
+
try {
|
|
389
|
+
await publishManifest({
|
|
390
|
+
loaded,
|
|
391
|
+
domain,
|
|
392
|
+
buildDirCid: { absPath: buildDirAbs, cid: result.cid },
|
|
393
|
+
env: flags.env,
|
|
394
|
+
rpc: flags.rpc,
|
|
395
|
+
mnemonic: flags.mnemonic,
|
|
396
|
+
derivationPath: flags.derivationPath,
|
|
397
|
+
});
|
|
398
|
+
} catch (err) {
|
|
399
|
+
console.error(`Manifest publish failed: ${err?.message ?? err}`);
|
|
400
|
+
process.exit(err instanceof NonRetryableError ? EXIT_CODE_NO_RETRY : 1);
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
const where = flags.config
|
|
404
|
+
? `--config ${flags.config}`
|
|
405
|
+
: `bulletin-deploy.config.{ts,js,mjs} via walking up from ${buildDirAbs}`;
|
|
406
|
+
console.log("");
|
|
407
|
+
console.log(`⚠ No bulletin-deploy.config.ts found (${where}).`);
|
|
408
|
+
console.log(` ${domain} was published as legacy contenthash only.`);
|
|
409
|
+
console.log(" Add a bulletin-deploy.config.ts to enable the product manifest:");
|
|
410
|
+
console.log(" • product icon, displayName, description on the base name");
|
|
411
|
+
console.log(" • per-modality subnames (app.<id>.dot, widget.<id>.dot, worker.<id>.dot)");
|
|
412
|
+
console.log(" • each modality’s archive CID on its subname contenthash");
|
|
413
|
+
console.log(" See: https://github.com/paritytech/triangle-js-sdks (Product Manifest RFC)");
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!flags.help && !flags.version) {
|
|
418
|
+
try { writeRunState({ status: "succeeded", endedAt: Date.now() }); } catch {}
|
|
419
|
+
}
|
|
420
|
+
process.exit(0);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
const noRetry = error instanceof NonRetryableError;
|
|
423
|
+
console.error(`Deployment failed${noRetry ? " (not retryable)" : ""}:`, error.message);
|
|
424
|
+
await handleFailedDeploy(error);
|
|
425
|
+
if (!flags.help && !flags.version) {
|
|
426
|
+
try { writeRunState({ status: "failed", endedAt: Date.now(), reason: (error?.message ?? String(error)).slice(0, 200) }); } catch {}
|
|
427
|
+
}
|
|
428
|
+
process.exit(noRetry ? EXIT_CODE_NO_RETRY : 1);
|
|
429
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface DeployContext {
|
|
2
|
+
domain?: string;
|
|
3
|
+
repo?: string;
|
|
4
|
+
branch?: string;
|
|
5
|
+
signerMode?: string;
|
|
6
|
+
chunkCount?: number;
|
|
7
|
+
totalSize?: string;
|
|
8
|
+
rpc?: string;
|
|
9
|
+
deployTag?: string;
|
|
10
|
+
cliFlags?: string;
|
|
11
|
+
ci?: {
|
|
12
|
+
runUrl?: string;
|
|
13
|
+
workflow?: string;
|
|
14
|
+
job?: string;
|
|
15
|
+
sha?: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
declare function setDeployContext(ctx: Partial<DeployContext>): void;
|
|
19
|
+
declare function installLogCapture(): void;
|
|
20
|
+
declare function getCapturedTail(): string;
|
|
21
|
+
declare function scrubSecrets(text: string): string;
|
|
22
|
+
declare function buildCliFlagsSummary(flags: Record<string, unknown>): string;
|
|
23
|
+
declare function buildReportBody(error: Error): string;
|
|
24
|
+
declare function buildTitle(error: Error): string;
|
|
25
|
+
declare function buildLabels(error: Error): string[];
|
|
26
|
+
declare function createGhIssue(title: string, body: string, labels: string[]): string;
|
|
27
|
+
declare function offerBugReport(error: Error): Promise<void>;
|
|
28
|
+
|
|
29
|
+
export { buildCliFlagsSummary, buildLabels, buildReportBody, buildTitle, createGhIssue, getCapturedTail, installLogCapture, offerBugReport, scrubSecrets, setDeployContext };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildCliFlagsSummary,
|
|
3
|
+
buildLabels,
|
|
4
|
+
buildReportBody,
|
|
5
|
+
buildTitle,
|
|
6
|
+
createGhIssue,
|
|
7
|
+
getCapturedTail,
|
|
8
|
+
installLogCapture,
|
|
9
|
+
offerBugReport,
|
|
10
|
+
scrubSecrets,
|
|
11
|
+
setDeployContext
|
|
12
|
+
} from "./chunk-ADNBLFDP.js";
|
|
13
|
+
import "./chunk-43HLT335.js";
|
|
14
|
+
import "./chunk-MFTODIIT.js";
|
|
15
|
+
import "./chunk-KJH2T5TQ.js";
|
|
16
|
+
export {
|
|
17
|
+
buildCliFlagsSummary,
|
|
18
|
+
buildLabels,
|
|
19
|
+
buildReportBody,
|
|
20
|
+
buildTitle,
|
|
21
|
+
createGhIssue,
|
|
22
|
+
getCapturedTail,
|
|
23
|
+
installLogCapture,
|
|
24
|
+
offerBugReport,
|
|
25
|
+
scrubSecrets,
|
|
26
|
+
setDeployContext
|
|
27
|
+
};
|