bulletin-deploy 0.7.3 → 0.7.4

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,13 +1,14 @@
1
1
  import {
2
2
  setDeployContext
3
- } from "./chunk-4EQERQRG.js";
3
+ } from "./chunk-YYULF2JX.js";
4
4
  import {
5
5
  DotNS,
6
6
  TX_TIMEOUT_MS,
7
7
  fetchNonce,
8
8
  parseDomainName,
9
- popStatusName
10
- } from "./chunk-M3H3F4FY.js";
9
+ popStatusName,
10
+ verifyNonceAdvanced
11
+ } from "./chunk-YREZFNCC.js";
11
12
  import {
12
13
  MirrorSkipped,
13
14
  mirrorToGitHubPages,
@@ -26,7 +27,7 @@ import {
26
27
  truncateAddress,
27
28
  withDeploySpan,
28
29
  withSpan
29
- } from "./chunk-BYIVK52G.js";
30
+ } from "./chunk-DHQ3JGF4.js";
30
31
  import {
31
32
  merkleizeJS
32
33
  } from "./chunk-B7GUYYAN.js";
@@ -48,7 +49,7 @@ import { sha256 } from "@noble/hashes/sha256";
48
49
  import { blake2b } from "@noble/hashes/blake2b";
49
50
  import { createClient as createPolkadotClient, Enum } from "polkadot-api";
50
51
  import { Binary } from "@polkadot-api/substrate-bindings";
51
- import { getWsProvider } from "polkadot-api/ws-provider";
52
+ import { getWsProvider, WsEvent } from "polkadot-api/ws-provider";
52
53
  import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
53
54
  import { CID } from "multiformats/cid";
54
55
  import { create as createMultihash } from "multiformats/hashes/digest";
@@ -75,12 +76,23 @@ function friendlyChainError(msg) {
75
76
  }
76
77
  var DEFAULT_BULLETIN_RPC = "wss://paseo-bulletin-rpc.polkadot.io";
77
78
  var DEFAULT_POOL_SIZE = 10;
78
- var BULLETIN_RPC = DEFAULT_BULLETIN_RPC;
79
+ var BULLETIN_ENDPOINTS = [DEFAULT_BULLETIN_RPC];
79
80
  var POOL_SIZE = DEFAULT_POOL_SIZE;
81
+ var _deployRpcFailedOver = false;
82
+ function makeBulletinStatusHandler(primary) {
83
+ return (s) => {
84
+ if (s.type === WsEvent.CONNECTED && s.uri !== primary) {
85
+ _deployRpcFailedOver = true;
86
+ setDeployAttribute("deploy.rpc.failed_over", "true");
87
+ captureWarning("Bulletin RPC failover", { from: primary, to: s.uri });
88
+ }
89
+ };
90
+ }
80
91
  var CHUNK_SIZE = 2 * 1024 * 1024;
81
92
  var MAX_FILE_SIZE = 8 * 1024 * 1024;
82
93
  var MAX_RECONNECTIONS = parseInt(process.env.BULLETIN_MAX_RECONNECTIONS ?? "3", 10);
83
94
  var CHUNK_TIMEOUT_MS = parseInt(process.env.BULLETIN_CHUNK_TIMEOUT_MS ?? "180000", 10);
95
+ var CHUNK_MORTALITY_PERIOD = 16;
84
96
  var RETRY_BASE_DELAY_MS = 2e3;
85
97
  var RETRY_MAX_DELAY_MS = 15e3;
86
98
  var WS_HEARTBEAT_TIMEOUT_MS = 3e5;
@@ -147,8 +159,12 @@ function toHashingEnum(mhCode) {
147
159
  }
148
160
  }
149
161
  async function getProvider() {
150
- console.log(` Connecting to Bulletin: ${BULLETIN_RPC}`);
151
- const client = createPolkadotClient(withPolkadotSdkCompat(getWsProvider(BULLETIN_RPC, { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS })));
162
+ const primary = BULLETIN_ENDPOINTS[0];
163
+ console.log(` Connecting to Bulletin: ${primary}`);
164
+ const client = createPolkadotClient(withPolkadotSdkCompat(getWsProvider(
165
+ BULLETIN_ENDPOINTS,
166
+ { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
167
+ )));
152
168
  const unsafeApi = client.getUnsafeApi();
153
169
  try {
154
170
  await cryptoWaitReady();
@@ -159,10 +175,10 @@ async function getProvider() {
159
175
  if (!selected) {
160
176
  const best = authorizations.reduce((a, b) => a.transactions > b.transactions ? a : b);
161
177
  console.log(` All pool accounts low on capacity, auto-authorizing account ${best.index}...`);
162
- await ensureAuthorized(unsafeApi, best.address, BULLETIN_RPC, `pool account ${best.index}`);
178
+ await ensureAuthorized(unsafeApi, best.address, BULLETIN_ENDPOINTS[0], `pool account ${best.index}`);
163
179
  selected = best;
164
180
  } else {
165
- await ensureAuthorized(unsafeApi, selected.address, BULLETIN_RPC, `pool account ${selected.index}`);
181
+ await ensureAuthorized(unsafeApi, selected.address, BULLETIN_ENDPOINTS[0], `pool account ${selected.index}`);
166
182
  }
167
183
  console.log(` Using pool account ${selected.index}: ${selected.address}`);
168
184
  setDeployAttribute("deploy.signer.mode", "pool");
@@ -175,8 +191,12 @@ async function getProvider() {
175
191
  }
176
192
  }
177
193
  async function getDirectProvider(mnemonic, derivationPath = "") {
178
- console.log(` Connecting to Bulletin: ${BULLETIN_RPC}`);
179
- const client = createPolkadotClient(withPolkadotSdkCompat(getWsProvider(BULLETIN_RPC, { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS })));
194
+ const primary = BULLETIN_ENDPOINTS[0];
195
+ console.log(` Connecting to Bulletin: ${primary}`);
196
+ const client = createPolkadotClient(withPolkadotSdkCompat(getWsProvider(
197
+ BULLETIN_ENDPOINTS,
198
+ { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
199
+ )));
180
200
  const unsafeApi = client.getUnsafeApi();
181
201
  const { signer, ss58 } = deriveRootSigner(mnemonic, derivationPath);
182
202
  console.log(` Using direct signer: ${ss58}${derivationPath ? ` (path: ${derivationPath})` : ""}`);
@@ -195,8 +215,9 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
195
215
  return { client, unsafeApi, signer, ss58 };
196
216
  }
197
217
  var MAX_BEST_CHAIN_DROPS = 5;
198
- function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction", rpc, senderSS58, expectedNonce, timeoutMs } = {}) {
218
+ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction", rpc, senderSS58, expectedNonce, timeoutMs, fetchNonce: fetchNonceOverride } = {}) {
199
219
  const timeout = timeoutMs ?? TX_TIMEOUT_MS;
220
+ const _fetchNonce = fetchNonceOverride ?? fetchNonce;
200
221
  return new Promise((resolve2, reject) => {
201
222
  let settled = false;
202
223
  let sub;
@@ -211,14 +232,15 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
211
232
  }
212
233
  fn(...args);
213
234
  };
214
- const tryNonceFallback = async () => {
235
+ const tryNonceFallback = async (subscriptionErrored) => {
215
236
  if (!rpc || !senderSS58 || expectedNonce == null) return false;
216
237
  try {
217
- const currentNonce = await fetchNonce(rpc, senderSS58);
238
+ const endpoints = Array.isArray(rpc) ? rpc : [rpc];
239
+ const verified = await verifyNonceAdvanced(endpoints, senderSS58, expectedNonce);
218
240
  if (settled) return true;
219
- if (currentNonce > expectedNonce) {
220
- console.log(` ${label}: nonce advanced (${expectedNonce} -> ${currentNonce}), tx was included`);
221
- settle(resolve2)({ value: onSuccess(), viaFallback: true });
241
+ if (verified.advanced) {
242
+ console.log(` ${label}: nonce advanced past ${expectedNonce} (witnessed by ${verified.witnessRpc}), tx was included`);
243
+ settle(resolve2)({ value: onSuccess(), viaFallback: true, subscriptionError: subscriptionErrored });
222
244
  return true;
223
245
  }
224
246
  } catch (e) {
@@ -229,7 +251,7 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
229
251
  };
230
252
  const timer = setTimeout(async () => {
231
253
  if (settled) return;
232
- if (await tryNonceFallback()) return;
254
+ if (await tryNonceFallback(false)) return;
233
255
  settle(reject)(new Error(`${label} timed out after ${timeout / 1e3}s waiting for block confirmation`));
234
256
  }, timeout);
235
257
  sub = tx.signSubmitAndWatch(signer, txOpts).subscribe({
@@ -237,7 +259,7 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
237
259
  if (event.type === "txBestBlocksState") {
238
260
  if (event.found) {
239
261
  if (event.ok) {
240
- settle(resolve2)({ value: onSuccess(event), viaFallback: false });
262
+ settle(resolve2)({ value: onSuccess(event), viaFallback: false, subscriptionError: false });
241
263
  } else {
242
264
  settle(reject)(new Error(`${label} dispatch error`));
243
265
  }
@@ -245,7 +267,7 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
245
267
  dropCount++;
246
268
  if (dropCount >= MAX_BEST_CHAIN_DROPS) {
247
269
  console.log(` ${label}: tx dropped ${dropCount} times, checking nonce...`);
248
- if (await tryNonceFallback()) return;
270
+ if (await tryNonceFallback(true)) return;
249
271
  settle(reject)(new Error(`${label} tx dropped from best chain ${dropCount} times`));
250
272
  } else {
251
273
  console.log(` ${label}: tx dropped from best chain (${dropCount}/${MAX_BEST_CHAIN_DROPS}), waiting...`);
@@ -260,16 +282,16 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
260
282
  });
261
283
  });
262
284
  }
263
- async function storeChunk(unsafeApi, signer, chunkBytes, nonce, ss58) {
285
+ async function storeChunk(unsafeApi, signer, chunkBytes, nonce, ss58, opts = {}) {
264
286
  const hashCode = 18;
265
287
  const cid = createCID(chunkBytes, CID_CONFIG.codec, hashCode);
266
288
  const tx = unsafeApi.tx.TransactionStorage.store_with_cid_config({ cid: { codec: BigInt(CID_CONFIG.codec), hashing: toHashingEnum(hashCode) }, data: Binary.fromBytes(chunkBytes) });
267
- const txOpts = { mortality: { mortal: true, period: 256 }, nonce };
268
- const { value, viaFallback } = await watchTransaction(tx, signer, txOpts, () => {
289
+ const txOpts = { mortality: { mortal: true, period: CHUNK_MORTALITY_PERIOD }, nonce };
290
+ const { value, viaFallback, subscriptionError } = await watchTransaction(tx, signer, txOpts, () => {
269
291
  console.log(` CID: ${cid.toString()}`);
270
292
  return { cid, len: chunkBytes.length };
271
- }, { label: `chunk(nonce:${nonce})`, rpc: BULLETIN_RPC, senderSS58: ss58, expectedNonce: nonce, timeoutMs: CHUNK_TIMEOUT_MS });
272
- return { ...value, viaFallback };
293
+ }, { label: `chunk(nonce:${nonce})`, rpc: BULLETIN_ENDPOINTS, senderSS58: ss58, expectedNonce: nonce, timeoutMs: CHUNK_TIMEOUT_MS, fetchNonce: opts.fetchNonce });
294
+ return { ...value, viaFallback, subscriptionError };
273
295
  }
274
296
  async function storeFile(contentBytes, { client: existingClient, unsafeApi: existingApi, signer: existingSigner } = {}) {
275
297
  console.log(`
@@ -305,7 +327,8 @@ async function storeFile(contentBytes, { client: existingClient, unsafeApi: exis
305
327
  throw e;
306
328
  }
307
329
  }
308
- async function storeChunkedContent(chunks, { client: existingClient, unsafeApi: existingApi, signer: existingSigner, ss58: existingSS58, reconnect } = {}) {
330
+ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi: existingApi, signer: existingSigner, ss58: existingSS58, reconnect, fetchNonce: fetchNonceOverride } = {}) {
331
+ const _fetchNonce = fetchNonceOverride ?? fetchNonce;
309
332
  console.log(`
310
333
  Chunks: ${chunks.length}`);
311
334
  const totalBytes = chunks.reduce((s, c) => s + c.length, 0);
@@ -337,7 +360,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
337
360
  Account has insufficient authorization for this upload (need ${requiredTxs} txs / ${(totalBytes / 1e6).toFixed(1)}MB, have ${txsRemaining} txs / ${Number(bytesRemaining) / 1e6}MB)`);
338
361
  console.log(` Attempting to re-authorize with Alice...`);
339
362
  try {
340
- await ensureAuthorized(unsafeApi, ss58, BULLETIN_RPC, void 0, { txs: requiredTxs, bytes: requiredBytes });
363
+ await ensureAuthorized(unsafeApi, ss58, BULLETIN_ENDPOINTS[0], void 0, { txs: requiredTxs, bytes: requiredBytes });
341
364
  console.log(` Re-authorization successful`);
342
365
  } catch (e) {
343
366
  throw new NonRetryableError(`Account ${ss58} has insufficient Bulletin authorization quota and auto-authorization via Alice failed (${e.message}). Authorize the account on-chain.`);
@@ -368,7 +391,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
368
391
  sampleMemory(`reconnect_${reconnectionsUsed}_after`);
369
392
  }
370
393
  try {
371
- let startNonce = await fetchNonce(BULLETIN_RPC, ss58);
394
+ let startNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
372
395
  console.log(` Starting nonce: ${startNonce}`);
373
396
  const BATCH_SIZE = 2;
374
397
  const MAX_CHUNK_RETRIES = 3;
@@ -394,7 +417,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
394
417
  }
395
418
  const nonce = assignedNonces.get(i);
396
419
  console.log(` [${i + 1}/${chunks.length}] ${(chunkData.length / 1024 / 1024).toFixed(2)} MB (nonce: ${nonce})`);
397
- return storeChunk(unsafeApi, signer, chunkData, nonce, ss58);
420
+ return storeChunk(unsafeApi, signer, chunkData, nonce, ss58, { fetchNonce: fetchNonceOverride });
398
421
  });
399
422
  const results = await Promise.allSettled(batchPromises);
400
423
  results.forEach((r, j) => {
@@ -404,11 +427,11 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
404
427
  }
405
428
  });
406
429
  const failures = results.map((r, j) => r.status === "rejected" ? { index: batchIndices[j], chunkData: batchChunks[j], error: r.reason } : null).filter(Boolean);
407
- const succeededViaFallback = results.some((r) => r.status === "fulfilled" && r.value.viaFallback);
408
- const needsReconnect = failures.some((f) => isConnectionError(f.error)) || succeededViaFallback;
430
+ const subscriptionErrored = results.some((r) => r.status === "fulfilled" && r.value.subscriptionError);
431
+ const needsReconnect = failures.some((f) => isConnectionError(f.error)) || subscriptionErrored;
409
432
  if (needsReconnect && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
410
433
  await doReconnect();
411
- const currentNonce = await fetchNonce(BULLETIN_RPC, ss58);
434
+ const currentNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
412
435
  for (const idx of batchIndices) {
413
436
  const chunkNonce = assignedNonces.get(idx);
414
437
  if (chunkNonce !== void 0 && chunkNonce < currentNonce && stored[idx] === null) {
@@ -439,7 +462,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
439
462
  }
440
463
  }
441
464
  try {
442
- const currentNonce = await fetchNonce(BULLETIN_RPC, ss58);
465
+ const currentNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
443
466
  const originalNonce = assignedNonces.get(fail.index);
444
467
  if (originalNonce !== void 0 && originalNonce < currentNonce) {
445
468
  console.log(` Chunk ${fail.index + 1}: nonce ${originalNonce} consumed (current=${currentNonce}), treating as included`);
@@ -449,7 +472,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
449
472
  break;
450
473
  }
451
474
  const retryNonce = originalNonce ?? currentNonce;
452
- const result2 = await storeChunk(unsafeApi, signer, fail.chunkData, retryNonce, ss58);
475
+ const result2 = await storeChunk(unsafeApi, signer, fail.chunkData, retryNonce, ss58, { fetchNonce: fetchNonceOverride });
453
476
  stored[fail.index] = result2;
454
477
  assignedNonces.delete(fail.index);
455
478
  retried = true;
@@ -495,7 +518,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
495
518
  const MAX_ROOT_RETRIES = 3;
496
519
  let result;
497
520
  for (let rootAttempt = 1; rootAttempt <= MAX_ROOT_RETRIES; rootAttempt++) {
498
- const rootNonce = await fetchNonce(BULLETIN_RPC, ss58);
521
+ const rootNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
499
522
  console.log(` Storing root node (nonce: ${rootNonce})...`);
500
523
  const rootTx = unsafeApi.tx.TransactionStorage.store_with_cid_config({ cid: { codec: BigInt(112), hashing: toHashingEnum(hashCode) }, data: Binary.fromBytes(dagBytes) });
501
524
  const rootTxOpts = { mortality: { mortal: true, period: 256 }, nonce: rootNonce };
@@ -504,9 +527,9 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
504
527
  console.log(` Root CID: ${rootCid.toString()}
505
528
  `);
506
529
  return rootCid.toString();
507
- }, { label: "root-node", rpc: BULLETIN_RPC, senderSS58: ss58, expectedNonce: rootNonce, timeoutMs: CHUNK_TIMEOUT_MS });
530
+ }, { label: "root-node", rpc: BULLETIN_ENDPOINTS, senderSS58: ss58, expectedNonce: rootNonce, timeoutMs: CHUNK_TIMEOUT_MS, fetchNonce: fetchNonceOverride });
508
531
  result = watchResult.value;
509
- if (watchResult.viaFallback && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
532
+ if (watchResult.subscriptionError && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
510
533
  await doReconnect();
511
534
  }
512
535
  break;
@@ -665,7 +688,9 @@ async function estimateUploadBytes(content) {
665
688
  }
666
689
  }
667
690
  async function deploy(content, domainName = null, options = {}) {
668
- BULLETIN_RPC = options.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
691
+ const userRpc = options.rpc ?? process.env.BULLETIN_RPC;
692
+ BULLETIN_ENDPOINTS = userRpc ? [userRpc, ...[DEFAULT_BULLETIN_RPC].filter((e) => e !== userRpc)] : [DEFAULT_BULLETIN_RPC];
693
+ _deployRpcFailedOver = false;
669
694
  POOL_SIZE = options.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
670
695
  initTelemetry();
671
696
  const randomSuffix = Math.floor(Math.random() * 100).toString().padStart(2, "0");
@@ -762,7 +787,7 @@ async function deploy(content, domainName = null, options = {}) {
762
787
  const uploadBytes = Math.ceil(estimated * 1.2);
763
788
  const chunksNeeded = Math.max(1, Math.ceil(uploadBytes / CHUNK_SIZE));
764
789
  const needs = { txs: BigInt(chunksNeeded + 2), bytes: BigInt(uploadBytes) };
765
- await topUpBy(provider.ss58, BULLETIN_RPC, needs, "uploader");
790
+ await topUpBy(provider.ss58, BULLETIN_ENDPOINTS[0], needs, "uploader");
766
791
  }
767
792
  console.log("\n" + "=".repeat(60));
768
793
  console.log("Storage");
@@ -808,7 +833,7 @@ async function deploy(content, domainName = null, options = {}) {
808
833
  carBytes,
809
834
  cid: predictedCid,
810
835
  toolVersion: VERSION,
811
- bulletinRpc: options.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC,
836
+ bulletinRpc: BULLETIN_ENDPOINTS[0],
812
837
  encrypted: Boolean(options.password)
813
838
  }).catch((err) => err instanceof Error ? err : new Error(String(err)));
814
839
  }
@@ -939,6 +964,7 @@ async function deploy(content, domainName = null, options = {}) {
939
964
  console.log("\n" + "=".repeat(60) + "\n");
940
965
  return { domainName: name, fullDomain: `${name}.dot`, cid, ipfsCid };
941
966
  } finally {
967
+ if (_deployRpcFailedOver) setDeployAttribute("deploy.rpc.failed_over", "true");
942
968
  provider?.client.destroy();
943
969
  }
944
970
  });
@@ -950,6 +976,7 @@ export {
950
976
  friendlyChainError,
951
977
  DEFAULT_BULLETIN_RPC,
952
978
  DEFAULT_POOL_SIZE,
979
+ CHUNK_MORTALITY_PERIOD,
953
980
  isConnectionError,
954
981
  deriveRootSigner,
955
982
  createCID,
@@ -0,0 +1,155 @@
1
+ // src/run-state.ts
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+
6
+ // package.json
7
+ var package_default = {
8
+ name: "bulletin-deploy",
9
+ version: "0.7.4",
10
+ private: false,
11
+ repository: {
12
+ type: "git",
13
+ url: "https://github.com/paritytech/bulletin-deploy.git"
14
+ },
15
+ publishConfig: {
16
+ registry: "https://registry.npmjs.org",
17
+ access: "public"
18
+ },
19
+ type: "module",
20
+ main: "./dist/index.js",
21
+ types: "./dist/index.d.ts",
22
+ bin: {
23
+ "bulletin-deploy": "./bin/bulletin-deploy"
24
+ },
25
+ exports: {
26
+ ".": {
27
+ types: "./dist/index.d.ts",
28
+ import: "./dist/index.js"
29
+ }
30
+ },
31
+ files: [
32
+ "dist",
33
+ "bin"
34
+ ],
35
+ scripts: {
36
+ 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 src/run-state.ts --format esm --dts --clean --target node22",
37
+ prepare: "npm run build",
38
+ test: "npm run build && node --test test/test.js test/pool.test.js test/helpers/e2e-helpers.test.js",
39
+ "test:e2e": "npm run build && node --test test/e2e.test.js",
40
+ "test:e2e:smoke": "bash scripts/e2e-pass.sh smoke",
41
+ "test:e2e:pr": "bash scripts/e2e-pass.sh pr",
42
+ "test:e2e:nightly": "bash scripts/e2e-pass.sh nightly",
43
+ benchmark: "npm run build && node benchmark.js"
44
+ },
45
+ dependencies: {
46
+ "@ipld/car": "^5.4.3",
47
+ "@ipld/dag-pb": "^4.1.3",
48
+ "@noble/hashes": "^1.7.2",
49
+ "@parity/dotns-cli": "0.5.6",
50
+ "@polkadot-api/substrate-bindings": "^0.16.5",
51
+ "@polkadot-labs/hdkd": "^0.0.25",
52
+ "@polkadot-labs/hdkd-helpers": "^0.0.26",
53
+ "@polkadot/keyring": "^13.0.0",
54
+ "@polkadot/util-crypto": "^13.0.0",
55
+ "@sentry/node": "^9.14.0",
56
+ "ipfs-unixfs": "^11.2.0",
57
+ "ipfs-unixfs-importer": "^16.1.4",
58
+ multiformats: "^13.4.1",
59
+ "polkadot-api": "^1.23.1",
60
+ viem: "^2.30.5"
61
+ },
62
+ devDependencies: {
63
+ "@types/node": "^22.0.0",
64
+ tsup: "^8.5.0",
65
+ typescript: "^5.9.3"
66
+ },
67
+ minimumVersion: "0.5.6",
68
+ engines: {
69
+ node: ">=22"
70
+ }
71
+ };
72
+
73
+ // src/run-state.ts
74
+ var VERSION = package_default.version;
75
+ function resolveStateDir() {
76
+ if (process.platform === "darwin") {
77
+ return path.join(os.homedir(), "Library", "Application Support", "bulletin-deploy");
78
+ }
79
+ if (process.platform === "win32") {
80
+ const base2 = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
81
+ return path.join(base2, "bulletin-deploy");
82
+ }
83
+ const base = process.env.XDG_STATE_HOME && process.env.XDG_STATE_HOME.length > 0 ? process.env.XDG_STATE_HOME : path.join(os.homedir(), ".local", "state");
84
+ return path.join(base, "bulletin-deploy");
85
+ }
86
+ function stateFilePath() {
87
+ return path.join(resolveStateDir(), "last-run.json");
88
+ }
89
+ function loadRunState() {
90
+ try {
91
+ const raw = fs.readFileSync(stateFilePath(), "utf-8");
92
+ const parsed = JSON.parse(raw);
93
+ if (!parsed || typeof parsed !== "object") return null;
94
+ return parsed;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ function writeRunState(patch) {
100
+ try {
101
+ const dir = resolveStateDir();
102
+ fs.mkdirSync(dir, { recursive: true });
103
+ const file = stateFilePath();
104
+ let existing = {};
105
+ try {
106
+ const raw = fs.readFileSync(file, "utf-8");
107
+ const parsed = JSON.parse(raw);
108
+ if (parsed && typeof parsed === "object") existing = parsed;
109
+ } catch {
110
+ }
111
+ const merged = { ...existing, ...patch };
112
+ const tmp = `${file}.${process.pid}.tmp`;
113
+ fs.writeFileSync(tmp, JSON.stringify(merged), { encoding: "utf-8" });
114
+ fs.renameSync(tmp, file);
115
+ } catch {
116
+ }
117
+ }
118
+ function isPidAlive(pid) {
119
+ try {
120
+ process.kill(pid, 0);
121
+ return true;
122
+ } catch (err) {
123
+ const code = err.code;
124
+ if (code === "EPERM") return true;
125
+ return false;
126
+ }
127
+ }
128
+ function shouldSkipStaleWarning(prev) {
129
+ if (prev.pid && isPidAlive(prev.pid)) return true;
130
+ if (prev.toolVersion !== VERSION) return true;
131
+ return false;
132
+ }
133
+ function probablyOomRssMb(override) {
134
+ if (typeof override === "number" && Number.isFinite(override)) return override;
135
+ const env = process.env.BULLETIN_DEPLOY_OOM_HINT_RSS_MB;
136
+ const parsed = env != null ? Number(env) : NaN;
137
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
138
+ return 1800;
139
+ }
140
+ function shouldShowOomHint(prev) {
141
+ if (prev.lastPeakRssMb == null) return false;
142
+ return prev.lastPeakRssMb >= probablyOomRssMb();
143
+ }
144
+
145
+ export {
146
+ package_default,
147
+ VERSION,
148
+ resolveStateDir,
149
+ stateFilePath,
150
+ loadRunState,
151
+ writeRunState,
152
+ shouldSkipStaleWarning,
153
+ probablyOomRssMb,
154
+ shouldShowOomHint
155
+ };