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.
@@ -1,38 +1,43 @@
1
1
  import {
2
- DEFAULT_MNEMONIC,
2
+ setDeployContext
3
+ } from "./chunk-5N4VL73E.js";
4
+ import {
3
5
  DotNS,
4
6
  TX_TIMEOUT_MS,
5
7
  fetchNonce,
6
8
  popStatusName,
7
9
  validateDomainLabel
8
- } from "./chunk-3C7PWPPG.js";
10
+ } from "./chunk-MORJYP3A.js";
9
11
  import {
10
12
  MirrorSkipped,
11
- mirrorToGitHubPages
12
- } from "./chunk-UZVOH3HB.js";
13
- import {
14
- merkleizeJS
15
- } from "./chunk-QILGABSF.js";
16
- import {
17
- derivePoolAccounts,
18
- detectTestnet,
19
- ensureAuthorized,
20
- fetchPoolAuthorizations,
21
- selectAccount,
22
- topUpBy
23
- } from "./chunk-JHNW2EKY.js";
13
+ mirrorToGitHubPages,
14
+ pollMirrorFreshness
15
+ } from "./chunk-2Q2WSKFD.js";
24
16
  import {
25
17
  VERSION,
26
18
  captureWarning,
27
19
  initTelemetry,
28
20
  resolveRunner,
29
21
  resolveRunnerType,
22
+ sampleMemory,
30
23
  setDeployAttribute,
24
+ setDeployReportContext,
31
25
  setDeploySentryTag,
32
26
  truncateAddress,
33
27
  withDeploySpan,
34
28
  withSpan
35
- } from "./chunk-LF3XAUCI.js";
29
+ } from "./chunk-PH43YXEE.js";
30
+ import {
31
+ merkleizeJS
32
+ } from "./chunk-B7GUYYAN.js";
33
+ import {
34
+ derivePoolAccounts,
35
+ detectTestnet,
36
+ ensureAuthorized,
37
+ fetchPoolAuthorizations,
38
+ selectAccount,
39
+ topUpBy
40
+ } from "./chunk-JHNW2EKY.js";
36
41
 
37
42
  // src/deploy.ts
38
43
  import { Buffer } from "buffer";
@@ -52,262 +57,9 @@ import { base58btc } from "multiformats/bases/base58";
52
57
  import * as dagPB from "@ipld/dag-pb";
53
58
  import { UnixFS } from "ipfs-unixfs";
54
59
  import { cryptoWaitReady } from "@polkadot/util-crypto";
55
- import { createCdm } from "@dotdm/cdm";
56
60
  import { getPolkadotSigner } from "polkadot-api/signer";
57
61
  import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
58
62
  import { mnemonicToEntropy, entropyToMiniSecret, ss58Address } from "@polkadot-labs/hdkd-helpers";
59
-
60
- // cdm.json
61
- var cdm_default = {
62
- targets: {
63
- acc2c3b5e912b762: {
64
- "asset-hub": "wss://asset-hub-paseo-rpc.n.dwellir.com",
65
- bulletin: "https://paseo-ipfs.polkadot.io/ipfs"
66
- }
67
- },
68
- dependencies: {
69
- acc2c3b5e912b762: {
70
- "@example/playground-registry": "latest"
71
- }
72
- },
73
- contracts: {
74
- acc2c3b5e912b762: {
75
- "@example/playground-registry": {
76
- version: 6,
77
- address: "0x279585Cb8E8971e34520A3ebbda3E0C4D77C3d97",
78
- abi: [
79
- {
80
- type: "constructor",
81
- inputs: [],
82
- stateMutability: "nonpayable"
83
- },
84
- {
85
- type: "function",
86
- name: "publish",
87
- inputs: [
88
- {
89
- name: "domain",
90
- type: "string"
91
- },
92
- {
93
- name: "metadata_uri",
94
- type: "string"
95
- }
96
- ],
97
- outputs: [],
98
- stateMutability: "nonpayable"
99
- },
100
- {
101
- type: "function",
102
- name: "unpublish",
103
- inputs: [
104
- {
105
- name: "domain",
106
- type: "string"
107
- }
108
- ],
109
- outputs: [],
110
- stateMutability: "nonpayable"
111
- },
112
- {
113
- type: "function",
114
- name: "rateApp",
115
- inputs: [
116
- {
117
- name: "domain",
118
- type: "string"
119
- },
120
- {
121
- name: "rating",
122
- type: "uint8"
123
- },
124
- {
125
- name: "comment_uri",
126
- type: "string"
127
- }
128
- ],
129
- outputs: [],
130
- stateMutability: "nonpayable"
131
- },
132
- {
133
- type: "function",
134
- name: "removeRating",
135
- inputs: [
136
- {
137
- name: "domain",
138
- type: "string"
139
- },
140
- {
141
- name: "reviewer",
142
- type: "address"
143
- }
144
- ],
145
- outputs: [],
146
- stateMutability: "nonpayable"
147
- },
148
- {
149
- type: "function",
150
- name: "getContextId",
151
- inputs: [],
152
- outputs: [
153
- {
154
- name: "",
155
- type: "bytes32"
156
- }
157
- ],
158
- stateMutability: "view"
159
- },
160
- {
161
- type: "function",
162
- name: "getAppCount",
163
- inputs: [],
164
- outputs: [
165
- {
166
- name: "",
167
- type: "uint32"
168
- }
169
- ],
170
- stateMutability: "view"
171
- },
172
- {
173
- type: "function",
174
- name: "getDomainAt",
175
- inputs: [
176
- {
177
- name: "index",
178
- type: "uint32"
179
- }
180
- ],
181
- outputs: [
182
- {
183
- name: "",
184
- type: "tuple",
185
- components: [
186
- {
187
- name: "isSome",
188
- type: "bool"
189
- },
190
- {
191
- name: "value",
192
- type: "string"
193
- }
194
- ]
195
- }
196
- ],
197
- stateMutability: "view"
198
- },
199
- {
200
- type: "function",
201
- name: "getOwnerAppCount",
202
- inputs: [
203
- {
204
- name: "owner",
205
- type: "address"
206
- }
207
- ],
208
- outputs: [
209
- {
210
- name: "",
211
- type: "uint32"
212
- }
213
- ],
214
- stateMutability: "view"
215
- },
216
- {
217
- type: "function",
218
- name: "getOwnerDomainAt",
219
- inputs: [
220
- {
221
- name: "owner",
222
- type: "address"
223
- },
224
- {
225
- name: "index",
226
- type: "uint32"
227
- }
228
- ],
229
- outputs: [
230
- {
231
- name: "",
232
- type: "tuple",
233
- components: [
234
- {
235
- name: "isSome",
236
- type: "bool"
237
- },
238
- {
239
- name: "value",
240
- type: "string"
241
- }
242
- ]
243
- }
244
- ],
245
- stateMutability: "view"
246
- },
247
- {
248
- type: "function",
249
- name: "getSudo",
250
- inputs: [],
251
- outputs: [
252
- {
253
- name: "",
254
- type: "address"
255
- }
256
- ],
257
- stateMutability: "view"
258
- },
259
- {
260
- type: "function",
261
- name: "getMetadataUri",
262
- inputs: [
263
- {
264
- name: "domain",
265
- type: "string"
266
- }
267
- ],
268
- outputs: [
269
- {
270
- name: "",
271
- type: "tuple",
272
- components: [
273
- {
274
- name: "isSome",
275
- type: "bool"
276
- },
277
- {
278
- name: "value",
279
- type: "string"
280
- }
281
- ]
282
- }
283
- ],
284
- stateMutability: "view"
285
- },
286
- {
287
- type: "function",
288
- name: "getOwner",
289
- inputs: [
290
- {
291
- name: "domain",
292
- type: "string"
293
- }
294
- ],
295
- outputs: [
296
- {
297
- name: "",
298
- type: "address"
299
- }
300
- ],
301
- stateMutability: "view"
302
- }
303
- ],
304
- metadataCid: "bafk2bzaceck7veaix4ttzyd6bmwlssgycrrlgilpat2c272nczzlrgnqy6fze"
305
- }
306
- }
307
- }
308
- };
309
-
310
- // src/deploy.ts
311
63
  var NonRetryableError = class extends Error {
312
64
  constructor(message) {
313
65
  super(message);
@@ -337,17 +89,6 @@ function isConnectionError(error) {
337
89
  return /heartbeat timeout|WS halt|Unable to connect/i.test(msg);
338
90
  }
339
91
  var CID_CONFIG = { version: 1, codec: 85, hashCode: 18, hashLength: 32 };
340
- function getGitRemoteUrl() {
341
- try {
342
- const raw = execSync("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
343
- if (raw.startsWith("git@")) {
344
- return raw.replace(/^git@([^:]+):/, "https://$1/").replace(/\.git$/, "");
345
- }
346
- return raw.replace(/\.git$/, "");
347
- } catch {
348
- return null;
349
- }
350
- }
351
92
  function deriveRootSigner(mnemonic, path2 = "") {
352
93
  const entropy = mnemonicToEntropy(mnemonic);
353
94
  const miniSecret = entropyToMiniSecret(entropy);
@@ -356,11 +97,6 @@ function deriveRootSigner(mnemonic, path2 = "") {
356
97
  const signer = getPolkadotSigner(keyPair.publicKey, "Sr25519", keyPair.sign);
357
98
  return { signer, ss58: ss58Address(keyPair.publicKey) };
358
99
  }
359
- function getRegistrySigner(explicitMnemonic, derivationPath) {
360
- const mnemonic = explicitMnemonic || process.env.DOTNS_MNEMONIC || process.env.MNEMONIC || DEFAULT_MNEMONIC;
361
- const { signer, ss58 } = deriveRootSigner(mnemonic, derivationPath ?? "");
362
- return { signer, origin: ss58 };
363
- }
364
100
  function createCID(data, codec = CID_CONFIG.codec, hashCode = CID_CONFIG.hashCode) {
365
101
  let hash;
366
102
  if (hashCode === 45600) hash = blake2b(data, { dkLen: CID_CONFIG.hashLength });
@@ -431,7 +167,7 @@ async function getProvider() {
431
167
  console.log(` Using pool account ${selected.index}: ${selected.address}`);
432
168
  setDeployAttribute("deploy.signer.mode", "pool");
433
169
  setDeployAttribute("deploy.pool.account", truncateAddress(selected.address));
434
- setDeployAttribute("deploy.pool.index", selected.index);
170
+ setDeployAttribute("deploy.pool.index", String(selected.index));
435
171
  return { client, unsafeApi, signer: selected.signer, ss58: selected.address };
436
172
  } catch (e) {
437
173
  client.destroy();
@@ -617,6 +353,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
617
353
  console.log(`
618
354
  Connection lost, reconnecting to Bulletin in ${(delay / 1e3).toFixed(0)}s (${reconnectionsUsed}/${MAX_RECONNECTIONS})...`);
619
355
  captureWarning("WebSocket connection lost, reconnecting", { reconnection: reconnectionsUsed, maxReconnections: MAX_RECONNECTIONS });
356
+ sampleMemory(`reconnect_${reconnectionsUsed}_before`);
620
357
  try {
621
358
  client.destroy();
622
359
  } catch {
@@ -628,6 +365,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
628
365
  signer = fresh.signer;
629
366
  ss58 = fresh.ss58;
630
367
  ownsClient = true;
368
+ sampleMemory(`reconnect_${reconnectionsUsed}_after`);
631
369
  }
632
370
  try {
633
371
  let startNonce = await fetchNonce(BULLETIN_RPC, ss58);
@@ -797,7 +535,7 @@ function chunk(data, size = CHUNK_SIZE) {
797
535
  let offset = 0;
798
536
  while (offset < data.length) {
799
537
  const end = Math.min(offset + size, data.length);
800
- chunks.push(new Uint8Array(data.subarray(offset, end)));
538
+ chunks.push(data.subarray(offset, end));
801
539
  offset = end;
802
540
  }
803
541
  return chunks;
@@ -826,33 +564,78 @@ async function merkleize(directoryPath, outputCarPath) {
826
564
  console.log(` CAR: ${(size / 1024 / 1024).toFixed(2)} MB`);
827
565
  return { carPath: outputCarPath, cid };
828
566
  }
829
- async function storeDirectory(directoryPath, provider = {}, password, jsMerkle) {
567
+ function computeStorageCid(chunks) {
568
+ const hashCode = 18;
569
+ const chunkInfo = chunks.map((c) => ({
570
+ cid: createCID(c, CID_CONFIG.codec, hashCode),
571
+ len: c.length
572
+ }));
573
+ const fileData = new UnixFS({ type: "file", blockSizes: chunkInfo.map((c) => BigInt(c.len)) });
574
+ const dagNode = dagPB.prepare({ Data: fileData.marshal(), Links: chunkInfo.map((c) => ({ Name: "", Tsize: c.len, Hash: c.cid })) });
575
+ const dagBytes = dagPB.encode(dagNode);
576
+ return createCID(dagBytes, 112, hashCode).toString();
577
+ }
578
+ async function storeDirectory(directoryPath, providerOrOptions = {}, password, jsMerkle) {
579
+ const opts = providerOrOptions && ("provider" in providerOrOptions || "onCarReady" in providerOrOptions || "password" in providerOrOptions || "jsMerkle" in providerOrOptions) ? providerOrOptions : { provider: providerOrOptions, password, jsMerkle };
580
+ const provider = opts.provider ?? {};
581
+ password = opts.password;
582
+ jsMerkle = opts.jsMerkle;
830
583
  let carContent;
831
584
  let ipfsCid;
832
585
  const dirBasename = path.basename(directoryPath);
586
+ sampleMemory("storage_start");
833
587
  if (jsMerkle) {
834
588
  const result = await withSpan("deploy.merkleize", "1a. merkleize (js)", { "deploy.directory": dirBasename }, async () => {
835
- return merkleizeJS(directoryPath);
589
+ const r = await merkleizeJS(directoryPath);
590
+ sampleMemory("merkleize_end");
591
+ return r;
836
592
  });
837
593
  carContent = result.carBytes;
838
594
  ipfsCid = result.cid;
839
595
  } else {
840
596
  const carPath = path.join(path.dirname(directoryPath), `${path.basename(directoryPath)}.car`);
841
597
  const { cid } = await withSpan("deploy.merkleize", "1a. merkleize", { "deploy.directory": dirBasename }, async () => {
842
- return merkleize(directoryPath, carPath);
598
+ const r = await merkleize(directoryPath, carPath);
599
+ sampleMemory("merkleize_end");
600
+ return r;
843
601
  });
844
602
  ipfsCid = cid;
845
- carContent = new Uint8Array(fs.readFileSync(carPath));
603
+ carContent = fs.readFileSync(carPath);
846
604
  }
847
605
  if (password) {
848
606
  console.log(` Encrypting CAR file...`);
849
607
  carContent = await encryptContent(carContent, password);
850
608
  console.log(` Encrypted: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
851
609
  }
610
+ const dumpPath = process.env.BULLETIN_DEPLOY_DUMP_CAR ?? path.join(path.dirname(directoryPath), `${path.basename(directoryPath)}.bulletin.car`);
611
+ fs.writeFileSync(dumpPath, carContent);
612
+ console.log(` Pre-upload CAR saved to ${dumpPath} (${(carContent.length / 1024 / 1024).toFixed(2)} MB)`);
852
613
  const carChunks = chunk(carContent, CHUNK_SIZE);
853
- const storageCid = await withSpan("deploy.chunk-upload", "1b. chunk-upload", { "deploy.chunks.total": carChunks.length, "deploy.car.bytes": carContent.length }, async () => {
854
- return storeChunkedContent(carChunks, provider);
614
+ const predictedStorageCid = computeStorageCid(carChunks);
615
+ if (opts.onCarReady) await opts.onCarReady(carContent, predictedStorageCid);
616
+ setDeployReportContext({
617
+ jsMerkle: Boolean(jsMerkle),
618
+ chunkCount: carChunks.length,
619
+ carBytes: carContent.length,
620
+ outputDir: path.dirname(directoryPath)
621
+ });
622
+ setDeployContext({
623
+ chunkCount: carChunks.length,
624
+ totalSize: `${(carContent.length / 1024 / 1024).toFixed(2)} MB`
855
625
  });
626
+ const carMb = String(Math.round(carContent.length / 1024 / 1024 * 100) / 100);
627
+ const storageCid = await withSpan("deploy.chunk-upload", "1b. chunk-upload", { "deploy.chunks.total": String(carChunks.length), "deploy.car.bytes": String(carContent.length), "deploy.car.mb": carMb }, async () => {
628
+ sampleMemory("chunk_upload_start");
629
+ const r = await storeChunkedContent(carChunks, provider);
630
+ sampleMemory("chunk_upload_end");
631
+ return r;
632
+ });
633
+ if (storageCid !== predictedStorageCid) {
634
+ captureWarning("computeStorageCid drift vs storeChunkedContent", {
635
+ predicted: predictedStorageCid,
636
+ uploaded: storageCid
637
+ });
638
+ }
856
639
  return { storageCid, ipfsCid, carBytes: carContent };
857
640
  }
858
641
  async function estimateUploadBytes(content) {
@@ -896,7 +679,7 @@ async function deploy(content, domainName = null, options = {}) {
896
679
  }
897
680
  let cid;
898
681
  let ipfsCid;
899
- let mirrorCarBytes;
682
+ let mirrorPromise = Promise.resolve(null);
900
683
  console.log("\n" + "=".repeat(60));
901
684
  console.log(`DEPLOYING TO TESTNET v${VERSION}`);
902
685
  console.log("=".repeat(60));
@@ -997,10 +780,25 @@ async function deploy(content, domainName = null, options = {}) {
997
780
  console.log(`
998
781
  Mode: Directory`);
999
782
  console.log(` Path: ${contentPath}`);
1000
- const dirResult = await storeDirectory(contentPath, providerWithReconnect, options.password, options.jsMerkle);
1001
- cid = dirResult.storageCid;
1002
- ipfsCid = dirResult.ipfsCid;
1003
- mirrorCarBytes = dirResult.carBytes;
783
+ const { storageCid: sCid, ipfsCid: iCid } = await storeDirectory(contentPath, {
784
+ provider: providerWithReconnect,
785
+ password: options.password,
786
+ jsMerkle: options.jsMerkle,
787
+ onCarReady: (carBytes, predictedCid) => {
788
+ if (options.ghPagesMirror) {
789
+ mirrorPromise = mirrorToGitHubPages({
790
+ domain: name,
791
+ carBytes,
792
+ cid: predictedCid,
793
+ toolVersion: VERSION,
794
+ bulletinRpc: options.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC,
795
+ encrypted: Boolean(options.password)
796
+ }).catch((err) => err instanceof Error ? err : new Error(String(err)));
797
+ }
798
+ }
799
+ });
800
+ cid = sCid;
801
+ ipfsCid = iCid;
1004
802
  } else {
1005
803
  console.log(`
1006
804
  Mode: File`);
@@ -1064,74 +862,40 @@ async function deploy(content, domainName = null, options = {}) {
1064
862
  });
1065
863
  if (options.ghPagesMirror) {
1066
864
  console.log("\n" + "=".repeat(60));
1067
- console.log("GitHub Pages mirror");
1068
- console.log("=".repeat(60));
1069
- if (!mirrorCarBytes) {
1070
- console.log(" Skipped: --gh-pages-mirror only supports directory deploys (no CAR captured for this content type).");
1071
- } else {
1072
- await withSpan("deploy.gh-pages-mirror", "3b. gh-pages-mirror", { "deploy.domain": name }, async () => {
1073
- try {
1074
- const mirror = await mirrorToGitHubPages({
1075
- domain: name,
1076
- carBytes: mirrorCarBytes,
1077
- cid,
1078
- toolVersion: VERSION,
1079
- bulletinRpc: options.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC,
1080
- encrypted: Boolean(options.password)
1081
- });
1082
- console.log(` Mirror: ${mirror.url}`);
1083
- console.log(` Manifest: https://${mirror.owner}.github.io/${mirror.repo}/${mirror.manifestPath}`);
1084
- setDeployAttribute("deploy.gh_pages_url", mirror.url);
1085
- } catch (err) {
1086
- if (err instanceof MirrorSkipped) {
1087
- console.log(` Skipped: ${err.message}`);
1088
- } else {
1089
- const msg = err instanceof Error ? err.message : String(err);
1090
- console.log(` Failed (non-fatal): ${msg}`);
1091
- captureWarning("gh-pages mirror failed", { error: msg.slice(0, 200) });
1092
- }
1093
- }
1094
- });
1095
- }
1096
- }
1097
- if (options.playground) {
1098
- console.log("\n" + "=".repeat(60));
1099
- console.log("Playground Registry");
865
+ console.log("Final checks");
1100
866
  console.log("=".repeat(60));
1101
- await withSpan("deploy.registry", "3. registry", { "deploy.domain": name }, async () => {
1102
- const repoUrl = getGitRemoteUrl();
1103
- const metadata = {};
1104
- if (repoUrl) metadata.repository = repoUrl;
1105
- console.log(`
1106
- Metadata: ${JSON.stringify(metadata)}`);
1107
- const metadataBytes = new Uint8Array(Buffer.from(JSON.stringify(metadata), "utf-8"));
1108
- console.log(` Uploading metadata to Bulletin...`);
1109
- const metadataCid = await storeFile(metadataBytes, provider);
1110
- console.log(` Metadata CID: ${metadataCid}`);
1111
- const { signer, origin } = options.signer && options.signerAddress ? { signer: options.signer, origin: options.signerAddress } : getRegistrySigner(options.mnemonic, options.derivationPath);
1112
- console.log(` Publishing to registry as ${origin}...`);
1113
- const MAX_REGISTRY_RETRIES = 3;
1114
- for (let attempt = 1; attempt <= MAX_REGISTRY_RETRIES; attempt++) {
1115
- const cdm = createCdm(cdm_default, { defaultSigner: signer, defaultOrigin: origin });
1116
- try {
1117
- const registry = cdm.getContract("@example/playground-registry");
1118
- const result = await registry.publish.tx(`${name}.dot`, metadataCid);
1119
- if (!result.ok) throw new Error("Registry publish transaction failed");
1120
- console.log(` Tx: ${result.txHash}`);
1121
- console.log(` Registered ${name}.dot in playground registry!`);
1122
- break;
1123
- } catch (e) {
1124
- if (attempt < MAX_REGISTRY_RETRIES) {
1125
- captureWarning("Registry publish failed, retrying", { attempt, maxRetries: MAX_REGISTRY_RETRIES, error: e.message?.slice(0, 200) });
1126
- console.log(` Attempt ${attempt} failed: ${e.message?.slice(0, 80)}`);
1127
- console.log(` Retrying in 6s...`);
1128
- await new Promise((r) => setTimeout(r, 6e3));
1129
- continue;
1130
- }
1131
- throw e;
1132
- } finally {
1133
- cdm.destroy();
1134
- }
867
+ await withSpan("deploy.gh-pages-mirror", "4. gh-pages-mirror", { "deploy.domain": name }, async () => {
868
+ const mirror = await mirrorPromise;
869
+ if (mirror === null) {
870
+ console.log(" GitHub Pages mirror: skipped (only directory deploys produce a CAR suitable for mirroring).");
871
+ return;
872
+ }
873
+ if (mirror instanceof MirrorSkipped) {
874
+ console.log(` GitHub Pages mirror: skipped \u2014 ${mirror.message}`);
875
+ return;
876
+ }
877
+ if (mirror instanceof Error) {
878
+ console.log(` GitHub Pages mirror: failed (non-fatal) \u2014 ${mirror.message}`);
879
+ captureWarning("gh-pages mirror failed", { error: mirror.message.slice(0, 200) });
880
+ return;
881
+ }
882
+ console.log(` Mirror: ${mirror.url}`);
883
+ console.log(` Manifest: https://${mirror.owner}.github.io/${mirror.repo}/${mirror.manifestPath}`);
884
+ setDeployAttribute("deploy.gh_pages_url", mirror.url);
885
+ process.stdout.write(" Verifying Pages serves this deploy's CAR... ");
886
+ const freshness = await pollMirrorFreshness(mirror.url, cid, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1e4 });
887
+ if (freshness.verified) {
888
+ console.log(`ok (${freshness.attempts} attempt${freshness.attempts === 1 ? "" : "s"}, ${(freshness.durationMs / 1e3).toFixed(0)}s).`);
889
+ } else {
890
+ console.log(`timed out.`);
891
+ console.log(` GitHub Pages last served cid=${freshness.lastCid ?? "n/a"} (expected ${cid}); it should catch up shortly. Non-fatal.`);
892
+ captureWarning("gh-pages mirror freshness poll timed out", {
893
+ url: mirror.url,
894
+ expectedCid: cid,
895
+ lastCid: freshness.lastCid ?? "n/a",
896
+ durationMs: freshness.durationMs,
897
+ attempts: freshness.attempts
898
+ });
1135
899
  }
1136
900
  });
1137
901
  }
@@ -1171,6 +935,7 @@ export {
1171
935
  chunk,
1172
936
  hasIPFS,
1173
937
  merkleize,
938
+ computeStorageCid,
1174
939
  storeDirectory,
1175
940
  estimateUploadBytes,
1176
941
  deploy
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-LF3XAUCI.js";
3
+ } from "./chunk-PH43YXEE.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
@@ -1,10 +1,10 @@
1
- import {
2
- isTestnetSpecName
3
- } from "./chunk-JHNW2EKY.js";
4
1
  import {
5
2
  captureWarning,
6
3
  withSpan
7
- } from "./chunk-LF3XAUCI.js";
4
+ } from "./chunk-PH43YXEE.js";
5
+ import {
6
+ isTestnetSpecName
7
+ } from "./chunk-JHNW2EKY.js";
8
8
 
9
9
  // src/dotns.ts
10
10
  import crypto from "crypto";