bulletin-deploy 0.6.16 → 0.7.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.
@@ -1,3 +1,9 @@
1
+ // src/memory-report.ts
2
+ import * as fs2 from "fs";
3
+ import * as os from "os";
4
+ import * as path2 from "path";
5
+ import * as v8 from "v8";
6
+
1
7
  // src/telemetry.ts
2
8
  import { execSync } from "child_process";
3
9
  import { createHash } from "crypto";
@@ -7,7 +13,7 @@ import * as path from "path";
7
13
  // package.json
8
14
  var package_default = {
9
15
  name: "bulletin-deploy",
10
- version: "0.6.16",
16
+ version: "0.7.0",
11
17
  private: false,
12
18
  repository: {
13
19
  type: "git",
@@ -31,11 +37,10 @@ var package_default = {
31
37
  },
32
38
  files: [
33
39
  "dist",
34
- "bin",
35
- "cdm.json"
40
+ "bin"
36
41
  ],
37
42
  scripts: {
38
- build: "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts --format esm --dts --clean --target node22",
43
+ build: "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/memory-report.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts --format esm --dts --clean --target node22",
39
44
  prepare: "npm run build",
40
45
  test: "npm run build && node --test test/test.js test/pool.test.js test/helpers/e2e-helpers.test.js",
41
46
  "test:e2e": "npm run build && node --test test/e2e.test.js",
@@ -45,7 +50,6 @@ var package_default = {
45
50
  benchmark: "npm run build && node benchmark.js"
46
51
  },
47
52
  dependencies: {
48
- "@dotdm/cdm": "^0.5.1",
49
53
  "@ipld/car": "^5.4.3",
50
54
  "@ipld/dag-pb": "^4.1.3",
51
55
  "@noble/hashes": "^1.7.2",
@@ -244,11 +248,51 @@ async function withSpan(op, description, attributes, fn) {
244
248
  }
245
249
  });
246
250
  }
251
+ var memoryPeak = null;
252
+ var deployRootSpan = null;
253
+ var stageSamples = {};
254
+ var reportContext = {};
255
+ function toMb(bytes) {
256
+ return Math.round(bytes / 1024 / 1024 * 100) / 100;
257
+ }
258
+ function sampleMemory(stage) {
259
+ if (!Sentry) return;
260
+ const m = process.memoryUsage();
261
+ const active = Sentry.getActiveSpan();
262
+ if (process.env.BULLETIN_DEPLOY_MEM_DEBUG) {
263
+ console.error(`[sampleMemory] stage=${stage} active=${active ? active.description ?? active.name ?? "?" : "null"} rss=${toMb(m.rss)}MB`);
264
+ }
265
+ if (active) {
266
+ active.setAttribute(`mem.${stage}.rss_mb`, String(toMb(m.rss)));
267
+ active.setAttribute(`mem.${stage}.heap_used_mb`, String(toMb(m.heapUsed)));
268
+ active.setAttribute(`mem.${stage}.external_mb`, String(toMb(m.external)));
269
+ active.setAttribute(`mem.${stage}.array_buffers_mb`, String(toMb(m.arrayBuffers)));
270
+ }
271
+ stageSamples[stage] = sampleFromBytes(m);
272
+ if (memoryPeak) {
273
+ if (m.rss > memoryPeak.rss) memoryPeak.rss = m.rss;
274
+ if (m.heapUsed > memoryPeak.heapUsed) memoryPeak.heapUsed = m.heapUsed;
275
+ if (m.external > memoryPeak.external) memoryPeak.external = m.external;
276
+ if (m.arrayBuffers > memoryPeak.arrayBuffers) memoryPeak.arrayBuffers = m.arrayBuffers;
277
+ if (deployRootSpan) {
278
+ deployRootSpan.setAttribute("deploy.mem.peak_rss_mb", String(toMb(memoryPeak.rss)));
279
+ deployRootSpan.setAttribute("deploy.mem.peak_heap_used_mb", String(toMb(memoryPeak.heapUsed)));
280
+ deployRootSpan.setAttribute("deploy.mem.peak_external_mb", String(toMb(memoryPeak.external)));
281
+ deployRootSpan.setAttribute("deploy.mem.peak_array_buffers_mb", String(toMb(memoryPeak.arrayBuffers)));
282
+ }
283
+ }
284
+ }
247
285
  async function withDeploySpan(domain, fn) {
248
286
  if (!Sentry) return fn();
249
287
  const attrs = { ...getDeployAttributes(domain), "deploy.domain": domain };
288
+ const m0 = process.memoryUsage();
289
+ memoryPeak = { rss: m0.rss, heapUsed: m0.heapUsed, external: m0.external, arrayBuffers: m0.arrayBuffers };
290
+ stageSamples = {};
291
+ reportContext = {};
292
+ const deployStartMs = Date.now();
250
293
  try {
251
294
  return await Sentry.startSpan({ op: "deploy", name: `deploy ${domain}`, attributes: attrs }, async (span) => {
295
+ deployRootSpan = span;
252
296
  Sentry.setTags({
253
297
  "deploy.repo": attrs["deploy.repo"],
254
298
  "deploy.branch": attrs["deploy.branch"],
@@ -269,12 +313,53 @@ async function withDeploySpan(domain, fn) {
269
313
  span.setStatus({ code: 2, message: "internal_error" });
270
314
  }
271
315
  throw error;
316
+ } finally {
317
+ sampleMemory("end");
318
+ if (memoryPeak) {
319
+ const report = maybeWriteMemoryReport({
320
+ peak: sampleFromBytes(memoryPeak),
321
+ stages: stageSamples,
322
+ deploy: {
323
+ domain,
324
+ repo: attrs["deploy.repo"],
325
+ deployTag: attrs["deploy.tag"],
326
+ durationMs: Date.now() - deployStartMs,
327
+ sentryTraceId: span.spanContext?.().traceId,
328
+ ...reportContext
329
+ },
330
+ outputDir: reportContext.outputDir,
331
+ onSentryAttach: (r) => {
332
+ try {
333
+ Sentry.captureMessage(`deploy memory threshold crossed (${r.threshold.peakRssMb} MB)`, {
334
+ level: "warning",
335
+ tags: { "deploy.mem.report": "1" },
336
+ extra: { memoryReport: r }
337
+ });
338
+ } catch {
339
+ }
340
+ }
341
+ });
342
+ if (report.status === "written") {
343
+ span.setAttribute("deploy.mem.report_written", "true");
344
+ span.setAttribute("deploy.mem.report_path", report.path);
345
+ console.log(`
346
+ High memory usage detected (peak ${report.peakRssMb} MB, threshold ${report.thresholdMb} MB).`);
347
+ console.log(` Diagnostic report written to ${report.path}`);
348
+ }
349
+ }
272
350
  }
273
351
  });
274
352
  } finally {
353
+ memoryPeak = null;
354
+ deployRootSpan = null;
355
+ stageSamples = {};
356
+ reportContext = {};
275
357
  await Sentry.flush(5e3);
276
358
  }
277
359
  }
360
+ function setDeployReportContext(patch) {
361
+ reportContext = { ...reportContext, ...patch };
362
+ }
278
363
  function setDeployAttribute(key, value) {
279
364
  if (!Sentry) return;
280
365
  const span = Sentry.getActiveSpan();
@@ -299,7 +384,108 @@ async function flush() {
299
384
  await Sentry.flush(5e3);
300
385
  }
301
386
 
387
+ // src/memory-report.ts
388
+ var DEFAULT_THRESHOLD_MB = 1500;
389
+ function toMb2(bytes) {
390
+ return Math.round(bytes / 1024 / 1024 * 100) / 100;
391
+ }
392
+ function countByType(items) {
393
+ const counts = {};
394
+ for (const item of items) {
395
+ const name = item?.constructor?.name ?? "Unknown";
396
+ counts[name] = (counts[name] ?? 0) + 1;
397
+ }
398
+ return counts;
399
+ }
400
+ function readActiveHandles() {
401
+ const proc = process;
402
+ const handles = typeof proc._getActiveHandles === "function" ? proc._getActiveHandles() : [];
403
+ return countByType(handles);
404
+ }
405
+ function readActiveRequests() {
406
+ const proc = process;
407
+ const reqs = typeof proc._getActiveRequests === "function" ? proc._getActiveRequests() : [];
408
+ return countByType(reqs);
409
+ }
410
+ function readPolkadotApiVersion() {
411
+ try {
412
+ const candidates = [
413
+ path2.join(process.cwd(), "node_modules/polkadot-api/package.json"),
414
+ path2.join(process.cwd(), "../node_modules/polkadot-api/package.json")
415
+ ];
416
+ for (const p of candidates) {
417
+ if (fs2.existsSync(p)) {
418
+ const pkg = JSON.parse(fs2.readFileSync(p, "utf-8"));
419
+ return typeof pkg.version === "string" ? pkg.version : void 0;
420
+ }
421
+ }
422
+ } catch {
423
+ }
424
+ return void 0;
425
+ }
426
+ function buildMemoryReport(input) {
427
+ return {
428
+ schemaVersion: 1,
429
+ toolVersion: VERSION,
430
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
431
+ threshold: { thresholdMb: input.thresholdMb, peakRssMb: input.peak.rssMb },
432
+ deploy: input.deploy,
433
+ memory: { peak: input.peak, stages: input.stages },
434
+ v8: {
435
+ heapStatistics: v8.getHeapStatistics(),
436
+ heapSpaceStatistics: v8.getHeapSpaceStatistics()
437
+ },
438
+ runtime: {
439
+ nodeVersion: process.version,
440
+ platform: process.platform,
441
+ arch: process.arch,
442
+ totalMemMb: Math.round(os.totalmem() / 1024 / 1024),
443
+ freeMemMb: Math.round(os.freemem() / 1024 / 1024),
444
+ cpuCount: os.cpus().length,
445
+ activeHandlesByType: readActiveHandles(),
446
+ activeRequestsByType: readActiveRequests()
447
+ },
448
+ polkadotApi: { version: readPolkadotApiVersion() }
449
+ };
450
+ }
451
+ function maybeWriteMemoryReport(input) {
452
+ const thresholdMb = input.thresholdMbOverride ?? (process.env.BULLETIN_DEPLOY_MEM_REPORT_THRESHOLD_MB ? Number(process.env.BULLETIN_DEPLOY_MEM_REPORT_THRESHOLD_MB) : DEFAULT_THRESHOLD_MB);
453
+ const peakRssMb = input.peak.rssMb;
454
+ if (process.env.BULLETIN_DEPLOY_MEM_REPORT === "0") {
455
+ return { status: "disabled", thresholdMb, peakRssMb };
456
+ }
457
+ if (!Number.isFinite(thresholdMb) || peakRssMb < thresholdMb) {
458
+ return { status: "below-threshold", thresholdMb, peakRssMb };
459
+ }
460
+ const isInternal = input.isInternal ?? isInternalContext;
461
+ if (!isInternal()) {
462
+ return { status: "not-internal", thresholdMb, peakRssMb };
463
+ }
464
+ const report = buildMemoryReport({
465
+ thresholdMb,
466
+ peak: input.peak,
467
+ stages: input.stages,
468
+ deploy: input.deploy
469
+ });
470
+ const outDir = input.outputDir ?? process.cwd();
471
+ const filePath = path2.join(outDir, ".bulletin-memory-report.json");
472
+ const write = input.writeFile ?? ((p, c) => fs2.writeFileSync(p, c));
473
+ try {
474
+ write(filePath, JSON.stringify(report, null, 2));
475
+ } catch {
476
+ }
477
+ if (input.onSentryAttach) input.onSentryAttach(report);
478
+ return { status: "written", thresholdMb, peakRssMb, path: filePath };
479
+ }
480
+ function sampleFromBytes(m) {
481
+ return { rssMb: toMb2(m.rss), heapUsedMb: toMb2(m.heapUsed), externalMb: toMb2(m.external), arrayBuffersMb: toMb2(m.arrayBuffers) };
482
+ }
483
+
302
484
  export {
485
+ DEFAULT_THRESHOLD_MB,
486
+ buildMemoryReport,
487
+ maybeWriteMemoryReport,
488
+ sampleFromBytes,
303
489
  VERSION,
304
490
  isInternalContextFromSignals,
305
491
  isInternalContext,
@@ -314,7 +500,9 @@ export {
314
500
  getDeployAttributes,
315
501
  isExpectedError,
316
502
  withSpan,
503
+ sampleMemory,
317
504
  withDeploySpan,
505
+ setDeployReportContext,
318
506
  setDeployAttribute,
319
507
  setDeploySentryTag,
320
508
  captureWarning,
package/dist/deploy.d.ts CHANGED
@@ -50,13 +50,27 @@ declare function merkleize(directoryPath: string, outputCarPath: string): Promis
50
50
  carPath: string;
51
51
  cid: string;
52
52
  }>;
53
- declare function storeDirectory(directoryPath: string, provider?: ExistingProvider, password?: string, jsMerkle?: boolean): Promise<{
53
+ declare function computeStorageCid(chunks: Uint8Array[]): string;
54
+ interface StoreDirectoryOptions {
55
+ provider?: ExistingProvider;
56
+ password?: string;
57
+ jsMerkle?: boolean;
58
+ /**
59
+ * Fires exactly once, right after the CAR has been merkleized + encrypted
60
+ * and the final storage CID is known, but BEFORE the chunk upload to
61
+ * Bulletin starts. Use to kick off parallel side-effects (e.g. the
62
+ * gh-pages mirror) that can run concurrently with the slow upload. The
63
+ * returned promise is awaited at the end of the deploy; errors are passed
64
+ * through to the caller so they can decide fatal / non-fatal policy.
65
+ */
66
+ onCarReady?: (carBytes: Uint8Array, storageCid: string) => Promise<void> | void;
67
+ }
68
+ declare function storeDirectory(directoryPath: string, providerOrOptions?: ExistingProvider | StoreDirectoryOptions, password?: string, jsMerkle?: boolean): Promise<{
54
69
  storageCid: string;
55
70
  ipfsCid: string;
56
71
  carBytes: Uint8Array;
57
72
  }>;
58
73
  interface DeployOptions {
59
- playground?: boolean;
60
74
  mnemonic?: string;
61
75
  /** Optional derivation path applied to the mnemonic (e.g. "//deploy/3"). Defaults to "" (root key). */
62
76
  derivationPath?: string;
@@ -89,4 +103,4 @@ interface DeployOptions {
89
103
  declare function estimateUploadBytes(content: DeployContent): Promise<number | null>;
90
104
  declare function deploy(content: DeployContent, domainName?: string | null, options?: DeployOptions): Promise<DeployResult>;
91
105
 
92
- export { DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, type DeployContent, type DeployOptions, type DeployResult, ENCRYPT_KEY_LEN, ENCRYPT_MAGIC, ENCRYPT_NONCE_LEN, ENCRYPT_PBKDF2_ITERATIONS, ENCRYPT_SALT_LEN, ENCRYPT_TAG_LEN, EXIT_CODE_NO_RETRY, NonRetryableError, chunk, createCID, deploy, deriveRootSigner, encodeContenthash, encryptContent, estimateUploadBytes, friendlyChainError, hasIPFS, isConnectionError, merkleize, storeChunkedContent, storeDirectory, storeFile };
106
+ export { DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, type DeployContent, type DeployOptions, type DeployResult, ENCRYPT_KEY_LEN, ENCRYPT_MAGIC, ENCRYPT_NONCE_LEN, ENCRYPT_PBKDF2_ITERATIONS, ENCRYPT_SALT_LEN, ENCRYPT_TAG_LEN, EXIT_CODE_NO_RETRY, NonRetryableError, type StoreDirectoryOptions, chunk, computeStorageCid, createCID, deploy, deriveRootSigner, encodeContenthash, encryptContent, estimateUploadBytes, friendlyChainError, hasIPFS, isConnectionError, merkleize, storeChunkedContent, storeDirectory, storeFile };
package/dist/deploy.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  EXIT_CODE_NO_RETRY,
11
11
  NonRetryableError,
12
12
  chunk,
13
+ computeStorageCid,
13
14
  createCID,
14
15
  deploy,
15
16
  deriveRootSigner,
@@ -23,12 +24,12 @@ import {
23
24
  storeChunkedContent,
24
25
  storeDirectory,
25
26
  storeFile
26
- } from "./chunk-K7TA53E2.js";
27
- import "./chunk-3C7PWPPG.js";
28
- import "./chunk-UZVOH3HB.js";
29
- import "./chunk-QILGABSF.js";
27
+ } from "./chunk-G3YCSH44.js";
28
+ import "./chunk-OCOCVZQV.js";
29
+ import "./chunk-2Q2WSKFD.js";
30
+ import "./chunk-Q42TQHNL.js";
31
+ import "./chunk-B7GUYYAN.js";
30
32
  import "./chunk-JHNW2EKY.js";
31
- import "./chunk-LF3XAUCI.js";
32
33
  import "./chunk-QGM4M3NI.js";
33
34
  export {
34
35
  DEFAULT_BULLETIN_RPC,
@@ -42,6 +43,7 @@ export {
42
43
  EXIT_CODE_NO_RETRY,
43
44
  NonRetryableError,
44
45
  chunk,
46
+ computeStorageCid,
45
47
  createCID,
46
48
  deploy,
47
49
  deriveRootSigner,
package/dist/dotns.js CHANGED
@@ -29,9 +29,9 @@ import {
29
29
  simulateUserStatus,
30
30
  stripTrailingDigits,
31
31
  validateDomainLabel
32
- } from "./chunk-3C7PWPPG.js";
32
+ } from "./chunk-OCOCVZQV.js";
33
+ import "./chunk-Q42TQHNL.js";
33
34
  import "./chunk-JHNW2EKY.js";
34
- import "./chunk-LF3XAUCI.js";
35
35
  import "./chunk-QGM4M3NI.js";
36
36
  export {
37
37
  CONNECTION_TIMEOUT_MS,
@@ -34,6 +34,30 @@ interface MirrorManifest {
34
34
  declare class MirrorSkipped extends Error {
35
35
  constructor(reason: string);
36
36
  }
37
+ interface FreshnessPollResult {
38
+ verified: boolean;
39
+ attempts: number;
40
+ durationMs: number;
41
+ lastCid: string | null;
42
+ lastStatus: number;
43
+ }
44
+ /**
45
+ * Poll the mirror's manifest URL until its `cid` field equals the expected
46
+ * value. Pages is CDN-backed — a fresh commit may return HTTP 200 for
47
+ * several seconds to a few minutes while Fastly still serves stale bytes,
48
+ * so we can't just 200-check. Matching the manifest CID proves the edge
49
+ * has picked up the current deploy; the sibling .car URL is then guaranteed
50
+ * fresh for the same commit.
51
+ *
52
+ * Non-fatal to the deploy — returns `{ verified: false }` if the poll times
53
+ * out so the caller can log a warning without aborting a deploy whose
54
+ * on-chain state is already good.
55
+ */
56
+ declare function pollMirrorFreshness(mirrorUrl: string, expectedCid: string, opts?: {
57
+ timeoutMs?: number;
58
+ intervalMs?: number;
59
+ fetchFn?: typeof fetch;
60
+ }): Promise<FreshnessPollResult>;
37
61
  declare function parseGitRemoteUrl(url: string): {
38
62
  owner: string;
39
63
  repo: string;
@@ -48,4 +72,4 @@ declare function mirrorUrl(owner: string, repo: string, domainFilename: string):
48
72
  declare function buildManifest(input: Omit<MirrorInput, "carBytes" | "repoPath" | "githubToken">): MirrorManifest;
49
73
  declare function mirrorToGitHubPages(input: MirrorInput): Promise<MirrorResult>;
50
74
 
51
- export { GH_PAGES_MIRROR_BRANCH, GH_PAGES_MIRROR_DIR, GH_PAGES_MIRROR_MAX_BYTES, type MirrorInput, type MirrorManifest, type MirrorResult, MirrorSkipped, buildManifest, mirrorToGitHubPages, mirrorUrl, normalizeDomainFilename, parseGitRemoteUrl, resolveOwnerRepo, resolveSourceCommit };
75
+ export { type FreshnessPollResult, GH_PAGES_MIRROR_BRANCH, GH_PAGES_MIRROR_DIR, GH_PAGES_MIRROR_MAX_BYTES, type MirrorInput, type MirrorManifest, type MirrorResult, MirrorSkipped, buildManifest, mirrorToGitHubPages, mirrorUrl, normalizeDomainFilename, parseGitRemoteUrl, pollMirrorFreshness, resolveOwnerRepo, resolveSourceCommit };
@@ -8,9 +8,10 @@ import {
8
8
  mirrorUrl,
9
9
  normalizeDomainFilename,
10
10
  parseGitRemoteUrl,
11
+ pollMirrorFreshness,
11
12
  resolveOwnerRepo,
12
13
  resolveSourceCommit
13
- } from "./chunk-UZVOH3HB.js";
14
+ } from "./chunk-2Q2WSKFD.js";
14
15
  import "./chunk-QGM4M3NI.js";
15
16
  export {
16
17
  GH_PAGES_MIRROR_BRANCH,
@@ -22,6 +23,7 @@ export {
22
23
  mirrorUrl,
23
24
  normalizeDomainFilename,
24
25
  parseGitRemoteUrl,
26
+ pollMirrorFreshness,
25
27
  resolveOwnerRepo,
26
28
  resolveSourceCommit
27
29
  };
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import {
2
2
  deploy
3
- } from "./chunk-K7TA53E2.js";
3
+ } from "./chunk-G3YCSH44.js";
4
4
  import {
5
5
  DotNS
6
- } from "./chunk-3C7PWPPG.js";
7
- import "./chunk-UZVOH3HB.js";
6
+ } from "./chunk-OCOCVZQV.js";
7
+ import "./chunk-2Q2WSKFD.js";
8
+ import "./chunk-Q42TQHNL.js";
8
9
  import {
9
10
  merkleizeJS
10
- } from "./chunk-QILGABSF.js";
11
+ } from "./chunk-B7GUYYAN.js";
11
12
  import {
12
13
  bootstrapPool,
13
14
  derivePoolAccounts,
@@ -15,7 +16,6 @@ import {
15
16
  fetchPoolAuthorizations,
16
17
  selectAccount
17
18
  } from "./chunk-JHNW2EKY.js";
18
- import "./chunk-LF3XAUCI.js";
19
19
  import "./chunk-QGM4M3NI.js";
20
20
  export {
21
21
  DotNS,
@@ -0,0 +1,93 @@
1
+ import * as v8 from 'node:v8';
2
+
3
+ declare const DEFAULT_THRESHOLD_MB = 1500;
4
+ interface MemorySampleMb {
5
+ rssMb: number;
6
+ heapUsedMb: number;
7
+ externalMb: number;
8
+ arrayBuffersMb: number;
9
+ }
10
+ interface DeployContextForReport {
11
+ /** Sanitized domain label (sanitizeBranch equivalent). No dots. */
12
+ domain?: string;
13
+ /** Sanitized repo slug. Matches what the deploy span already carries. */
14
+ repo?: string;
15
+ deployTag?: string;
16
+ deployMode?: "pool" | "direct" | "external";
17
+ jsMerkle?: boolean;
18
+ chunkCount?: number;
19
+ carBytes?: number;
20
+ reconnects?: number;
21
+ durationMs?: number;
22
+ sentryTraceId?: string;
23
+ }
24
+ interface MemoryReport {
25
+ schemaVersion: 1;
26
+ toolVersion: string;
27
+ generatedAt: string;
28
+ threshold: {
29
+ thresholdMb: number;
30
+ peakRssMb: number;
31
+ };
32
+ deploy: DeployContextForReport;
33
+ memory: {
34
+ peak: MemorySampleMb;
35
+ stages: Record<string, MemorySampleMb>;
36
+ };
37
+ v8: {
38
+ heapStatistics: ReturnType<typeof v8.getHeapStatistics>;
39
+ heapSpaceStatistics: ReturnType<typeof v8.getHeapSpaceStatistics>;
40
+ };
41
+ runtime: {
42
+ nodeVersion: string;
43
+ platform: string;
44
+ arch: string;
45
+ totalMemMb: number;
46
+ freeMemMb: number;
47
+ cpuCount: number;
48
+ activeHandlesByType: Record<string, number>;
49
+ activeRequestsByType: Record<string, number>;
50
+ };
51
+ polkadotApi?: {
52
+ version?: string;
53
+ clientsCreated?: number;
54
+ };
55
+ }
56
+ declare function buildMemoryReport(input: {
57
+ thresholdMb: number;
58
+ peak: MemorySampleMb;
59
+ stages: Record<string, MemorySampleMb>;
60
+ deploy: DeployContextForReport;
61
+ }): MemoryReport;
62
+ interface MaybeWriteMemoryReportInput {
63
+ peak: MemorySampleMb;
64
+ stages: Record<string, MemorySampleMb>;
65
+ deploy: DeployContextForReport;
66
+ /** Where to write the report file. Usually the build directory. */
67
+ outputDir?: string;
68
+ /** Override the default 1500 MB threshold (BULLETIN_DEPLOY_MEM_REPORT_THRESHOLD_MB). */
69
+ thresholdMbOverride?: number;
70
+ /** Hook for tests — defaults to isInternalContext(). */
71
+ isInternal?: () => boolean;
72
+ /** Hook for tests — defaults to real fs writes. */
73
+ writeFile?: (path: string, content: string) => void;
74
+ /** Hook for tests — receives the report when it would be attached to Sentry. */
75
+ onSentryAttach?: (report: MemoryReport) => void;
76
+ }
77
+ interface MemoryReportResult {
78
+ /** Why the report did/didn't fire. Useful for tests and CLI logging. */
79
+ status: "disabled" | "below-threshold" | "not-internal" | "written";
80
+ thresholdMb: number;
81
+ peakRssMb: number;
82
+ path?: string;
83
+ }
84
+ declare function maybeWriteMemoryReport(input: MaybeWriteMemoryReportInput): MemoryReportResult;
85
+ /** Convert a MemorySample with raw byte fields into the MB-shaped sample. */
86
+ declare function sampleFromBytes(m: {
87
+ rss: number;
88
+ heapUsed: number;
89
+ external: number;
90
+ arrayBuffers: number;
91
+ }): MemorySampleMb;
92
+
93
+ export { DEFAULT_THRESHOLD_MB, type DeployContextForReport, type MaybeWriteMemoryReportInput, type MemoryReport, type MemoryReportResult, type MemorySampleMb, buildMemoryReport, maybeWriteMemoryReport, sampleFromBytes };
@@ -0,0 +1,13 @@
1
+ import {
2
+ DEFAULT_THRESHOLD_MB,
3
+ buildMemoryReport,
4
+ maybeWriteMemoryReport,
5
+ sampleFromBytes
6
+ } from "./chunk-Q42TQHNL.js";
7
+ import "./chunk-QGM4M3NI.js";
8
+ export {
9
+ DEFAULT_THRESHOLD_MB,
10
+ buildMemoryReport,
11
+ maybeWriteMemoryReport,
12
+ sampleFromBytes
13
+ };
package/dist/merkle.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  merkleizeJS
3
- } from "./chunk-QILGABSF.js";
3
+ } from "./chunk-B7GUYYAN.js";
4
4
  import "./chunk-QGM4M3NI.js";
5
5
  export {
6
6
  merkleizeJS
@@ -1,3 +1,6 @@
1
+ import { DeployContextForReport } from './memory-report.js';
2
+ import 'node:v8';
3
+
1
4
  declare const VERSION: string;
2
5
  interface InternalContextSignals {
3
6
  githubRepository?: string;
@@ -17,10 +20,14 @@ declare function resolveRunnerType(): string;
17
20
  declare function getDeployAttributes(domain: string): Record<string, string | number | boolean | undefined>;
18
21
  declare function isExpectedError(msg: string): boolean;
19
22
  declare function withSpan<T>(op: string, description: string, attributes: Record<string, string | number | boolean | undefined>, fn: () => T | Promise<T>): Promise<T>;
23
+ declare function sampleMemory(stage: string): void;
20
24
  declare function withDeploySpan<T>(domain: string, fn: () => T | Promise<T>): Promise<T>;
25
+ declare function setDeployReportContext(patch: Partial<DeployContextForReport> & {
26
+ outputDir?: string;
27
+ }): void;
21
28
  declare function setDeployAttribute(key: string, value: string | number | boolean): void;
22
29
  declare function setDeploySentryTag(key: string, value: string): void;
23
30
  declare function captureWarning(message: string, context?: Record<string, unknown>): void;
24
31
  declare function flush(): Promise<void>;
25
32
 
26
- export { type InternalContextSignals, VERSION, captureWarning, flush, getDeployAttributes, initTelemetry, isExpectedError, isInternalContext, isInternalContextFromSignals, resolveRepo, resolveRunner, resolveRunnerType, sanitizeBranch, sanitizeRepo, scrubPaths, setDeployAttribute, setDeploySentryTag, truncateAddress, withDeploySpan, withSpan };
33
+ export { type InternalContextSignals, VERSION, captureWarning, flush, getDeployAttributes, initTelemetry, isExpectedError, isInternalContext, isInternalContextFromSignals, resolveRepo, resolveRunner, resolveRunnerType, sampleMemory, sanitizeBranch, sanitizeRepo, scrubPaths, setDeployAttribute, setDeployReportContext, setDeploySentryTag, truncateAddress, withDeploySpan, withSpan };
package/dist/telemetry.js CHANGED
@@ -10,15 +10,17 @@ import {
10
10
  resolveRepo,
11
11
  resolveRunner,
12
12
  resolveRunnerType,
13
+ sampleMemory,
13
14
  sanitizeBranch,
14
15
  sanitizeRepo,
15
16
  scrubPaths,
16
17
  setDeployAttribute,
18
+ setDeployReportContext,
17
19
  setDeploySentryTag,
18
20
  truncateAddress,
19
21
  withDeploySpan,
20
22
  withSpan
21
- } from "./chunk-LF3XAUCI.js";
23
+ } from "./chunk-Q42TQHNL.js";
22
24
  import "./chunk-QGM4M3NI.js";
23
25
  export {
24
26
  VERSION,
@@ -32,10 +34,12 @@ export {
32
34
  resolveRepo,
33
35
  resolveRunner,
34
36
  resolveRunnerType,
37
+ sampleMemory,
35
38
  sanitizeBranch,
36
39
  sanitizeRepo,
37
40
  scrubPaths,
38
41
  setDeployAttribute,
42
+ setDeployReportContext,
39
43
  setDeploySentryTag,
40
44
  truncateAddress,
41
45
  withDeploySpan,
@@ -8,8 +8,8 @@ import {
8
8
  isPreReleaseVersion,
9
9
  preReleaseWarning,
10
10
  promptYesNo
11
- } from "./chunk-BGLOVKHX.js";
12
- import "./chunk-LF3XAUCI.js";
11
+ } from "./chunk-PA3AS7QR.js";
12
+ import "./chunk-Q42TQHNL.js";
13
13
  import "./chunk-QGM4M3NI.js";
14
14
  export {
15
15
  assessVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulletin-deploy",
3
- "version": "0.6.16",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,11 +24,10 @@
24
24
  },
25
25
  "files": [
26
26
  "dist",
27
- "bin",
28
- "cdm.json"
27
+ "bin"
29
28
  ],
30
29
  "scripts": {
31
- "build": "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts --format esm --dts --clean --target node22",
30
+ "build": "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/memory-report.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts --format esm --dts --clean --target node22",
32
31
  "prepare": "npm run build",
33
32
  "test": "npm run build && node --test test/test.js test/pool.test.js test/helpers/e2e-helpers.test.js",
34
33
  "test:e2e": "npm run build && node --test test/e2e.test.js",
@@ -38,7 +37,6 @@
38
37
  "benchmark": "npm run build && node benchmark.js"
39
38
  },
40
39
  "dependencies": {
41
- "@dotdm/cdm": "^0.5.1",
42
40
  "@ipld/car": "^5.4.3",
43
41
  "@ipld/dag-pb": "^4.1.3",
44
42
  "@noble/hashes": "^1.7.2",