@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,725 @@
1
+ import {
2
+ package_default,
3
+ writeRunState
4
+ } from "./chunk-KJH2T5TQ.js";
5
+
6
+ // src/memory-report.ts
7
+ import * as fs2 from "fs";
8
+ import * as os from "os";
9
+ import * as path2 from "path";
10
+ import * as v8 from "v8";
11
+
12
+ // src/telemetry.ts
13
+ import { execSync } from "child_process";
14
+ import { createHash } from "crypto";
15
+ import * as fs from "fs";
16
+ import * as path from "path";
17
+ var VERSION = package_default.version;
18
+ var DOTNS_BACKEND = "contract";
19
+ var DOTNS_POP_SOURCE = "personhood-precompile";
20
+ var DEFAULT_DSN = "https://e021c025d79c4c3ade2862a11f13c40b@o4511059872841728.ingest.de.sentry.io/4511093597405264";
21
+ var INTERNAL_ORG_RE = /^(paritytech|w3f|polkadot-fellows)\//i;
22
+ var PARITY_HOST_APPS = /* @__PURE__ */ new Set(["playground-cli"]);
23
+ function extractRepoSlug(url) {
24
+ return url.replace(/.*github\.com[:/]/, "").replace(/\.git$/, "");
25
+ }
26
+ function tryGitRemote() {
27
+ try {
28
+ return extractRepoSlug(execSync("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim());
29
+ } catch {
30
+ return void 0;
31
+ }
32
+ }
33
+ function isInternalContextFromSignals(signals) {
34
+ if (INTERNAL_ORG_RE.test(signals.githubRepository ?? "")) return true;
35
+ if (signals.runnerName?.startsWith("parity-")) return true;
36
+ if (signals.gitRemote && INTERNAL_ORG_RE.test(signals.gitRemote)) return true;
37
+ if (signals.hostApp && PARITY_HOST_APPS.has(signals.hostApp)) return true;
38
+ return false;
39
+ }
40
+ function isInternalContext() {
41
+ return isInternalContextFromSignals({
42
+ githubRepository: process.env.GITHUB_REPOSITORY,
43
+ runnerName: process.env.RUNNER_NAME,
44
+ gitRemote: tryGitRemote(),
45
+ hostApp: process.env.BULLETIN_DEPLOY_HOST_APP
46
+ });
47
+ }
48
+ var OPT_OUT = process.env.BULLETIN_DEPLOY_TELEMETRY === "0";
49
+ var OPT_IN = process.env.BULLETIN_DEPLOY_TELEMETRY === "1";
50
+ var DISABLED = OPT_OUT || !OPT_IN && !isInternalContext();
51
+ var CONVENTIONAL_BRANCH_PREFIXES = /* @__PURE__ */ new Set([
52
+ "fix",
53
+ "feat",
54
+ "chore",
55
+ "docs",
56
+ "test",
57
+ "refactor",
58
+ "release",
59
+ "bump",
60
+ "perf",
61
+ "style",
62
+ "ci",
63
+ "build",
64
+ "revert"
65
+ ]);
66
+ function scrubPaths(msg) {
67
+ if (!msg) return msg;
68
+ return msg.replace(/\/Users\/[^\/\s"'`]+/g, "/Users/<redacted>").replace(/\/home\/[^\/\s"'`]+/g, "/home/<redacted>");
69
+ }
70
+ function truncateAddress(ss58) {
71
+ if (!ss58) return ss58;
72
+ return ss58.length > 8 ? `${ss58.slice(0, 8)}\u2026` : ss58;
73
+ }
74
+ function sanitizeBranch(name) {
75
+ if (!name) return name;
76
+ const slash = name.indexOf("/");
77
+ if (slash === -1) return name;
78
+ const prefix = name.slice(0, slash).toLowerCase();
79
+ if (CONVENTIONAL_BRANCH_PREFIXES.has(prefix)) return name;
80
+ return name.slice(slash + 1);
81
+ }
82
+ function sanitizeRepo(slug) {
83
+ if (!slug) return slug;
84
+ if (INTERNAL_ORG_RE.test(slug)) return slug;
85
+ const slash = slug.indexOf("/");
86
+ if (slash === -1) {
87
+ return `ext/${createHash("sha256").update(slug).digest("hex").slice(0, 12)}`;
88
+ }
89
+ const org = slug.slice(0, slash);
90
+ const repo = slug.slice(slash + 1);
91
+ return `${org}/${createHash("sha256").update(repo).digest("hex").slice(0, 12)}`;
92
+ }
93
+ var Sentry = null;
94
+ if (!DISABLED) {
95
+ try {
96
+ Sentry = await import("@sentry/node");
97
+ } catch {
98
+ }
99
+ }
100
+ var runStateActive = false;
101
+ var relaunchOomHintShown = false;
102
+ function setRunStateActive(v) {
103
+ runStateActive = v;
104
+ }
105
+ function markRelaunchOomHintShown() {
106
+ relaunchOomHintShown = true;
107
+ }
108
+ async function closeTelemetry(timeoutMs) {
109
+ if (!Sentry) return;
110
+ try {
111
+ await Sentry.close(timeoutMs);
112
+ } catch {
113
+ }
114
+ }
115
+ function initTelemetry() {
116
+ if (!Sentry) return;
117
+ if (process.env.BULLETIN_DEPLOY_USE_AMBIENT_SENTRY === "1") {
118
+ return;
119
+ }
120
+ Sentry.init({
121
+ dsn: process.env.SENTRY_DSN || DEFAULT_DSN,
122
+ release: `${package_default.name}@${VERSION}`,
123
+ tracesSampleRate: 1,
124
+ environment: process.env.CI ? "ci" : "local",
125
+ // Sentry Node SDK captures os.hostname() by default, which leaks personal
126
+ // machine names (e.g. "Mac.fritz.box"). Override to something anonymous.
127
+ serverName: process.env.CI ? process.env.RUNNER_NAME ?? "ci" : "local",
128
+ beforeSend(event) {
129
+ if (event.server_name) event.server_name = process.env.CI ? process.env.RUNNER_NAME ?? "ci" : "local";
130
+ if (event.message) event.message = scrubPaths(event.message);
131
+ for (const ex of event.exception?.values ?? []) {
132
+ if (ex.value) ex.value = scrubPaths(ex.value);
133
+ }
134
+ for (const bc of event.breadcrumbs ?? []) {
135
+ if (bc.message) bc.message = scrubPaths(bc.message);
136
+ }
137
+ return event;
138
+ },
139
+ beforeSendTransaction(event) {
140
+ const spans = event.spans ?? [];
141
+ for (const span of spans) {
142
+ const attrs = span.data;
143
+ if (!attrs) continue;
144
+ for (const k of Object.keys(attrs)) {
145
+ const v = attrs[k];
146
+ if (typeof v === "string") attrs[k] = scrubPaths(v);
147
+ }
148
+ }
149
+ return event;
150
+ }
151
+ });
152
+ Sentry.setTag("bulletin-deploy.version", VERSION);
153
+ Sentry.setContext("bulletin-deploy", {
154
+ version: VERSION,
155
+ release: `${package_default.name}@${VERSION}`,
156
+ node: process.version
157
+ });
158
+ }
159
+ function tryPackageJsonRepo() {
160
+ try {
161
+ const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf-8"));
162
+ const repo = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
163
+ if (repo) return extractRepoSlug(repo);
164
+ } catch {
165
+ }
166
+ return void 0;
167
+ }
168
+ function resolveRepo(domain) {
169
+ return process.env.GITHUB_REPOSITORY || tryGitRemote() || tryPackageJsonRepo() || domain || "unknown";
170
+ }
171
+ function tryGitBranch() {
172
+ try {
173
+ return execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
174
+ } catch {
175
+ return "unknown";
176
+ }
177
+ }
178
+ function resolveRunner() {
179
+ if (!process.env.CI) return "local";
180
+ if (process.env.RUNNER_NAME?.startsWith("parity-")) return process.env.RUNNER_NAME;
181
+ return process.env.RUNNER_NAME || "unknown";
182
+ }
183
+ function resolveRunnerType() {
184
+ if (!process.env.CI) return "local";
185
+ if (process.env.RUNNER_NAME?.startsWith("parity-")) return "self-hosted";
186
+ return "github-hosted";
187
+ }
188
+ function getDeployAttributes(domain) {
189
+ const hostApp = process.env.BULLETIN_DEPLOY_HOST_APP;
190
+ const attrs = {
191
+ "deploy.repo": sanitizeRepo(resolveRepo(domain)),
192
+ "deploy.branch": sanitizeBranch(process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || tryGitBranch()),
193
+ "deploy.source": process.env.CI ? "ci" : "local",
194
+ "deploy.pr": process.env.GITHUB_PR_NUMBER || void 0,
195
+ "deploy.tool_version": VERSION,
196
+ "deploy.runner": resolveRunner(),
197
+ "deploy.runner_type": resolveRunnerType(),
198
+ // Seed "false" so successful spans form the %SAD denominator; the catch block and
199
+ // captureWarning flip it to "true" on friction.
200
+ "deploy.sad": "false",
201
+ // Same ratio-denominator reasoning as deploy.sad above, but for the
202
+ // %EXPECTED-refusal metric: catch block flips to "true" when the error
203
+ // matches isExpectedError (user-facing product rule, not tool friction).
204
+ "deploy.expected": "false",
205
+ // Seed "false" so every span carries the attribute (boolean-both-values rule).
206
+ // Flipped to "true" by getWsProvider's onStatusChanged when papi connects to a
207
+ // non-primary endpoint, and flushed again in deploy()'s finally block.
208
+ "deploy.rpc.failed_over": "false",
209
+ // DotNS preflight balance gate. Seeded "false" so successful spans form the
210
+ // denominator for "% of deploys hitting the floor" and "% recovered via
211
+ // testnet auto-top-up" metrics. Flipped by gateOnFeeBalance.
212
+ "deploy.dotns.signer_below_floor": "false",
213
+ "deploy.dotns.toppedup": "false",
214
+ // Seeded "hash" so spans missing an explicit set (non-DotNS deploys) group
215
+ // cleanly in the "hash" bucket.
216
+ "deploy.dotns.tx_resolution_kind": "hash",
217
+ // Seeded "false" so every span carries the attribute for ratio queries.
218
+ // Flipped by deploy.ts storage phase when content is encrypted.
219
+ "deploy.encrypted": "false",
220
+ // Flipped by deploy.ts after parseDomainName resolves isSubdomain.
221
+ "deploy.subdomain": "false",
222
+ // Flipped by deploy.ts after readPreviousContenthashSafe when a prior CID is found.
223
+ "deploy.incremental": "false",
224
+ // Seeded "false" so every span carries the attribute for ratio queries.
225
+ // Flipped to "true" by storeDirectoryV2 when the Phase A root node is already on-chain.
226
+ "deploy.storage.phase_a.root_already_onchain": "false",
227
+ // Seeded 0 so every span carries the attribute; incremented by storeDirectoryV2
228
+ // for each Phase B chunk confirmed present on-chain (probe hit → skip re-upload).
229
+ "deploy.storage.phase_b.probe_hit_count": 0,
230
+ "deploy.phase_a.chunks_uploaded": 0,
231
+ "deploy.probe.finality_miss_count": 0,
232
+ "deploy.probe.finality_miss_reupload_count": 0,
233
+ "deploy.pool.eligible_count": 0,
234
+ // Nonce-advance collision probe counters. Seeded to 0 so every span carries
235
+ // the attribute for ratio queries even when no collision is detected.
236
+ "deploy.pool.nonce_collision_count": 0,
237
+ "deploy.pool.nonce_collision_missing": 0,
238
+ "deploy.pool.nonce_collision_reupload_count": 0,
239
+ // Manifest-aware Phase A trust: count of section-1 CIDs trusted from prev manifest.
240
+ "deploy.phase_a.chunks_trusted": 0,
241
+ // Manifest fetch outcome. Seeded so every span carries the attributes even when
242
+ // fetchPreviousManifest is never reached (first deploy, early error, non-incremental
243
+ // path). "none" + "0" form the denominator for ratio queries:
244
+ // count_if(deploy.manifest.fetch_source, "heuristic_fallback") / count().
245
+ // Both string-valued per @sentry/node EAP numeric-attribute caveat.
246
+ "deploy.manifest.fetch_source": "none",
247
+ "deploy.manifest.fetch_attempts": "0",
248
+ "deploy.manifest.bytes_downloaded": "0",
249
+ // Bulletin storage upload chain receipt (root-node tx, or last chunk when root skipped).
250
+ // Empty-string default so every span carries the attribute for filter queries.
251
+ "bulletin.upload.tx_hash": "",
252
+ "bulletin.upload.block_hash": "",
253
+ "bulletin.upload.block_number": 0,
254
+ // DotNS setContenthash chain receipt.
255
+ "deploy.contenthash.tx": "",
256
+ "deploy.contenthash.block": 0,
257
+ "deploy.contenthash.block_hash": "",
258
+ // DotNS register chain receipt (fresh registrations only).
259
+ "deploy.register.tx": "",
260
+ "deploy.register.block": 0,
261
+ "deploy.register.block_hash": "",
262
+ // DotNS setSubnodeOwner chain receipt (subdomain registrations only).
263
+ "deploy.subnode.tx": "",
264
+ "deploy.subnode.block": 0,
265
+ "deploy.subnode.block_hash": ""
266
+ };
267
+ if (hostApp) attrs["deploy.host_app"] = hostApp;
268
+ const hostAppVersion = process.env.BULLETIN_DEPLOY_HOST_APP_VERSION;
269
+ if (hostAppVersion) attrs["deploy.host_app_version"] = hostAppVersion;
270
+ attrs["deploy.dotns_backend"] = DOTNS_BACKEND;
271
+ attrs["deploy.dotns_pop_source"] = DOTNS_POP_SOURCE;
272
+ return attrs;
273
+ }
274
+ function isExpectedError(msg) {
275
+ return /personhood|owned by|owner mismatch|reserved for original|invalid domain label|not authorized for bulletin|insufficient balance|insufficient funds|quota exhausted|insufficient .* authorization|bip39 mnemonic|ipfs cli not installed|base name is \d+ chars|NameNotAvailable|name must be lowercase/i.test(msg);
276
+ }
277
+ function classifyDeployError(msg) {
278
+ if (isExpectedError(msg)) return "user";
279
+ if (/chunk.*failed after.*retr|tx dropped from best chain|timed out after \d+s waiting for block|Contract reverted|Contract execution would revert|dotns register failed|All promises were rejected|"type"\s*:\s*"Invalid"|Commitment still too new|not finalised after \d+s|chain may have (dropped|evicted)|ReviveApi.*timed out/i.test(msg)) return "environment";
280
+ if (/javascript heap out of memory|allocation failed.*heap|External signer mode is not supported with dotns-cli/i.test(msg)) return "internal";
281
+ return "unknown";
282
+ }
283
+ function classifySadReason(message) {
284
+ if (/process terminated: SIG/i.test(message)) return "killed";
285
+ if (/memory threshold/i.test(message)) return "memory";
286
+ if (/spektr injection|account map failed/i.test(message)) return "signer";
287
+ if (/chunk upload failed|chunk retry failed/i.test(message)) return "chain_storage";
288
+ if (/websocket connection lost|rpc.*endpoint failed|rpc failover/i.test(message)) return "rpc";
289
+ return "other";
290
+ }
291
+ function computeDeployOutcome(errorCategory, isSad, sadReason) {
292
+ if (errorCategory === "user") return "user_error";
293
+ if (errorCategory === "environment") return "env_error";
294
+ if (errorCategory === "internal") return "internal_error";
295
+ if (errorCategory === "unknown") return "unknown_error";
296
+ if (isSad) return `sad_${sadReason}`;
297
+ return "clean";
298
+ }
299
+ var ERROR_KIND_RULES = [
300
+ [/Contract reverted|Contract execution would revert|revert(?:ed|ing)?\s*\(flags=[0-9]+\)/i, "contract-revert"],
301
+ [/timed out after \d+s waiting for block|Transaction not included after \d+s|Transaction did not settle within/i, "chain-timeout"],
302
+ [/\bstale\b.*nonce|nonce.*\bstale\b|"type"\s*:\s*"(?:Future|Stale)"|Invalid::Future|tx rejected by pool/i, "nonce-stale"],
303
+ [/heartbeat timeout|WS halt|Unable to connect|ChainHead disjointed|websocket.*closed|socket closed|disconnect/i, "connection"],
304
+ [/requires ProofOfPersonhoodFull,\s*but this signer is NoStatus/i, "naming.pop_required"],
305
+ [/Domain\s+\S+\.dot\s+is already owned by\s+0x[a-fA-F0-9]+/i, "naming.already_owned"],
306
+ [/Cannot deploy\s+[\w.-]+\.dot:\s*parent\s+[\w.-]+\.dot\s+is owned by/i, "naming.subdomain_orphan"],
307
+ [/Post-deploy verification failed for .+: on-chain contenthash is /i, "verify.contenthash_mismatch"],
308
+ [/Deploy verification failed:\s*DAG-PB root.+not finalised/i, "verify.dagpb_not_finalised"],
309
+ [/Retry budget exhausted:.*recovery attempts/i, "network.recovery_exhausted"],
310
+ [/ReviveApi\.\w+ timed out after \d+ms/i, "chain.api_timeout"],
311
+ [/^INVARIANT FAILED:/i, "tool.invariant"]
312
+ ];
313
+ function classifyErrorKind(msg) {
314
+ for (const [re, kind] of ERROR_KIND_RULES) {
315
+ if (re.test(msg)) return kind;
316
+ }
317
+ return "unknown";
318
+ }
319
+ function sanitizeErrorMessage(msg) {
320
+ return scrubPaths(msg.slice(0, 500));
321
+ }
322
+ function analyseErrorPattern(msg) {
323
+ const tags = [];
324
+ const len = msg.length;
325
+ if (len < 50) tags.push("len:lt50");
326
+ else if (len < 100) tags.push("len:50-99");
327
+ else if (len < 200) tags.push("len:100-199");
328
+ else if (len < 500) tags.push("len:200-499");
329
+ else tags.push("len:gte500");
330
+ if (/[a-z]+:\/\/[^\s:/?#]+:[^\s@/?#]+@/i.test(msg)) tags.push("url-userinfo");
331
+ const longHexRuns = (msg.match(/[0-9a-fA-F]{40,}/g) ?? []).filter((h) => !h.toLowerCase().startsWith("e30") && h.length !== 40);
332
+ if (longHexRuns.length > 0) tags.push(`long-hex:${Math.min(longHexRuns.length, 9)}`);
333
+ const b64ish = msg.match(/[A-Za-z0-9+/=]{30,}/g) ?? [];
334
+ if (b64ish.some((s) => /[A-Z]/.test(s) && /[a-z]/.test(s) && /[0-9]/.test(s))) tags.push("base64ish");
335
+ if (/\b[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/.test(msg)) tags.push("jwt-shape");
336
+ const evmCount = (msg.match(/0x[a-fA-F0-9]{40}\b/g) ?? []).length;
337
+ if (evmCount > 0) tags.push(`evm:${Math.min(evmCount, 9)}`);
338
+ if (/\b[1-9A-HJ-NP-Za-km-z]{46,49}\b/.test(msg)) tags.push("ss58-shape");
339
+ if (/(?:\b[a-z]{3,8}\s){11,23}\b[a-z]{3,8}\b/.test(msg)) tags.push("mnemonic-shape");
340
+ return tags.join(",");
341
+ }
342
+ async function withSpan(op, description, attributes, fn) {
343
+ if (!Sentry) return fn();
344
+ return Sentry.startSpan({ op, name: description, attributes }, async (span) => {
345
+ try {
346
+ return await fn();
347
+ } catch (error) {
348
+ const msg = error.message ?? String(error);
349
+ span.setAttribute("error.message", msg);
350
+ span.setAttribute("deploy.error_kind", classifyErrorKind(msg));
351
+ span.setAttribute("deploy.error_message", sanitizeErrorMessage(msg));
352
+ span.setAttribute("deploy.error_pattern_signature", analyseErrorPattern(msg));
353
+ span.setStatus({ code: 2, message: "internal_error" });
354
+ throw error;
355
+ }
356
+ });
357
+ }
358
+ var memoryPeak = null;
359
+ var deployRootSpan = null;
360
+ var stageSamples = {};
361
+ var reportContext = {};
362
+ var currentErrorCategory = null;
363
+ var currentDeploySad = false;
364
+ var currentSadReason = "other";
365
+ var currentSadReasonPriority = 0;
366
+ var SAD_REASON_PRIORITY = {
367
+ killed: 5,
368
+ memory: 4,
369
+ signer: 3,
370
+ chain_storage: 2,
371
+ rpc: 1,
372
+ other: 0
373
+ };
374
+ function toMb(bytes) {
375
+ return Math.round(bytes / 1024 / 1024 * 100) / 100;
376
+ }
377
+ function sampleMemory(stage) {
378
+ if (!Sentry) return;
379
+ const m = process.memoryUsage();
380
+ const active = Sentry.getActiveSpan();
381
+ if (process.env.BULLETIN_DEPLOY_MEM_DEBUG) {
382
+ console.error(`[sampleMemory] stage=${stage} active=${active ? active.description ?? active.name ?? "?" : "null"} rss=${toMb(m.rss)}MB`);
383
+ }
384
+ if (active) {
385
+ active.setAttribute(`mem.${stage}.rss_bytes`, m.rss);
386
+ active.setAttribute(`mem.${stage}.heap_used_bytes`, m.heapUsed);
387
+ active.setAttribute(`mem.${stage}.external_bytes`, m.external);
388
+ active.setAttribute(`mem.${stage}.array_buffers_bytes`, m.arrayBuffers);
389
+ }
390
+ stageSamples[stage] = sampleFromBytes(m);
391
+ if (memoryPeak) {
392
+ if (m.rss > memoryPeak.rss) memoryPeak.rss = m.rss;
393
+ if (m.heapUsed > memoryPeak.heapUsed) memoryPeak.heapUsed = m.heapUsed;
394
+ if (m.external > memoryPeak.external) memoryPeak.external = m.external;
395
+ if (m.arrayBuffers > memoryPeak.arrayBuffers) memoryPeak.arrayBuffers = m.arrayBuffers;
396
+ if (deployRootSpan) {
397
+ deployRootSpan.setAttribute("deploy.mem.peak_rss_bytes", memoryPeak.rss);
398
+ deployRootSpan.setAttribute("deploy.mem.peak_heap_used_bytes", memoryPeak.heapUsed);
399
+ deployRootSpan.setAttribute("deploy.mem.peak_external_bytes", memoryPeak.external);
400
+ deployRootSpan.setAttribute("deploy.mem.peak_array_buffers_bytes", memoryPeak.arrayBuffers);
401
+ }
402
+ }
403
+ if (runStateActive && memoryPeak) {
404
+ try {
405
+ writeRunState({ lastPeakRssMb: toMb(memoryPeak.rss), lastStage: stage });
406
+ } catch {
407
+ }
408
+ }
409
+ }
410
+ async function withDeploySpan(domain, fn) {
411
+ if (!Sentry) return fn();
412
+ const attrs = { ...getDeployAttributes(domain), "deploy.domain": domain };
413
+ const m0 = process.memoryUsage();
414
+ memoryPeak = { rss: m0.rss, heapUsed: m0.heapUsed, external: m0.external, arrayBuffers: m0.arrayBuffers };
415
+ stageSamples = {};
416
+ reportContext = {};
417
+ currentErrorCategory = null;
418
+ currentDeploySad = false;
419
+ currentSadReason = "other";
420
+ currentSadReasonPriority = 0;
421
+ const deployStartMs = Date.now();
422
+ try {
423
+ return await Sentry.startSpan({ op: "deploy", name: `deploy ${domain}`, attributes: attrs }, async (span) => {
424
+ deployRootSpan = span;
425
+ span.setAttribute("deploy.tool_version", VERSION);
426
+ if (relaunchOomHintShown) {
427
+ span.setAttribute("deploy.relaunch.oom_hint_shown", "true");
428
+ }
429
+ const tagsToSet = {
430
+ "deploy.repo": attrs["deploy.repo"],
431
+ "deploy.branch": attrs["deploy.branch"],
432
+ "deploy.domain": domain,
433
+ "deploy.source": attrs["deploy.source"],
434
+ "deploy.tool_version": VERSION,
435
+ "deploy.runner_type": resolveRunnerType(),
436
+ "deploy.host_app": attrs["deploy.host_app"] ?? ""
437
+ };
438
+ if (!tagsToSet["deploy.host_app"]) delete tagsToSet["deploy.host_app"];
439
+ Sentry.setTags(tagsToSet);
440
+ try {
441
+ const result = await fn();
442
+ span.setAttribute("deploy.status", "ok");
443
+ return result;
444
+ } catch (error) {
445
+ const msg = error.message ?? String(error);
446
+ span.setAttribute("deploy.status", "error");
447
+ span.setAttribute("deploy.error", msg.slice(0, 500));
448
+ const errorCategory = classifyDeployError(msg);
449
+ span.setAttribute("deploy.error_category", errorCategory);
450
+ span.setAttribute("deploy.error_kind", classifyErrorKind(msg));
451
+ span.setAttribute("deploy.error_message", sanitizeErrorMessage(msg));
452
+ span.setAttribute("deploy.error_pattern_signature", analyseErrorPattern(msg));
453
+ currentErrorCategory = errorCategory;
454
+ const isExpected = isExpectedError(msg);
455
+ span.setAttribute("deploy.expected", isExpected ? "true" : "false");
456
+ span.setAttribute("deploy.sad", isExpected ? "false" : "true");
457
+ if (!isExpected) {
458
+ span.setStatus({ code: 2, message: "internal_error" });
459
+ }
460
+ throw error;
461
+ } finally {
462
+ sampleMemory("end");
463
+ span.setAttribute(
464
+ "deploy.outcome",
465
+ computeDeployOutcome(currentErrorCategory, currentDeploySad, currentSadReason)
466
+ );
467
+ if (memoryPeak) {
468
+ try {
469
+ const report = maybeWriteMemoryReport({
470
+ peak: sampleFromBytes(memoryPeak),
471
+ stages: stageSamples,
472
+ deploy: {
473
+ domain,
474
+ repo: attrs["deploy.repo"],
475
+ deployTag: attrs["deploy.tag"],
476
+ durationMs: Date.now() - deployStartMs,
477
+ sentryTraceId: span.spanContext?.().traceId,
478
+ ...reportContext
479
+ },
480
+ outputDir: reportContext.outputDir,
481
+ onSentryAttach: (r) => {
482
+ try {
483
+ Sentry.captureMessage(`deploy memory threshold crossed (${r.threshold.peakRssMb} MB)`, {
484
+ level: "warning",
485
+ tags: { "deploy.mem.report": "1" },
486
+ extra: { memoryReport: r }
487
+ });
488
+ } catch {
489
+ }
490
+ }
491
+ });
492
+ if (report.status === "written") {
493
+ span.setAttribute("deploy.mem.report_written", "true");
494
+ span.setAttribute("deploy.mem.report_path", report.path);
495
+ console.log(`
496
+ High memory usage detected (peak ${report.peakRssMb} MB, threshold ${report.thresholdMb} MB).`);
497
+ console.log(` Diagnostic report written to ${report.path}`);
498
+ }
499
+ } catch (e) {
500
+ captureWarning("maybeWriteMemoryReport threw", {
501
+ error: e?.message?.slice(0, 200)
502
+ });
503
+ }
504
+ }
505
+ }
506
+ });
507
+ } finally {
508
+ memoryPeak = null;
509
+ deployRootSpan = null;
510
+ stageSamples = {};
511
+ reportContext = {};
512
+ currentErrorCategory = null;
513
+ currentDeploySad = false;
514
+ currentSadReason = "other";
515
+ currentSadReasonPriority = 0;
516
+ try {
517
+ await Sentry.flush(5e3);
518
+ } catch {
519
+ }
520
+ }
521
+ }
522
+ function setDeployReportContext(patch) {
523
+ reportContext = { ...reportContext, ...patch };
524
+ }
525
+ function setDeployAttribute(key, value) {
526
+ if (!deployRootSpan) return;
527
+ deployRootSpan.setAttribute(key, value);
528
+ }
529
+ function __setDeployRootSpanForTest(span) {
530
+ deployRootSpan = span;
531
+ }
532
+ function __setSentryForTest(stub) {
533
+ const prev = Sentry;
534
+ Sentry = stub;
535
+ return prev;
536
+ }
537
+ function getCurrentSentryTraceId() {
538
+ if (!Sentry) return void 0;
539
+ const span = Sentry.getActiveSpan();
540
+ if (!span) return void 0;
541
+ return span.spanContext?.().traceId;
542
+ }
543
+ function setDeploySentryTag(key, value) {
544
+ if (!Sentry) return;
545
+ Sentry.setTag(key, value);
546
+ }
547
+ function captureWarning(message, context) {
548
+ if (!Sentry) return;
549
+ try {
550
+ Sentry.addBreadcrumb({ level: "warning", message, data: context });
551
+ Sentry.captureMessage(message, { level: "warning", extra: context });
552
+ if (deployRootSpan) deployRootSpan.setAttribute("deploy.sad", "true");
553
+ const reason = classifySadReason(message);
554
+ const priority = SAD_REASON_PRIORITY[reason] ?? 0;
555
+ if (priority >= currentSadReasonPriority) {
556
+ currentSadReason = reason;
557
+ currentSadReasonPriority = priority;
558
+ }
559
+ currentDeploySad = true;
560
+ } catch {
561
+ }
562
+ }
563
+ async function flush() {
564
+ if (!Sentry) return;
565
+ try {
566
+ await Sentry.flush(5e3);
567
+ } catch {
568
+ }
569
+ }
570
+
571
+ // src/memory-report.ts
572
+ function isBunRuntime() {
573
+ return typeof globalThis.Bun !== "undefined" || typeof process.versions.bun === "string";
574
+ }
575
+ var DEFAULT_THRESHOLD_MB = 800;
576
+ function toMb2(bytes) {
577
+ return Math.round(bytes / 1024 / 1024 * 100) / 100;
578
+ }
579
+ function safeHeap(f) {
580
+ try {
581
+ return f();
582
+ } catch {
583
+ return void 0;
584
+ }
585
+ }
586
+ function countByType(items) {
587
+ const counts = {};
588
+ for (const item of items) {
589
+ const name = item?.constructor?.name ?? "Unknown";
590
+ counts[name] = (counts[name] ?? 0) + 1;
591
+ }
592
+ return counts;
593
+ }
594
+ function readActiveHandles() {
595
+ const proc = process;
596
+ return safeHeap(() => {
597
+ const handles = typeof proc._getActiveHandles === "function" ? proc._getActiveHandles() : [];
598
+ return countByType(handles);
599
+ }) ?? {};
600
+ }
601
+ function readActiveRequests() {
602
+ const proc = process;
603
+ return safeHeap(() => {
604
+ const reqs = typeof proc._getActiveRequests === "function" ? proc._getActiveRequests() : [];
605
+ return countByType(reqs);
606
+ }) ?? {};
607
+ }
608
+ function readPolkadotApiVersion() {
609
+ try {
610
+ const candidates = [
611
+ path2.join(process.cwd(), "node_modules/polkadot-api/package.json"),
612
+ path2.join(process.cwd(), "../node_modules/polkadot-api/package.json")
613
+ ];
614
+ for (const p of candidates) {
615
+ if (fs2.existsSync(p)) {
616
+ const pkg = JSON.parse(fs2.readFileSync(p, "utf-8"));
617
+ return typeof pkg.version === "string" ? pkg.version : void 0;
618
+ }
619
+ }
620
+ } catch {
621
+ }
622
+ return void 0;
623
+ }
624
+ function buildMemoryReport(input) {
625
+ return {
626
+ schemaVersion: 1,
627
+ toolVersion: VERSION,
628
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
629
+ threshold: { thresholdMb: input.thresholdMb, peakRssMb: input.peak.rssMb },
630
+ deploy: input.deploy,
631
+ memory: { peak: input.peak, stages: input.stages },
632
+ v8: {
633
+ heapStatistics: safeHeap(() => v8.getHeapStatistics()),
634
+ heapSpaceStatistics: safeHeap(() => v8.getHeapSpaceStatistics())
635
+ },
636
+ runtime: {
637
+ nodeVersion: process.version,
638
+ platform: process.platform,
639
+ arch: process.arch,
640
+ totalMemMb: Math.round(os.totalmem() / 1024 / 1024),
641
+ freeMemMb: Math.round(os.freemem() / 1024 / 1024),
642
+ cpuCount: os.cpus().length,
643
+ activeHandlesByType: readActiveHandles(),
644
+ activeRequestsByType: readActiveRequests()
645
+ },
646
+ polkadotApi: { version: readPolkadotApiVersion() }
647
+ };
648
+ }
649
+ function maybeWriteMemoryReport(input) {
650
+ const thresholdMb = input.thresholdMbOverride ?? (process.env.BULLETIN_DEPLOY_MEM_REPORT_THRESHOLD_MB ? Number(process.env.BULLETIN_DEPLOY_MEM_REPORT_THRESHOLD_MB) : DEFAULT_THRESHOLD_MB);
651
+ const peakRssMb = input.peak.rssMb;
652
+ if (process.env.BULLETIN_DEPLOY_MEM_REPORT === "0") {
653
+ return { status: "disabled", thresholdMb, peakRssMb };
654
+ }
655
+ if (isBunRuntime()) {
656
+ return { status: "unsupported-runtime", thresholdMb, peakRssMb };
657
+ }
658
+ if (!Number.isFinite(thresholdMb) || peakRssMb < thresholdMb) {
659
+ return { status: "below-threshold", thresholdMb, peakRssMb };
660
+ }
661
+ const isInternal = input.isInternal ?? isInternalContext;
662
+ if (!isInternal()) {
663
+ return { status: "not-internal", thresholdMb, peakRssMb };
664
+ }
665
+ const report = buildMemoryReport({
666
+ thresholdMb,
667
+ peak: input.peak,
668
+ stages: input.stages,
669
+ deploy: input.deploy
670
+ });
671
+ const outDir = input.outputDir ?? process.cwd();
672
+ const filePath = path2.join(outDir, ".bulletin-memory-report.json");
673
+ const write = input.writeFile ?? ((p, c) => fs2.writeFileSync(p, c));
674
+ try {
675
+ write(filePath, JSON.stringify(report, null, 2));
676
+ } catch {
677
+ }
678
+ if (input.onSentryAttach) input.onSentryAttach(report);
679
+ return { status: "written", thresholdMb, peakRssMb, path: filePath };
680
+ }
681
+ function sampleFromBytes(m) {
682
+ return { rssMb: toMb2(m.rss), heapUsedMb: toMb2(m.heapUsed), externalMb: toMb2(m.external), arrayBuffersMb: toMb2(m.arrayBuffers) };
683
+ }
684
+
685
+ export {
686
+ isBunRuntime,
687
+ DEFAULT_THRESHOLD_MB,
688
+ safeHeap,
689
+ buildMemoryReport,
690
+ maybeWriteMemoryReport,
691
+ sampleFromBytes,
692
+ VERSION,
693
+ isInternalContextFromSignals,
694
+ isInternalContext,
695
+ scrubPaths,
696
+ truncateAddress,
697
+ sanitizeBranch,
698
+ sanitizeRepo,
699
+ setRunStateActive,
700
+ markRelaunchOomHintShown,
701
+ closeTelemetry,
702
+ initTelemetry,
703
+ resolveRepo,
704
+ resolveRunner,
705
+ resolveRunnerType,
706
+ getDeployAttributes,
707
+ isExpectedError,
708
+ classifyDeployError,
709
+ classifySadReason,
710
+ computeDeployOutcome,
711
+ classifyErrorKind,
712
+ sanitizeErrorMessage,
713
+ analyseErrorPattern,
714
+ withSpan,
715
+ sampleMemory,
716
+ withDeploySpan,
717
+ setDeployReportContext,
718
+ setDeployAttribute,
719
+ __setDeployRootSpanForTest,
720
+ __setSentryForTest,
721
+ getCurrentSentryTraceId,
722
+ setDeploySentryTag,
723
+ captureWarning,
724
+ flush
725
+ };