bulletin-deploy 0.6.16 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -20
- package/bin/bulletin-deploy +19 -5
- package/dist/bug-report.d.ts +14 -3
- package/dist/bug-report.js +17 -111
- package/dist/{chunk-UZVOH3HB.js → chunk-2Q2WSKFD.js} +33 -3
- package/dist/chunk-5N4VL73E.js +223 -0
- package/dist/{chunk-QILGABSF.js → chunk-B7GUYYAN.js} +22 -14
- package/dist/{chunk-K7TA53E2.js → chunk-BVTSCGRO.js} +130 -365
- package/dist/{chunk-BGLOVKHX.js → chunk-DYKCMHZ6.js} +1 -1
- package/dist/{chunk-3C7PWPPG.js → chunk-MORJYP3A.js} +4 -4
- package/dist/{chunk-LF3XAUCI.js → chunk-PH43YXEE.js} +225 -5
- package/dist/deploy.d.ts +17 -3
- package/dist/deploy.js +9 -5
- package/dist/dotns.js +2 -2
- package/dist/gh-pages-mirror.d.ts +25 -1
- package/dist/gh-pages-mirror.js +3 -1
- package/dist/index.js +7 -5
- package/dist/memory-report.d.ts +95 -0
- package/dist/memory-report.js +17 -0
- package/dist/merkle.js +1 -1
- package/dist/telemetry.d.ts +9 -1
- package/dist/telemetry.js +7 -1
- package/dist/version-check.js +2 -2
- package/package.json +3 -5
- package/cdm.json +0 -248
package/README.md
CHANGED
|
@@ -47,9 +47,6 @@ bulletin-deploy ./dist my-app00.dot
|
|
|
47
47
|
|
|
48
48
|
# Custom RPC endpoint
|
|
49
49
|
bulletin-deploy --rpc wss://custom-bulletin.example.com ./dist my-app00.dot
|
|
50
|
-
|
|
51
|
-
# Deploy and publish to the Playground remix registry
|
|
52
|
-
bulletin-deploy --playground ./dist my-app00.dot
|
|
53
50
|
```
|
|
54
51
|
|
|
55
52
|
### All options
|
|
@@ -63,7 +60,6 @@ Options:
|
|
|
63
60
|
Bulletin direct signer and the DotNS registration signer then
|
|
64
61
|
run as that derived account. Useful when running parallel
|
|
65
62
|
deploys against the same root mnemonic without nonce contention.
|
|
66
|
-
--playground Publish to the Playground remix registry
|
|
67
63
|
--js-merkle Use pure-JS merkleization (no IPFS Kubo binary required)
|
|
68
64
|
--pool-size N Number of pool accounts (default: 10)
|
|
69
65
|
--tag "..." Free-form label attached to the deploy span as deploy.tag
|
|
@@ -111,18 +107,6 @@ Limitations / follow-ups:
|
|
|
111
107
|
- **Mirror failures are non-fatal.** The source of truth is Bulletin + DotNS; the mirror is a cache. Failures log and let the deploy succeed.
|
|
112
108
|
- **GitHub Pages build latency.** The CAR lands on `gh-pages` immediately; Pages serves it after the build completes (~1–2 min in practice). Hosts should fall back to Bulletin while the 404 window lasts.
|
|
113
109
|
|
|
114
|
-
### Playground registry
|
|
115
|
-
|
|
116
|
-
By default, deploys only upload to Bulletin storage and register the DotNS domain. The **Playground remix registry** is an on-chain app directory that makes your deploy visible in [Polkadot Playground](https://playground.polkadot.cloud).
|
|
117
|
-
|
|
118
|
-
To publish to it, pass `--playground`:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
bulletin-deploy --playground ./dist my-app.dot
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
This requires `cdm.json` in your project root (shipped with bulletin-deploy) and a git remote origin.
|
|
125
|
-
|
|
126
110
|
## GitHub Actions
|
|
127
111
|
|
|
128
112
|
1. Copy `workflows/deploy-on-pr.yml` to your repo's `.github/workflows/` directory
|
|
@@ -275,6 +259,7 @@ Sentry telemetry is **off by default for external users**. It's automatically on
|
|
|
275
259
|
|
|
276
260
|
- `BULLETIN_DEPLOY_TELEMETRY=1` — explicit opt-in (useful if you want to help us debug an issue you're seeing).
|
|
277
261
|
- `BULLETIN_DEPLOY_TELEMETRY=0` — force off regardless of context.
|
|
262
|
+
- When running under **Bun**, the Parity-internal memory-report diagnostic bundle is skipped (basic deploy telemetry still works). The bundle relies on Node's `v8` module, which Bun implements only partially.
|
|
278
263
|
|
|
279
264
|
Detection signals (OR'd together):
|
|
280
265
|
1. `GITHUB_REPOSITORY` matches a known-internal org — Parity-owned CI workflow.
|
|
@@ -331,20 +316,21 @@ Runs `test/test.js` + `test/pool.test.js` + `test/helpers/e2e-helpers.test.js` v
|
|
|
331
316
|
|
|
332
317
|
### Live-testnet E2E
|
|
333
318
|
|
|
334
|
-
|
|
319
|
+
Four scenarios land on Paseo Bulletin:
|
|
335
320
|
|
|
336
321
|
- **S1** — happy path on a stable label (`e2epool.dot` / `e2edirect.dot`)
|
|
337
322
|
- **S2** — fresh registration via commit-reveal (nightly only)
|
|
338
323
|
- **S3** — deploy to `e2eowned.dot` (owned by a different account), expects `EXIT_CODE_NO_RETRY` (78) and the "owned by a different account" error message
|
|
324
|
+
- **S4** — deploy with `--gh-pages-mirror`, waits for GitHub Pages to serve the just-pushed manifest (CID-freshness check), then byte-compares the CAR on Pages against a pre-upload dump to confirm the mirror is an exact copy of what went to Bulletin
|
|
339
325
|
|
|
340
|
-
**Prerequisites** (one-time per testnet lifetime): see [`docs/e2e-bootstrap.md`](docs/e2e-bootstrap.md). Grants Alice PoP Full, funds+maps Bob, pre-registers `e2eowned.dot` to Bob via `dotns-cli`.
|
|
326
|
+
**Prerequisites** (one-time per testnet lifetime): see [`docs/e2e-bootstrap.md`](docs/e2e-bootstrap.md). Grants Alice PoP Full, funds+maps Bob, pre-registers `e2eowned.dot` to Bob via `dotns-cli`. S4 additionally needs GitHub Pages enabled on the repo with `gh-pages` as the source branch and a token with `contents: write` (the workflow's `GITHUB_TOKEN` provides this; locally your git credentials must be able to push).
|
|
341
327
|
|
|
342
328
|
**Local launchers:**
|
|
343
329
|
|
|
344
330
|
```bash
|
|
345
331
|
npm run test:e2e:smoke # 1 scenario (S1 pool/js) ~5 min
|
|
346
|
-
npm run test:e2e:pr #
|
|
347
|
-
npm run test:e2e:nightly #
|
|
332
|
+
npm run test:e2e:pr # 4 scenarios (matches per-PR CI) ~20 min
|
|
333
|
+
npm run test:e2e:nightly # 8 scenarios (matches nightly CI) ~30–45 min
|
|
348
334
|
```
|
|
349
335
|
|
|
350
336
|
All three run through to completion even if one fails; a colored summary prints at the end with per-scenario pass/fail, timing, JUnit report paths, and a pre-filtered Sentry trace link.
|
package/bin/bulletin-deploy
CHANGED
|
@@ -4,9 +4,13 @@ import { deploy, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, NonRetryableError, EXI
|
|
|
4
4
|
import { bootstrapPool } from "../dist/pool.js";
|
|
5
5
|
import { VERSION } from "../dist/telemetry.js";
|
|
6
6
|
import { handleFailedDeploy, preReleaseWarning } from "../dist/version-check.js";
|
|
7
|
-
import { setDeployContext } from "../dist/bug-report.js";
|
|
7
|
+
import { setDeployContext, installLogCapture, buildCliFlagsSummary } from "../dist/bug-report.js";
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
|
|
10
|
+
// Install early so anything printed during flag parsing / preflight is
|
|
11
|
+
// available to the bug-report log tail.
|
|
12
|
+
installLogCapture();
|
|
13
|
+
|
|
10
14
|
const args = process.argv.slice(2);
|
|
11
15
|
|
|
12
16
|
const flags = {};
|
|
@@ -18,7 +22,6 @@ for (let i = 0; i < args.length; i++) {
|
|
|
18
22
|
else if (args[i] === "--derivation-path") { flags.derivationPath = args[++i]; }
|
|
19
23
|
else if (args[i] === "--rpc") { flags.rpc = args[++i]; }
|
|
20
24
|
else if (args[i] === "--password") { flags.password = args[++i]; }
|
|
21
|
-
else if (args[i] === "--playground") { flags.playground = true; }
|
|
22
25
|
else if (args[i] === "--js-merkle") { flags.jsMerkle = true; }
|
|
23
26
|
else if (args[i] === "--tag") { flags.tag = args[++i]; }
|
|
24
27
|
else if (args[i] === "--gh-pages-mirror") { flags.ghPagesMirror = true; }
|
|
@@ -45,7 +48,6 @@ Options:
|
|
|
45
48
|
--rpc wss://... Bulletin RPC (or set BULLETIN_RPC env var)
|
|
46
49
|
--pool-size N Number of pool accounts (default: 10)
|
|
47
50
|
--password "..." Encrypt SPA content (users will be prompted to decrypt)
|
|
48
|
-
--playground Publish to the playground remix registry
|
|
49
51
|
--js-merkle Use pure-JS merkleization (no IPFS Kubo binary required)
|
|
50
52
|
--tag "..." Label deploy in telemetry (or set DEPLOY_TAG env var); see Telemetry in README
|
|
51
53
|
--gh-pages-mirror After deploy, push the CAR to the current repo's gh-pages branch
|
|
@@ -69,16 +71,28 @@ try {
|
|
|
69
71
|
if (!domain) { console.error("Error: domain required (e.g. my-app.dot)"); process.exit(1); }
|
|
70
72
|
if (!fs.existsSync(buildDir)) { console.error(`Error: ${buildDir} does not exist`); process.exit(1); }
|
|
71
73
|
|
|
74
|
+
const effectiveRpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
|
|
75
|
+
const deployTag = flags.tag ?? process.env.DEPLOY_TAG;
|
|
76
|
+
const ci = process.env.GITHUB_ACTIONS === "true" ? {
|
|
77
|
+
runUrl: process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
|
|
78
|
+
? `${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}` : ""}`
|
|
79
|
+
: undefined,
|
|
80
|
+
workflow: process.env.GITHUB_WORKFLOW,
|
|
81
|
+
job: process.env.GITHUB_JOB,
|
|
82
|
+
sha: process.env.GITHUB_SHA,
|
|
83
|
+
} : undefined;
|
|
72
84
|
setDeployContext({
|
|
73
85
|
domain,
|
|
74
|
-
rpc:
|
|
86
|
+
rpc: effectiveRpc,
|
|
75
87
|
repo: process.env.GITHUB_REPOSITORY,
|
|
76
88
|
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME,
|
|
77
89
|
signerMode: flags.mnemonic ? "direct" : "pool",
|
|
90
|
+
deployTag,
|
|
91
|
+
cliFlags: buildCliFlagsSummary(flags),
|
|
92
|
+
ci,
|
|
78
93
|
});
|
|
79
94
|
|
|
80
95
|
const result = await deploy(buildDir, domain, {
|
|
81
|
-
playground: flags.playground,
|
|
82
96
|
mnemonic: flags.mnemonic,
|
|
83
97
|
derivationPath: flags.derivationPath,
|
|
84
98
|
rpc: flags.rpc,
|
package/dist/bug-report.d.ts
CHANGED
|
@@ -6,13 +6,24 @@ interface DeployContext {
|
|
|
6
6
|
chunkCount?: number;
|
|
7
7
|
totalSize?: string;
|
|
8
8
|
rpc?: string;
|
|
9
|
-
|
|
9
|
+
deployTag?: string;
|
|
10
|
+
cliFlags?: string;
|
|
11
|
+
ci?: {
|
|
12
|
+
runUrl?: string;
|
|
13
|
+
workflow?: string;
|
|
14
|
+
job?: string;
|
|
15
|
+
sha?: string;
|
|
16
|
+
};
|
|
10
17
|
}
|
|
11
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;
|
|
12
23
|
declare function buildReportBody(error: Error): string;
|
|
13
24
|
declare function buildTitle(error: Error): string;
|
|
14
25
|
declare function buildLabels(error: Error): string[];
|
|
15
|
-
declare function createGhIssue(title: string, body: string, labels: string[]):
|
|
26
|
+
declare function createGhIssue(title: string, body: string, labels: string[]): string;
|
|
16
27
|
declare function offerBugReport(error: Error): Promise<void>;
|
|
17
28
|
|
|
18
|
-
export { buildLabels, buildReportBody, buildTitle, createGhIssue, offerBugReport, setDeployContext };
|
|
29
|
+
export { buildCliFlagsSummary, buildLabels, buildReportBody, buildTitle, createGhIssue, getCapturedTail, installLogCapture, offerBugReport, scrubSecrets, setDeployContext };
|
package/dist/bug-report.js
CHANGED
|
@@ -1,121 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
buildCliFlagsSummary,
|
|
3
|
+
buildLabels,
|
|
4
|
+
buildReportBody,
|
|
5
|
+
buildTitle,
|
|
6
|
+
createGhIssue,
|
|
7
|
+
getCapturedTail,
|
|
8
|
+
installLogCapture,
|
|
9
|
+
offerBugReport,
|
|
10
|
+
scrubSecrets,
|
|
11
|
+
setDeployContext
|
|
12
|
+
} from "./chunk-5N4VL73E.js";
|
|
13
|
+
import "./chunk-DYKCMHZ6.js";
|
|
14
|
+
import "./chunk-PH43YXEE.js";
|
|
9
15
|
import "./chunk-QGM4M3NI.js";
|
|
10
|
-
|
|
11
|
-
// src/bug-report.ts
|
|
12
|
-
import { execSync, execFileSync } from "child_process";
|
|
13
|
-
import * as os from "os";
|
|
14
|
-
var _deployContext = {};
|
|
15
|
-
function setDeployContext(ctx) {
|
|
16
|
-
_deployContext = { ..._deployContext, ...ctx };
|
|
17
|
-
}
|
|
18
|
-
function hasGhCli() {
|
|
19
|
-
try {
|
|
20
|
-
execSync("gh --version", { stdio: "pipe" });
|
|
21
|
-
return true;
|
|
22
|
-
} catch {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function buildReportBody(error) {
|
|
27
|
-
const lines = [
|
|
28
|
-
"## Environment",
|
|
29
|
-
"",
|
|
30
|
-
`- **bulletin-deploy**: ${VERSION}`,
|
|
31
|
-
`- **Node.js**: ${process.version}`,
|
|
32
|
-
`- **OS**: ${os.platform()} ${os.arch()} ${os.release()}`,
|
|
33
|
-
"",
|
|
34
|
-
"## Error",
|
|
35
|
-
"",
|
|
36
|
-
"```",
|
|
37
|
-
error.stack || error.message,
|
|
38
|
-
"```",
|
|
39
|
-
""
|
|
40
|
-
];
|
|
41
|
-
const ctx = _deployContext;
|
|
42
|
-
if (ctx.domain || ctx.repo || ctx.rpc) {
|
|
43
|
-
lines.push("## Deploy Context", "");
|
|
44
|
-
if (ctx.domain) lines.push(`- **Domain**: ${ctx.domain}`);
|
|
45
|
-
if (ctx.repo) lines.push(`- **Repo**: ${ctx.repo}`);
|
|
46
|
-
if (ctx.branch) lines.push(`- **Branch**: ${ctx.branch}`);
|
|
47
|
-
if (ctx.signerMode) lines.push(`- **Signer mode**: ${ctx.signerMode}`);
|
|
48
|
-
if (ctx.chunkCount != null) lines.push(`- **Chunks**: ${ctx.chunkCount}`);
|
|
49
|
-
if (ctx.totalSize) lines.push(`- **Total size**: ${ctx.totalSize}`);
|
|
50
|
-
if (ctx.rpc) lines.push(`- **RPC**: ${ctx.rpc}`);
|
|
51
|
-
if (ctx.sentryTraceId) lines.push(`- **Sentry trace**: ${ctx.sentryTraceId}`);
|
|
52
|
-
lines.push("");
|
|
53
|
-
}
|
|
54
|
-
return lines.join("\n");
|
|
55
|
-
}
|
|
56
|
-
function buildTitle(error) {
|
|
57
|
-
const msg = error.message.slice(0, 60);
|
|
58
|
-
return `[deploy-bug] ${msg}`;
|
|
59
|
-
}
|
|
60
|
-
function buildLabels(error) {
|
|
61
|
-
const labels = ["bug", "auto-report"];
|
|
62
|
-
const area = classifyErrorArea(error.message);
|
|
63
|
-
if (area) labels.push(area);
|
|
64
|
-
return labels;
|
|
65
|
-
}
|
|
66
|
-
function createGhIssue(title, body, labels) {
|
|
67
|
-
const args = [
|
|
68
|
-
"issue",
|
|
69
|
-
"create",
|
|
70
|
-
"--repo",
|
|
71
|
-
"paritytech/bulletin-deploy",
|
|
72
|
-
"--title",
|
|
73
|
-
title,
|
|
74
|
-
...labels.flatMap((l) => ["--label", l]),
|
|
75
|
-
"--body-file",
|
|
76
|
-
"-"
|
|
77
|
-
];
|
|
78
|
-
execFileSync("gh", args, { input: body, stdio: ["pipe", "inherit", "inherit"] });
|
|
79
|
-
}
|
|
80
|
-
async function offerBugReport(error) {
|
|
81
|
-
if (!isInteractive()) return;
|
|
82
|
-
const yes = await promptYesNo("\n This looks like a bug. Open an issue with debug info? [Y/n] ");
|
|
83
|
-
if (!yes) return;
|
|
84
|
-
const title = buildTitle(error);
|
|
85
|
-
const body = buildReportBody(error);
|
|
86
|
-
const labels = buildLabels(error);
|
|
87
|
-
if (hasGhCli()) {
|
|
88
|
-
try {
|
|
89
|
-
createGhIssue(title, body, labels);
|
|
90
|
-
console.error(" Issue created.");
|
|
91
|
-
} catch {
|
|
92
|
-
try {
|
|
93
|
-
console.error(" Retrying without labels...");
|
|
94
|
-
createGhIssue(title, body, []);
|
|
95
|
-
console.error(" Issue created (without labels).");
|
|
96
|
-
} catch {
|
|
97
|
-
console.error(" Failed to create issue. Debug info below:\n");
|
|
98
|
-
printFallback(title, body, labels);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
console.error("\n gh CLI not found. Debug info below \u2014 paste into a new issue:\n");
|
|
103
|
-
console.error(` https://github.com/paritytech/bulletin-deploy/issues/new
|
|
104
|
-
`);
|
|
105
|
-
printFallback(title, body, labels);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function printFallback(title, body, labels) {
|
|
109
|
-
console.error(` Title: ${title}`);
|
|
110
|
-
console.error(` Labels: ${labels.join(", ")}
|
|
111
|
-
`);
|
|
112
|
-
console.error(body);
|
|
113
|
-
}
|
|
114
16
|
export {
|
|
17
|
+
buildCliFlagsSummary,
|
|
115
18
|
buildLabels,
|
|
116
19
|
buildReportBody,
|
|
117
20
|
buildTitle,
|
|
118
21
|
createGhIssue,
|
|
22
|
+
getCapturedTail,
|
|
23
|
+
installLogCapture,
|
|
119
24
|
offerBugReport,
|
|
25
|
+
scrubSecrets,
|
|
120
26
|
setDeployContext
|
|
121
27
|
};
|
|
@@ -12,6 +12,35 @@ var MirrorSkipped = class extends Error {
|
|
|
12
12
|
this.name = "MirrorSkipped";
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
+
async function pollMirrorFreshness(mirrorUrl2, expectedCid, opts = {}) {
|
|
16
|
+
const timeoutMs = opts.timeoutMs ?? 5 * 60 * 1e3;
|
|
17
|
+
const intervalMs = opts.intervalMs ?? 1e4;
|
|
18
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
19
|
+
const manifestUrl = mirrorUrl2.replace(/\.car$/, ".json");
|
|
20
|
+
const started = Date.now();
|
|
21
|
+
const deadline = started + timeoutMs;
|
|
22
|
+
let attempts = 0;
|
|
23
|
+
let lastCid = null;
|
|
24
|
+
let lastStatus = 0;
|
|
25
|
+
while (Date.now() < deadline) {
|
|
26
|
+
attempts++;
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetchFn(manifestUrl, { redirect: "follow", cache: "no-store" });
|
|
29
|
+
lastStatus = res.status;
|
|
30
|
+
if (res.status === 200) {
|
|
31
|
+
const m = await res.json();
|
|
32
|
+
if (m.cid === expectedCid) {
|
|
33
|
+
return { verified: true, attempts, durationMs: Date.now() - started, lastCid: m.cid, lastStatus };
|
|
34
|
+
}
|
|
35
|
+
lastCid = m.cid ?? null;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
if (Date.now() + intervalMs >= deadline) break;
|
|
40
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
41
|
+
}
|
|
42
|
+
return { verified: false, attempts, durationMs: Date.now() - started, lastCid, lastStatus };
|
|
43
|
+
}
|
|
15
44
|
function parseGitRemoteUrl(url) {
|
|
16
45
|
const trimmed = url.trim();
|
|
17
46
|
const ssh = trimmed.match(/^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
@@ -91,7 +120,7 @@ async function mirrorToGitHubPages(input) {
|
|
|
91
120
|
}
|
|
92
121
|
if (input.carBytes.length > GH_PAGES_MIRROR_MAX_BYTES) {
|
|
93
122
|
const mb = (input.carBytes.length / 1024 / 1024).toFixed(1);
|
|
94
|
-
throw new MirrorSkipped(`CAR is ${mb} MB
|
|
123
|
+
throw new MirrorSkipped(`CAR is ${mb} MB, exceeds GitHub's 100 MB single-file soft limit. Pages can't host this CAR \u2014 the on-chain deploy still succeeds and hosts will fall back to Bulletin.`);
|
|
95
124
|
}
|
|
96
125
|
const domainFilename = normalizeDomainFilename(input.domain);
|
|
97
126
|
const { owner, repo } = ownerRepo;
|
|
@@ -112,8 +141,8 @@ async function mirrorToGitHubPages(input) {
|
|
|
112
141
|
branchExists = false;
|
|
113
142
|
}
|
|
114
143
|
if (branchExists) {
|
|
115
|
-
runGit(["fetch", "origin",
|
|
116
|
-
runGit(["worktree", "add", workTree, GH_PAGES_MIRROR_BRANCH], repoPath);
|
|
144
|
+
runGit(["fetch", "origin", GH_PAGES_MIRROR_BRANCH, "--depth=1"], repoPath);
|
|
145
|
+
runGit(["worktree", "add", "--detach", workTree, `origin/${GH_PAGES_MIRROR_BRANCH}`], repoPath);
|
|
117
146
|
} else {
|
|
118
147
|
runGit(["worktree", "add", "--detach", workTree, "HEAD"], repoPath);
|
|
119
148
|
runGit(["checkout", "--orphan", GH_PAGES_MIRROR_BRANCH], workTree);
|
|
@@ -173,6 +202,7 @@ export {
|
|
|
173
202
|
GH_PAGES_MIRROR_DIR,
|
|
174
203
|
GH_PAGES_MIRROR_BRANCH,
|
|
175
204
|
MirrorSkipped,
|
|
205
|
+
pollMirrorFreshness,
|
|
176
206
|
parseGitRemoteUrl,
|
|
177
207
|
resolveOwnerRepo,
|
|
178
208
|
resolveSourceCommit,
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyErrorArea,
|
|
3
|
+
isInteractive,
|
|
4
|
+
promptYesNo
|
|
5
|
+
} from "./chunk-DYKCMHZ6.js";
|
|
6
|
+
import {
|
|
7
|
+
VERSION,
|
|
8
|
+
getCurrentSentryTraceId
|
|
9
|
+
} from "./chunk-PH43YXEE.js";
|
|
10
|
+
|
|
11
|
+
// src/bug-report.ts
|
|
12
|
+
import { execSync, execFileSync } from "child_process";
|
|
13
|
+
import * as os from "os";
|
|
14
|
+
var _deployContext = {};
|
|
15
|
+
function setDeployContext(ctx) {
|
|
16
|
+
_deployContext = { ..._deployContext, ...ctx };
|
|
17
|
+
}
|
|
18
|
+
function hasGhCli() {
|
|
19
|
+
try {
|
|
20
|
+
execSync("gh --version", { stdio: "pipe" });
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var LOG_TAIL_BYTES = 32 * 1024;
|
|
27
|
+
var _logBuffer = "";
|
|
28
|
+
var _logCaptureInstalled = false;
|
|
29
|
+
function installLogCapture() {
|
|
30
|
+
if (_logCaptureInstalled) return;
|
|
31
|
+
_logCaptureInstalled = true;
|
|
32
|
+
const append = (args) => {
|
|
33
|
+
const line = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ") + "\n";
|
|
34
|
+
_logBuffer += line;
|
|
35
|
+
if (_logBuffer.length > LOG_TAIL_BYTES * 2) {
|
|
36
|
+
_logBuffer = _logBuffer.slice(_logBuffer.length - LOG_TAIL_BYTES);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const wrap = (key) => {
|
|
40
|
+
const orig = console[key].bind(console);
|
|
41
|
+
console[key] = (...a) => {
|
|
42
|
+
append(a);
|
|
43
|
+
orig(...a);
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
wrap("log");
|
|
47
|
+
wrap("error");
|
|
48
|
+
wrap("warn");
|
|
49
|
+
}
|
|
50
|
+
function safeStringify(v) {
|
|
51
|
+
try {
|
|
52
|
+
if (v instanceof Error) return v.stack || v.message;
|
|
53
|
+
return JSON.stringify(v);
|
|
54
|
+
} catch {
|
|
55
|
+
return String(v);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function getCapturedTail() {
|
|
59
|
+
if (!_logBuffer) return "";
|
|
60
|
+
const tail = _logBuffer.length > LOG_TAIL_BYTES ? "\u2026 [truncated]\n" + _logBuffer.slice(_logBuffer.length - LOG_TAIL_BYTES) : _logBuffer;
|
|
61
|
+
return scrubSecrets(tail);
|
|
62
|
+
}
|
|
63
|
+
function scrubSecrets(text) {
|
|
64
|
+
if (!text) return text;
|
|
65
|
+
let out = text;
|
|
66
|
+
out = out.replace(/(--mnemonic(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi, "$1<REDACTED>");
|
|
67
|
+
out = out.replace(/(--password(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi, "$1<REDACTED>");
|
|
68
|
+
out = out.replace(/\b(MNEMONIC|PASSWORD|BULLETIN_MNEMONIC|GITHUB_TOKEN|GH_TOKEN|NPM_TOKEN|SENTRY_AUTH_TOKEN)=([^\s]+)/gi, "$1=<REDACTED>");
|
|
69
|
+
out = out.replace(/\b(ghp|ghs|gho|ghu|ghr)_[A-Za-z0-9]{20,}\b/g, "<REDACTED_TOKEN>");
|
|
70
|
+
out = out.replace(/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, "<REDACTED_TOKEN>");
|
|
71
|
+
out = out.replace(/\b(?:[a-z]{3,10}\s+){11}[a-z]{3,10}\b/g, "<REDACTED_MNEMONIC>");
|
|
72
|
+
out = out.replace(/([a-z][a-z0-9+.-]*:\/\/)[^:@\s]+:[^@\s]+@/gi, "$1<REDACTED>@");
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function buildCliFlagsSummary(flags) {
|
|
76
|
+
const parts = [];
|
|
77
|
+
if (flags.bootstrap) parts.push("--bootstrap");
|
|
78
|
+
if (flags.jsMerkle) parts.push("--js-merkle");
|
|
79
|
+
if (flags.ghPagesMirror) parts.push("--gh-pages-mirror");
|
|
80
|
+
if (flags.poolSize != null) parts.push(`--pool-size ${String(flags.poolSize)}`);
|
|
81
|
+
if (typeof flags.tag === "string" && flags.tag) parts.push(`--tag ${flags.tag}`);
|
|
82
|
+
if (flags.mnemonic) parts.push("--mnemonic <set>");
|
|
83
|
+
if (flags.password) parts.push("--password <set>");
|
|
84
|
+
if (flags.derivationPath) parts.push("--derivation-path <set>");
|
|
85
|
+
if (typeof flags.rpc === "string" && flags.rpc) parts.push("--rpc <set>");
|
|
86
|
+
return parts.join(" ");
|
|
87
|
+
}
|
|
88
|
+
function buildReportBody(error) {
|
|
89
|
+
const lines = [
|
|
90
|
+
"## Environment",
|
|
91
|
+
"",
|
|
92
|
+
`- **bulletin-deploy**: ${VERSION}`,
|
|
93
|
+
`- **Node.js**: ${process.version}`,
|
|
94
|
+
`- **OS**: ${os.platform()} ${os.arch()} ${os.release()}`,
|
|
95
|
+
"",
|
|
96
|
+
"## Error",
|
|
97
|
+
"",
|
|
98
|
+
"```",
|
|
99
|
+
scrubSecrets(error.stack || error.message),
|
|
100
|
+
"```",
|
|
101
|
+
""
|
|
102
|
+
];
|
|
103
|
+
const ctx = _deployContext;
|
|
104
|
+
const traceId = getCurrentSentryTraceId();
|
|
105
|
+
const hasCtx = ctx.domain || ctx.repo || ctx.rpc || ctx.cliFlags || ctx.ci?.runUrl || traceId;
|
|
106
|
+
if (hasCtx) {
|
|
107
|
+
lines.push("## Deploy Context", "");
|
|
108
|
+
if (ctx.domain) lines.push(`- **Domain**: ${ctx.domain}`);
|
|
109
|
+
if (ctx.repo) lines.push(`- **Repo**: ${ctx.repo}`);
|
|
110
|
+
if (ctx.branch) lines.push(`- **Branch**: ${ctx.branch}`);
|
|
111
|
+
if (ctx.signerMode) lines.push(`- **Signer mode**: ${ctx.signerMode}`);
|
|
112
|
+
if (ctx.chunkCount != null) lines.push(`- **Chunks**: ${ctx.chunkCount}`);
|
|
113
|
+
if (ctx.totalSize) lines.push(`- **Total size**: ${ctx.totalSize}`);
|
|
114
|
+
if (ctx.rpc) lines.push(`- **RPC**: ${ctx.rpc}`);
|
|
115
|
+
if (ctx.deployTag) lines.push(`- **Deploy tag**: ${ctx.deployTag}`);
|
|
116
|
+
if (ctx.cliFlags) lines.push(`- **CLI flags**: \`${ctx.cliFlags}\``);
|
|
117
|
+
if (traceId) lines.push(`- **Sentry trace**: ${traceId}`);
|
|
118
|
+
lines.push("");
|
|
119
|
+
}
|
|
120
|
+
if (ctx.ci?.runUrl) {
|
|
121
|
+
lines.push("## CI", "");
|
|
122
|
+
lines.push(`- **Run**: ${ctx.ci.runUrl}`);
|
|
123
|
+
if (ctx.ci.workflow) lines.push(`- **Workflow**: ${ctx.ci.workflow}`);
|
|
124
|
+
if (ctx.ci.job) lines.push(`- **Job**: ${ctx.ci.job}`);
|
|
125
|
+
if (ctx.ci.sha) lines.push(`- **SHA**: ${ctx.ci.sha}`);
|
|
126
|
+
lines.push("");
|
|
127
|
+
}
|
|
128
|
+
const tail = getCapturedTail();
|
|
129
|
+
if (tail) {
|
|
130
|
+
lines.push("## Log tail", "", "<details><summary>Last ~32 KB of stdout/stderr (secrets scrubbed)</summary>", "", "```", tail.trimEnd(), "```", "", "</details>", "");
|
|
131
|
+
}
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
function buildTitle(error) {
|
|
135
|
+
const msg = error.message.slice(0, 60);
|
|
136
|
+
return `[deploy-bug] ${msg}`;
|
|
137
|
+
}
|
|
138
|
+
function buildLabels(error) {
|
|
139
|
+
const labels = ["bug", "auto-report"];
|
|
140
|
+
const area = classifyErrorArea(error.message);
|
|
141
|
+
if (area) labels.push(area);
|
|
142
|
+
return labels;
|
|
143
|
+
}
|
|
144
|
+
function createGhIssue(title, body, labels) {
|
|
145
|
+
const args = [
|
|
146
|
+
"issue",
|
|
147
|
+
"create",
|
|
148
|
+
"--repo",
|
|
149
|
+
"paritytech/bulletin-deploy",
|
|
150
|
+
"--title",
|
|
151
|
+
title,
|
|
152
|
+
...labels.flatMap((l) => ["--label", l]),
|
|
153
|
+
"--body-file",
|
|
154
|
+
"-"
|
|
155
|
+
];
|
|
156
|
+
const out = execFileSync("gh", args, { input: body, stdio: ["pipe", "pipe", "inherit"] });
|
|
157
|
+
return out.toString("utf8").trim();
|
|
158
|
+
}
|
|
159
|
+
function applyCoreLabels(issueUrl) {
|
|
160
|
+
try {
|
|
161
|
+
execFileSync(
|
|
162
|
+
"gh",
|
|
163
|
+
["issue", "edit", issueUrl, "--add-label", "bug", "--add-label", "auto-report"],
|
|
164
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
165
|
+
);
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function offerBugReport(error) {
|
|
172
|
+
if (!isInteractive()) return;
|
|
173
|
+
const yes = await promptYesNo("\n This looks like a bug. Open an issue with debug info? [Y/n] ");
|
|
174
|
+
if (!yes) return;
|
|
175
|
+
const title = buildTitle(error);
|
|
176
|
+
const body = buildReportBody(error);
|
|
177
|
+
const labels = buildLabels(error);
|
|
178
|
+
if (!hasGhCli()) {
|
|
179
|
+
console.error("\n gh CLI not found. Debug info below \u2014 paste into a new issue:\n");
|
|
180
|
+
console.error(` https://github.com/paritytech/bulletin-deploy/issues/new
|
|
181
|
+
`);
|
|
182
|
+
printFallback(title, body, labels);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const url = createGhIssue(title, body, labels);
|
|
187
|
+
console.error(` Issue created: ${url}`);
|
|
188
|
+
return;
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
console.error(" Retrying without labels...");
|
|
193
|
+
const url = createGhIssue(title, body, []);
|
|
194
|
+
const applied = applyCoreLabels(url);
|
|
195
|
+
if (applied) {
|
|
196
|
+
console.error(` Issue created: ${url} (labels applied after retry)`);
|
|
197
|
+
} else {
|
|
198
|
+
console.error(` Issue created: ${url} (labels could not be applied; please add 'bug' and 'auto-report' manually)`);
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
console.error(" Failed to create issue. Debug info below:\n");
|
|
202
|
+
printFallback(title, body, labels);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function printFallback(title, body, labels) {
|
|
206
|
+
console.error(` Title: ${title}`);
|
|
207
|
+
console.error(` Labels: ${labels.join(", ")}
|
|
208
|
+
`);
|
|
209
|
+
console.error(body);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export {
|
|
213
|
+
setDeployContext,
|
|
214
|
+
installLogCapture,
|
|
215
|
+
getCapturedTail,
|
|
216
|
+
scrubSecrets,
|
|
217
|
+
buildCliFlagsSummary,
|
|
218
|
+
buildReportBody,
|
|
219
|
+
buildTitle,
|
|
220
|
+
buildLabels,
|
|
221
|
+
createGhIssue,
|
|
222
|
+
offerBugReport
|
|
223
|
+
};
|
|
@@ -12,8 +12,11 @@ var CidPreservingBlockstore = class {
|
|
|
12
12
|
*all() {
|
|
13
13
|
yield* this.data.values();
|
|
14
14
|
}
|
|
15
|
+
clear() {
|
|
16
|
+
this.data.clear();
|
|
17
|
+
}
|
|
15
18
|
};
|
|
16
|
-
function
|
|
19
|
+
function* walkDirectoryLazy(dirPath, prefix = "") {
|
|
17
20
|
let dirents;
|
|
18
21
|
try {
|
|
19
22
|
dirents = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -23,42 +26,46 @@ function walkDirectory(dirPath, prefix = "") {
|
|
|
23
26
|
if (code === "ENOTDIR") throw new Error(`Not a directory: ${dirPath}`);
|
|
24
27
|
throw err;
|
|
25
28
|
}
|
|
26
|
-
const entries = [];
|
|
27
29
|
for (const entry of dirents) {
|
|
28
30
|
const fullPath = path.join(dirPath, entry.name);
|
|
29
31
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
30
32
|
if (entry.isDirectory()) {
|
|
31
|
-
|
|
33
|
+
yield* walkDirectoryLazy(fullPath, relativePath);
|
|
32
34
|
} else if (entry.isFile()) {
|
|
33
|
-
|
|
35
|
+
yield { path: relativePath, absolutePath: fullPath };
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
|
-
return entries;
|
|
37
38
|
}
|
|
38
39
|
async function collectBytes(iter) {
|
|
39
40
|
const parts = [];
|
|
41
|
+
let totalLength = 0;
|
|
40
42
|
for await (const chunk of iter) {
|
|
41
43
|
parts.push(chunk);
|
|
44
|
+
totalLength += chunk.length;
|
|
42
45
|
}
|
|
43
|
-
const totalLength = parts.reduce((sum, p) => sum + p.length, 0);
|
|
44
46
|
const result = new Uint8Array(totalLength);
|
|
45
47
|
let offset = 0;
|
|
46
|
-
for (
|
|
48
|
+
for (let i = 0; i < parts.length; i++) {
|
|
49
|
+
const part = parts[i];
|
|
47
50
|
result.set(part, offset);
|
|
48
51
|
offset += part.length;
|
|
52
|
+
parts[i] = void 0;
|
|
49
53
|
}
|
|
50
54
|
return result;
|
|
51
55
|
}
|
|
52
56
|
async function merkleizeJS(directoryPath) {
|
|
53
57
|
console.log(` Merkleizing (JS): ${directoryPath}`);
|
|
54
|
-
const files = walkDirectory(directoryPath);
|
|
55
58
|
const blockstore = new CidPreservingBlockstore();
|
|
56
|
-
const source =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const source = (function* () {
|
|
60
|
+
for (const file of walkDirectoryLazy(directoryPath)) {
|
|
61
|
+
yield {
|
|
62
|
+
path: file.path,
|
|
63
|
+
content: (async function* () {
|
|
64
|
+
yield fs.readFileSync(file.absolutePath);
|
|
65
|
+
})()
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
62
69
|
let rootCid;
|
|
63
70
|
for await (const entry of importer(source, blockstore, {
|
|
64
71
|
cidVersion: 1,
|
|
@@ -77,6 +84,7 @@ async function merkleizeJS(directoryPath) {
|
|
|
77
84
|
}
|
|
78
85
|
await writer.close();
|
|
79
86
|
const carBytes = await collectPromise;
|
|
87
|
+
blockstore.clear();
|
|
80
88
|
console.log(` CAR (JS): ${(carBytes.length / 1024 / 1024).toFixed(2)} MB`);
|
|
81
89
|
return { carBytes, cid: rootCid.toString() };
|
|
82
90
|
}
|