@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.
Files changed (109) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +233 -0
  3. package/assets/environments.json +313 -0
  4. package/bin/bulletin-bootstrap +84 -0
  5. package/bin/bulletin-deploy +429 -0
  6. package/dist/bug-report.d.ts +29 -0
  7. package/dist/bug-report.js +27 -0
  8. package/dist/chunk-2VAUMZB2.js +284 -0
  9. package/dist/chunk-43HLT335.js +232 -0
  10. package/dist/chunk-5VZQ2KSU.js +231 -0
  11. package/dist/chunk-ADNBLFDP.js +225 -0
  12. package/dist/chunk-BMAEWZYV.js +24 -0
  13. package/dist/chunk-C2TS5MER.js +64 -0
  14. package/dist/chunk-DNXH4QTI.js +2336 -0
  15. package/dist/chunk-FZWJV5AD.js +231 -0
  16. package/dist/chunk-GZD2UFLR.js +8 -0
  17. package/dist/chunk-HOTQDYHD.js +219 -0
  18. package/dist/chunk-IDYGYIMH.js +207 -0
  19. package/dist/chunk-KHVTYIIX.js +146 -0
  20. package/dist/chunk-KJH2T5TQ.js +172 -0
  21. package/dist/chunk-KOSF5FDO.js +49 -0
  22. package/dist/chunk-LZJMVPYW.js +156 -0
  23. package/dist/chunk-MFTODIIT.js +725 -0
  24. package/dist/chunk-MMAZFJDG.js +91 -0
  25. package/dist/chunk-NF2FL4ZO.js +164 -0
  26. package/dist/chunk-OITUIM2E.js +524 -0
  27. package/dist/chunk-P6CHOMN3.js +2368 -0
  28. package/dist/chunk-QMYW3D6E.js +316 -0
  29. package/dist/chunk-QTZNULSH.js +185 -0
  30. package/dist/chunk-RI3ZLNPN.js +71 -0
  31. package/dist/chunk-S7EM5VMW.js +108 -0
  32. package/dist/chunk-T7EEVWNU.js +32 -0
  33. package/dist/chunk-UPWEOGLQ.js +37 -0
  34. package/dist/chunk-ZOC4GITL.js +13 -0
  35. package/dist/chunk-ZYVGHDMU.js +117 -0
  36. package/dist/chunk-probe.d.ts +37 -0
  37. package/dist/chunk-probe.js +18 -0
  38. package/dist/chunker.d.ts +8 -0
  39. package/dist/chunker.js +10 -0
  40. package/dist/deploy.d.ts +299 -0
  41. package/dist/deploy.js +96 -0
  42. package/dist/dotns.d.ts +506 -0
  43. package/dist/dotns.js +101 -0
  44. package/dist/environments.d.ts +104 -0
  45. package/dist/environments.js +23 -0
  46. package/dist/errors.d.ts +6 -0
  47. package/dist/errors.js +8 -0
  48. package/dist/gh-pages-mirror.d.ts +76 -0
  49. package/dist/gh-pages-mirror.js +30 -0
  50. package/dist/incremental-stats.d.ts +69 -0
  51. package/dist/incremental-stats.js +10 -0
  52. package/dist/index.d.ts +23 -0
  53. package/dist/index.js +146 -0
  54. package/dist/manifest/byte-budget.d.ts +46 -0
  55. package/dist/manifest/byte-budget.js +14 -0
  56. package/dist/manifest/config-load.d.ts +36 -0
  57. package/dist/manifest/config-load.js +10 -0
  58. package/dist/manifest/publish.d.ts +54 -0
  59. package/dist/manifest/publish.js +23 -0
  60. package/dist/manifest/schema.d.ts +29 -0
  61. package/dist/manifest/schema.js +10 -0
  62. package/dist/manifest/types.d.ts +90 -0
  63. package/dist/manifest/types.js +6 -0
  64. package/dist/manifest-embed.d.ts +18 -0
  65. package/dist/manifest-embed.js +9 -0
  66. package/dist/manifest-fetch.d.ts +32 -0
  67. package/dist/manifest-fetch.js +21 -0
  68. package/dist/manifest-roundtrip.d.ts +15 -0
  69. package/dist/manifest-roundtrip.js +55 -0
  70. package/dist/manifest.d.ts +44 -0
  71. package/dist/manifest.js +20 -0
  72. package/dist/memory-report.d.ts +95 -0
  73. package/dist/memory-report.js +17 -0
  74. package/dist/merkle.d.ts +50 -0
  75. package/dist/merkle.js +33 -0
  76. package/dist/personhood/bind-paid-alias.d.ts +43 -0
  77. package/dist/personhood/bind-paid-alias.js +10 -0
  78. package/dist/personhood/bind-personal-id.d.ts +55 -0
  79. package/dist/personhood/bind-personal-id.js +12 -0
  80. package/dist/personhood/bootstrap.d.ts +85 -0
  81. package/dist/personhood/bootstrap.js +245 -0
  82. package/dist/personhood/claim-pgas.d.ts +61 -0
  83. package/dist/personhood/claim-pgas.js +12 -0
  84. package/dist/personhood/constants.d.ts +23 -0
  85. package/dist/personhood/constants.js +22 -0
  86. package/dist/personhood/encoding.d.ts +49 -0
  87. package/dist/personhood/encoding.js +24 -0
  88. package/dist/personhood/hashing.d.ts +4 -0
  89. package/dist/personhood/hashing.js +8 -0
  90. package/dist/personhood/member-key.d.ts +12 -0
  91. package/dist/personhood/member-key.js +10 -0
  92. package/dist/personhood/people-client.d.ts +14 -0
  93. package/dist/personhood/people-client.js +48 -0
  94. package/dist/personhood/reprove.d.ts +43 -0
  95. package/dist/personhood/reprove.js +225 -0
  96. package/dist/pool.d.ts +51 -0
  97. package/dist/pool.js +30 -0
  98. package/dist/run-state.d.ts +22 -0
  99. package/dist/run-state.js +20 -0
  100. package/dist/telemetry.d.ts +56 -0
  101. package/dist/telemetry.js +71 -0
  102. package/dist/version-check.d.ts +38 -0
  103. package/dist/version-check.js +30 -0
  104. package/docs/bootstrap.md +49 -0
  105. package/docs/e2e-bootstrap.md +154 -0
  106. package/docs/telemetry.md +62 -0
  107. package/docs/testing.md +44 -0
  108. package/package.json +82 -0
  109. 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
+ };