bulletin-deploy 0.7.4 → 0.7.6

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,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { deploy, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, NonRetryableError, EXIT_CODE_NO_RETRY } from "../dist/deploy.js";
4
- import { bootstrapPool } from "../dist/pool.js";
5
4
  import { VERSION, setDeployAttribute, captureWarning, closeTelemetry, setRunStateActive, markRelaunchOomHintShown } from "../dist/telemetry.js";
6
5
  import { handleFailedDeploy, preReleaseWarning } from "../dist/version-check.js";
7
6
  import { setDeployContext, installLogCapture, buildCliFlagsSummary } from "../dist/bug-report.js";
@@ -17,7 +16,7 @@ const args = process.argv.slice(2);
17
16
  const flags = {};
18
17
  const positional = [];
19
18
  for (let i = 0; i < args.length; i++) {
20
- if (args[i] === "--bootstrap") { flags.bootstrap = true; }
19
+ if (args[i] === "--bootstrap") { flags.removedBootstrap = true; }
21
20
  else if (args[i] === "--pool-size") { flags.poolSize = parseInt(args[++i], 10); }
22
21
  else if (args[i] === "--mnemonic") { flags.mnemonic = args[++i]; }
23
22
  else if (args[i] === "--derivation-path") { flags.derivationPath = args[++i]; }
@@ -36,12 +35,16 @@ if (flags.version) {
36
35
  process.exit(0);
37
36
  }
38
37
 
39
- if (flags.help || (positional.length === 0 && !flags.bootstrap)) {
38
+ if (flags.removedBootstrap) {
39
+ console.error("Error: --bootstrap was removed from bulletin-deploy. Use bulletin-bootstrap instead.");
40
+ process.exit(1);
41
+ }
42
+
43
+ if (flags.help || positional.length === 0) {
40
44
  console.log(`bulletin-deploy v${VERSION}
41
45
 
42
46
  Usage:
43
47
  bulletin-deploy <build-dir> <domain.dot> Deploy an app
44
- bulletin-deploy --bootstrap Initialize pool accounts
45
48
 
46
49
  Options:
47
50
  --mnemonic "..." DotNS owner mnemonic (or set MNEMONIC env var)
@@ -62,10 +65,9 @@ const rcWarning = preReleaseWarning(VERSION);
62
65
  if (rcWarning) console.error(rcWarning);
63
66
 
64
67
  // ── Crash capture (issue #154) ───────────────────────────────────
65
- // Only wire crash capture for actual deploy / bootstrap runs — skip for
66
- // --help / --version (which exit above) and --bootstrap (one-off ops op,
67
- // no background work worth hinting OOM for).
68
- if (!flags.help && !flags.version && !flags.bootstrap) {
68
+ // Only wire crash capture for actual deploy runs — skip for --help / --version
69
+ // (which exit above).
70
+ if (!flags.help && !flags.version) {
69
71
  // Sanitised argv — positional args + presence-only flag summary. Never
70
72
  // puts a mnemonic/password/RPC/derivation-path on disk, even if the user
71
73
  // passes one on the command line.
@@ -148,58 +150,52 @@ if (!flags.help && !flags.version && !flags.bootstrap) {
148
150
  }
149
151
 
150
152
  try {
151
- if (flags.bootstrap) {
152
- const rpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
153
- const poolSize = flags.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
154
- await bootstrapPool(rpc, poolSize, flags.mnemonic);
155
- } else {
156
- const [buildDir, domain] = positional;
157
- if (!buildDir) { console.error("Error: build directory required"); process.exit(1); }
158
- if (!domain) { console.error("Error: domain required (e.g. my-app.dot)"); process.exit(1); }
159
- if (!fs.existsSync(buildDir)) { console.error(`Error: ${buildDir} does not exist`); process.exit(1); }
160
-
161
- const effectiveRpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
162
- const deployTag = flags.tag ?? process.env.DEPLOY_TAG;
163
- const ci = process.env.GITHUB_ACTIONS === "true" ? {
164
- runUrl: process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
165
- ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}${process.env.GITHUB_RUN_ATTEMPT ? `/attempts/${process.env.GITHUB_RUN_ATTEMPT}` : ""}`
166
- : undefined,
167
- workflow: process.env.GITHUB_WORKFLOW,
168
- job: process.env.GITHUB_JOB,
169
- sha: process.env.GITHUB_SHA,
170
- } : undefined;
171
- setDeployContext({
172
- domain,
173
- rpc: effectiveRpc,
174
- repo: process.env.GITHUB_REPOSITORY,
175
- branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME,
176
- signerMode: flags.mnemonic ? "direct" : "pool",
177
- deployTag,
178
- cliFlags: buildCliFlagsSummary(flags),
179
- ci,
180
- });
181
-
182
- const result = await deploy(buildDir, domain, {
183
- mnemonic: flags.mnemonic,
184
- derivationPath: flags.derivationPath,
185
- rpc: flags.rpc,
186
- poolSize: flags.poolSize,
187
- password: flags.password,
188
- jsMerkle: flags.jsMerkle,
189
- tag: flags.tag,
190
- ghPagesMirror: flags.ghPagesMirror,
191
- });
192
-
193
- const output = process.env.GITHUB_OUTPUT;
194
- if (output) {
195
- fs.appendFileSync(output, `cid=${result.cid}\n`);
196
- fs.appendFileSync(output, `domain=${result.domainName}\n`);
197
- }
153
+ const [buildDir, domain] = positional;
154
+ if (!buildDir) { console.error("Error: build directory required"); process.exit(1); }
155
+ if (!domain) { console.error("Error: domain required (e.g. my-app.dot)"); process.exit(1); }
156
+ if (!fs.existsSync(buildDir)) { console.error(`Error: ${buildDir} does not exist`); process.exit(1); }
157
+
158
+ const effectiveRpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
159
+ const deployTag = flags.tag ?? process.env.DEPLOY_TAG;
160
+ const ci = process.env.GITHUB_ACTIONS === "true" ? {
161
+ runUrl: process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
162
+ ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}${process.env.GITHUB_RUN_ATTEMPT ? `/attempts/${process.env.GITHUB_RUN_ATTEMPT}` : ""}`
163
+ : undefined,
164
+ workflow: process.env.GITHUB_WORKFLOW,
165
+ job: process.env.GITHUB_JOB,
166
+ sha: process.env.GITHUB_SHA,
167
+ } : undefined;
168
+ setDeployContext({
169
+ domain,
170
+ rpc: effectiveRpc,
171
+ repo: process.env.GITHUB_REPOSITORY,
172
+ branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME,
173
+ signerMode: flags.mnemonic ? "direct" : "pool",
174
+ deployTag,
175
+ cliFlags: buildCliFlagsSummary(flags),
176
+ ci,
177
+ });
198
178
 
199
- console.log(`CID: ${result.cid}`);
200
- console.log(`Domain: ${result.domainName}`);
179
+ const result = await deploy(buildDir, domain, {
180
+ mnemonic: flags.mnemonic,
181
+ derivationPath: flags.derivationPath,
182
+ rpc: flags.rpc,
183
+ poolSize: flags.poolSize,
184
+ password: flags.password,
185
+ jsMerkle: flags.jsMerkle,
186
+ tag: flags.tag,
187
+ ghPagesMirror: flags.ghPagesMirror,
188
+ });
189
+
190
+ const output = process.env.GITHUB_OUTPUT;
191
+ if (output) {
192
+ fs.appendFileSync(output, `cid=${result.cid}\n`);
193
+ fs.appendFileSync(output, `domain=${result.domainName}\n`);
201
194
  }
202
- if (!flags.help && !flags.version && !flags.bootstrap) {
195
+
196
+ console.log(`CID: ${result.cid}`);
197
+ console.log(`Domain: ${result.domainName}`);
198
+ if (!flags.help && !flags.version) {
203
199
  try { writeRunState({ status: "succeeded", endedAt: Date.now() }); } catch {}
204
200
  }
205
201
  process.exit(0);
@@ -207,7 +203,7 @@ try {
207
203
  const noRetry = error instanceof NonRetryableError;
208
204
  console.error(`Deployment failed${noRetry ? " (not retryable)" : ""}:`, error.message);
209
205
  await handleFailedDeploy(error);
210
- if (!flags.help && !flags.version && !flags.bootstrap) {
206
+ if (!flags.help && !flags.version) {
211
207
  try { writeRunState({ status: "failed", endedAt: Date.now(), reason: (error?.message ?? String(error)).slice(0, 200) }); } catch {}
212
208
  }
213
209
  process.exit(noRetry ? EXIT_CODE_NO_RETRY : 1);
@@ -9,10 +9,10 @@ import {
9
9
  offerBugReport,
10
10
  scrubSecrets,
11
11
  setDeployContext
12
- } from "./chunk-YYULF2JX.js";
13
- import "./chunk-CQ753LDA.js";
14
- import "./chunk-DHQ3JGF4.js";
15
- import "./chunk-UJP2PZGU.js";
12
+ } from "./chunk-YX62STIA.js";
13
+ import "./chunk-EECNTEAE.js";
14
+ import "./chunk-Y6CTPQS2.js";
15
+ import "./chunk-HHY32NGJ.js";
16
16
  import "./chunk-QGM4M3NI.js";
17
17
  export {
18
18
  buildCliFlagsSummary,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  setDeployContext
3
- } from "./chunk-YYULF2JX.js";
3
+ } from "./chunk-YX62STIA.js";
4
4
  import {
5
5
  DotNS,
6
6
  TX_TIMEOUT_MS,
@@ -8,7 +8,7 @@ import {
8
8
  parseDomainName,
9
9
  popStatusName,
10
10
  verifyNonceAdvanced
11
- } from "./chunk-YREZFNCC.js";
11
+ } from "./chunk-G6CVI6U2.js";
12
12
  import {
13
13
  MirrorSkipped,
14
14
  mirrorToGitHubPages,
@@ -27,7 +27,7 @@ import {
27
27
  truncateAddress,
28
28
  withDeploySpan,
29
29
  withSpan
30
- } from "./chunk-DHQ3JGF4.js";
30
+ } from "./chunk-Y6CTPQS2.js";
31
31
  import {
32
32
  merkleizeJS
33
33
  } from "./chunk-B7GUYYAN.js";
@@ -38,7 +38,7 @@ import {
38
38
  fetchPoolAuthorizations,
39
39
  selectAccount,
40
40
  topUpBy
41
- } from "./chunk-JHNW2EKY.js";
41
+ } from "./chunk-WIBZPZSY.js";
42
42
 
43
43
  // src/deploy.ts
44
44
  import { Buffer } from "buffer";
@@ -175,10 +175,10 @@ async function getProvider() {
175
175
  if (!selected) {
176
176
  const best = authorizations.reduce((a, b) => a.transactions > b.transactions ? a : b);
177
177
  console.log(` All pool accounts low on capacity, auto-authorizing account ${best.index}...`);
178
- await ensureAuthorized(unsafeApi, best.address, BULLETIN_ENDPOINTS[0], `pool account ${best.index}`);
178
+ await ensureAuthorized(unsafeApi, best.address, `pool account ${best.index}`);
179
179
  selected = best;
180
180
  } else {
181
- await ensureAuthorized(unsafeApi, selected.address, BULLETIN_ENDPOINTS[0], `pool account ${selected.index}`);
181
+ await ensureAuthorized(unsafeApi, selected.address, `pool account ${selected.index}`);
182
182
  }
183
183
  console.log(` Using pool account ${selected.index}: ${selected.address}`);
184
184
  setDeployAttribute("deploy.signer.mode", "pool");
@@ -214,14 +214,12 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
214
214
  setDeployAttribute("deploy.signer.address", truncateAddress(ss58));
215
215
  return { client, unsafeApi, signer, ss58 };
216
216
  }
217
- var MAX_BEST_CHAIN_DROPS = 5;
218
217
  function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction", rpc, senderSS58, expectedNonce, timeoutMs, fetchNonce: fetchNonceOverride } = {}) {
219
218
  const timeout = timeoutMs ?? TX_TIMEOUT_MS;
220
219
  const _fetchNonce = fetchNonceOverride ?? fetchNonce;
221
220
  return new Promise((resolve2, reject) => {
222
221
  let settled = false;
223
222
  let sub;
224
- let dropCount = 0;
225
223
  const settle = (fn) => (...args) => {
226
224
  if (settled) return;
227
225
  settled = true;
@@ -232,7 +230,7 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
232
230
  }
233
231
  fn(...args);
234
232
  };
235
- const tryNonceFallback = async (subscriptionErrored) => {
233
+ const tryNonceFallback = async () => {
236
234
  if (!rpc || !senderSS58 || expectedNonce == null) return false;
237
235
  try {
238
236
  const endpoints = Array.isArray(rpc) ? rpc : [rpc];
@@ -240,7 +238,7 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
240
238
  if (settled) return true;
241
239
  if (verified.advanced) {
242
240
  console.log(` ${label}: nonce advanced past ${expectedNonce} (witnessed by ${verified.witnessRpc}), tx was included`);
243
- settle(resolve2)({ value: onSuccess(), viaFallback: true, subscriptionError: subscriptionErrored });
241
+ settle(resolve2)({ value: onSuccess(), viaFallback: true });
244
242
  return true;
245
243
  }
246
244
  } catch (e) {
@@ -251,28 +249,21 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
251
249
  };
252
250
  const timer = setTimeout(async () => {
253
251
  if (settled) return;
254
- if (await tryNonceFallback(false)) return;
252
+ if (await tryNonceFallback()) return;
255
253
  settle(reject)(new Error(`${label} timed out after ${timeout / 1e3}s waiting for block confirmation`));
256
254
  }, timeout);
257
255
  sub = tx.signSubmitAndWatch(signer, txOpts).subscribe({
258
256
  next: async (event) => {
259
- if (event.type === "txBestBlocksState") {
260
- if (event.found) {
261
- if (event.ok) {
262
- settle(resolve2)({ value: onSuccess(event), viaFallback: false, subscriptionError: false });
263
- } else {
264
- settle(reject)(new Error(`${label} dispatch error`));
265
- }
266
- } else {
267
- dropCount++;
268
- if (dropCount >= MAX_BEST_CHAIN_DROPS) {
269
- console.log(` ${label}: tx dropped ${dropCount} times, checking nonce...`);
270
- if (await tryNonceFallback(true)) return;
271
- settle(reject)(new Error(`${label} tx dropped from best chain ${dropCount} times`));
272
- } else {
273
- console.log(` ${label}: tx dropped from best chain (${dropCount}/${MAX_BEST_CHAIN_DROPS}), waiting...`);
274
- }
275
- }
257
+ if (event.type !== "txBestBlocksState") return;
258
+ if (event.found) {
259
+ if (event.ok) settle(resolve2)({ value: onSuccess(event), viaFallback: false });
260
+ else settle(reject)(new Error(`${label} dispatch error`));
261
+ return;
262
+ }
263
+ if (event.isValid === false) {
264
+ console.log(` ${label}: tx rejected by pool (isValid:false), checking nonce fallback...`);
265
+ if (await tryNonceFallback()) return;
266
+ settle(reject)(new Error(`${label} tx rejected by pool (isValid:false)`));
276
267
  }
277
268
  },
278
269
  error: (e) => {
@@ -287,11 +278,11 @@ async function storeChunk(unsafeApi, signer, chunkBytes, nonce, ss58, opts = {})
287
278
  const cid = createCID(chunkBytes, CID_CONFIG.codec, hashCode);
288
279
  const tx = unsafeApi.tx.TransactionStorage.store_with_cid_config({ cid: { codec: BigInt(CID_CONFIG.codec), hashing: toHashingEnum(hashCode) }, data: Binary.fromBytes(chunkBytes) });
289
280
  const txOpts = { mortality: { mortal: true, period: CHUNK_MORTALITY_PERIOD }, nonce };
290
- const { value, viaFallback, subscriptionError } = await watchTransaction(tx, signer, txOpts, () => {
281
+ const { value, viaFallback } = await watchTransaction(tx, signer, txOpts, () => {
291
282
  console.log(` CID: ${cid.toString()}`);
292
283
  return { cid, len: chunkBytes.length };
293
284
  }, { label: `chunk(nonce:${nonce})`, rpc: BULLETIN_ENDPOINTS, senderSS58: ss58, expectedNonce: nonce, timeoutMs: CHUNK_TIMEOUT_MS, fetchNonce: opts.fetchNonce });
294
- return { ...value, viaFallback, subscriptionError };
285
+ return { ...value, viaFallback };
295
286
  }
296
287
  async function storeFile(contentBytes, { client: existingClient, unsafeApi: existingApi, signer: existingSigner } = {}) {
297
288
  console.log(`
@@ -360,7 +351,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
360
351
  Account has insufficient authorization for this upload (need ${requiredTxs} txs / ${(totalBytes / 1e6).toFixed(1)}MB, have ${txsRemaining} txs / ${Number(bytesRemaining) / 1e6}MB)`);
361
352
  console.log(` Attempting to re-authorize with Alice...`);
362
353
  try {
363
- await ensureAuthorized(unsafeApi, ss58, BULLETIN_ENDPOINTS[0], void 0, { txs: requiredTxs, bytes: requiredBytes });
354
+ await ensureAuthorized(unsafeApi, ss58, void 0, { txs: requiredTxs, bytes: requiredBytes });
364
355
  console.log(` Re-authorization successful`);
365
356
  } catch (e) {
366
357
  throw new NonRetryableError(`Account ${ss58} has insufficient Bulletin authorization quota and auto-authorization via Alice failed (${e.message}). Authorize the account on-chain.`);
@@ -427,8 +418,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
427
418
  }
428
419
  });
429
420
  const failures = results.map((r, j) => r.status === "rejected" ? { index: batchIndices[j], chunkData: batchChunks[j], error: r.reason } : null).filter(Boolean);
430
- const subscriptionErrored = results.some((r) => r.status === "fulfilled" && r.value.subscriptionError);
431
- const needsReconnect = failures.some((f) => isConnectionError(f.error)) || subscriptionErrored;
421
+ const needsReconnect = failures.some((f) => isConnectionError(f.error));
432
422
  if (needsReconnect && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
433
423
  await doReconnect();
434
424
  const currentNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
@@ -529,9 +519,6 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
529
519
  return rootCid.toString();
530
520
  }, { label: "root-node", rpc: BULLETIN_ENDPOINTS, senderSS58: ss58, expectedNonce: rootNonce, timeoutMs: CHUNK_TIMEOUT_MS, fetchNonce: fetchNonceOverride });
531
521
  result = watchResult.value;
532
- if (watchResult.subscriptionError && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
533
- await doReconnect();
534
- }
535
522
  break;
536
523
  } catch (e) {
537
524
  if (reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
@@ -723,22 +710,6 @@ async function deploy(content, domainName = null, options = {}) {
723
710
  await preflight.connect(
724
711
  options.signer && options.signerAddress ? { signer: options.signer, signerAddress: options.signerAddress } : options.mnemonic ? { mnemonic: options.mnemonic, derivationPath: options.derivationPath } : {}
725
712
  );
726
- const mappingDeadline = Date.now() + 15e3;
727
- let mappingConfirmed = false;
728
- while (Date.now() < mappingDeadline) {
729
- if (await preflight.clientWrapper.checkIfAccountMapped(preflight.substrateAddress)) {
730
- mappingConfirmed = true;
731
- break;
732
- }
733
- await new Promise((r) => setTimeout(r, 1500));
734
- }
735
- if (!mappingConfirmed) {
736
- preflight.disconnect();
737
- throw new NonRetryableError(
738
- `Account mapping did not take effect on-chain for ${preflight.substrateAddress}. The map_account tx may have been dropped or hit a dispatch error \u2014 check the signer's balance for existential deposit + fees.`
739
- );
740
- }
741
- console.log(` Account: mapped`);
742
713
  if (parsed?.isSubdomain) {
743
714
  try {
744
715
  const { owned: subOwned } = await preflight.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
@@ -787,7 +758,7 @@ async function deploy(content, domainName = null, options = {}) {
787
758
  const uploadBytes = Math.ceil(estimated * 1.2);
788
759
  const chunksNeeded = Math.max(1, Math.ceil(uploadBytes / CHUNK_SIZE));
789
760
  const needs = { txs: BigInt(chunksNeeded + 2), bytes: BigInt(uploadBytes) };
790
- await topUpBy(provider.ss58, BULLETIN_ENDPOINTS[0], needs, "uploader");
761
+ await topUpBy(provider.unsafeApi, provider.ss58, needs, "uploader");
791
762
  }
792
763
  console.log("\n" + "=".repeat(60));
793
764
  console.log("Storage");
@@ -942,16 +913,11 @@ async function deploy(content, domainName = null, options = {}) {
942
913
  const freshness = await pollMirrorFreshness(mirror.url, cid, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1e4 });
943
914
  if (freshness.verified) {
944
915
  console.log(`ok (${freshness.attempts} attempt${freshness.attempts === 1 ? "" : "s"}, ${(freshness.durationMs / 1e3).toFixed(0)}s).`);
916
+ setDeployAttribute("deploy.gh_pages_freshness_verified", "true");
945
917
  } else {
946
918
  console.log(`timed out.`);
947
919
  console.log(` GitHub Pages last served cid=${freshness.lastCid ?? "n/a"} (expected ${cid}); it should catch up shortly. Non-fatal.`);
948
- captureWarning("gh-pages mirror freshness poll timed out", {
949
- url: mirror.url,
950
- expectedCid: cid,
951
- lastCid: freshness.lastCid ?? "n/a",
952
- durationMs: freshness.durationMs,
953
- attempts: freshness.attempts
954
- });
920
+ setDeployAttribute("deploy.gh_pages_freshness_verified", "false");
955
921
  }
956
922
  });
957
923
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-DHQ3JGF4.js";
3
+ } from "./chunk-Y6CTPQS2.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
@@ -2,10 +2,10 @@ import {
2
2
  captureWarning,
3
3
  setDeployAttribute,
4
4
  withSpan
5
- } from "./chunk-DHQ3JGF4.js";
5
+ } from "./chunk-Y6CTPQS2.js";
6
6
  import {
7
7
  isTestnetSpecName
8
- } from "./chunk-JHNW2EKY.js";
8
+ } from "./chunk-WIBZPZSY.js";
9
9
 
10
10
  // src/dotns.ts
11
11
  import { spawn } from "child_process";
@@ -227,11 +227,25 @@ function sanitizeDomainLabel(label) {
227
227
  function validateDomainLabel(label) {
228
228
  if (!/^[a-z0-9-]{3,}$/.test(label)) throw new Error("Invalid domain label: must contain only lowercase letters, digits, and hyphens, min 3 chars");
229
229
  if (label.startsWith("-") || label.endsWith("-")) throw new Error("Invalid domain label: cannot start or end with hyphen");
230
- return sanitizeDomainLabel(label);
230
+ const sanitized = sanitizeDomainLabel(label);
231
+ if (/-\d+$/.test(sanitized)) {
232
+ const baseWithHyphen = sanitized.replace(/\d+$/, "");
233
+ const dropHyphen = sanitized.replace(/-(\d+)$/, "$1");
234
+ const insertSegment = sanitized.replace(/-(\d+)$/, "-pr$1");
235
+ throw new Error(
236
+ `Invalid domain label: "${sanitized}" \u2014 dotns base-name extraction leaves a trailing hyphen ("${baseWithHyphen}"), which the registry rejects with PopError("Name must be lowercase ASCII DNS label"). Drop the hyphen before the digits (e.g. "${dropHyphen}") or add a non-digit segment between (e.g. "${insertSegment}").`
237
+ );
238
+ }
239
+ return sanitized;
231
240
  }
232
241
  function isCommitmentMature(chainNowSeconds, commitTimestampSeconds, minimumAgeSeconds) {
233
242
  return chainNowSeconds > commitTimestampSeconds + minimumAgeSeconds;
234
243
  }
244
+ function isExplicitCommitmentBuffer(envValue) {
245
+ if (envValue === void 0 || envValue === "") return false;
246
+ const parsed = Number(envValue);
247
+ return Number.isFinite(parsed) && parsed > 0;
248
+ }
235
249
  function classifyDotnsLabel(label) {
236
250
  const totalLength = label.length;
237
251
  const trailingDigits = countTrailingDigits(label);
@@ -435,34 +449,46 @@ var DotNS = class {
435
449
  this._mnemonic = null;
436
450
  }
437
451
  const authEnv = buildAuthEnv({ mnemonic: this._mnemonic ?? void 0, keyUri: this._keyUri ?? void 0 });
438
- const popInfo = await withTimeout(
439
- runDotnsCli(["pop", "info", "--json", ...rpcFlag(rpc)], authEnv),
440
- CONNECTION_TIMEOUT_MS,
441
- "pop info (connect)"
442
- );
443
- this.substrateAddress = popInfo.substrate;
444
- this.evmAddress = popInfo.evm;
452
+ await cryptoWaitReady();
453
+ const keyring = new Keyring({ type: "sr25519" });
454
+ const account = this._keyUri ? keyring.addFromUri(this._keyUri) : keyring.addFromMnemonic(source);
455
+ this.signer = getPolkadotSigner(account.publicKey, "Sr25519", async (input) => account.sign(input));
456
+ this.substrateAddress = account.address;
445
457
  console.log(` SS58 Address: ${this.substrateAddress}`);
446
- console.log(` H160 Address: ${this.evmAddress}`);
447
458
  try {
448
- await cryptoWaitReady();
449
- const keyring = new Keyring({ type: "sr25519" });
450
- const account = isKeyUri || this._keyUri ? keyring.addFromUri(this._keyUri ?? source) : keyring.addFromMnemonic(source);
451
- this.signer = getPolkadotSigner(account.publicKey, "Sr25519", async (input) => account.sign(input));
452
459
  this.client = createClient(getWsProvider(rpc, { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS }));
453
460
  const unsafeApi = this.client.getUnsafeApi();
454
461
  this.clientWrapper = new ReviveClientWrapper(unsafeApi);
462
+ this.evmAddress = await withTimeout(
463
+ this.clientWrapper.getEvmAddress(this.substrateAddress),
464
+ CONNECTION_TIMEOUT_MS,
465
+ "ReviveApi.address"
466
+ );
467
+ console.log(` H160 Address: ${this.evmAddress}`);
455
468
  } catch (e) {
456
- captureWarning("DotNS polkadot-api client setup failed (contractCall / checkIfAccountMapped unavailable)", { error: e.message?.slice(0, 200) });
469
+ throw new Error(`DotNS connect: failed to resolve EVM address from ${this.substrateAddress} via ReviveApi.address (${e.message?.slice(0, 200)})`);
457
470
  }
458
- try {
459
- await runDotnsCli(["account", "map", ...rpcFlag(rpc)], authEnv);
460
- } catch (e) {
461
- const msg = e.message?.toLowerCase() ?? "";
462
- if (!msg.includes("already") && !msg.includes("mapped")) {
463
- captureWarning("account map failed during connect", { error: e.message?.slice(0, 200) });
471
+ if (!await this.clientWrapper.checkIfAccountMapped(this.substrateAddress)) {
472
+ console.log(` Mapping account on Asset Hub Revive...`);
473
+ try {
474
+ await runDotnsCli(["account", "map", ...rpcFlag(rpc)], authEnv);
475
+ } catch (e) {
476
+ captureWarning("account map failed during connect (will rely on chain confirmation)", { error: e.message?.slice(0, 200) });
477
+ }
478
+ const mappingDeadline = Date.now() + 15e3;
479
+ let mappingConfirmed = false;
480
+ while (Date.now() < mappingDeadline) {
481
+ if (await this.clientWrapper.checkIfAccountMapped(this.substrateAddress)) {
482
+ mappingConfirmed = true;
483
+ break;
484
+ }
485
+ await new Promise((r) => setTimeout(r, 1500));
486
+ }
487
+ if (!mappingConfirmed) {
488
+ throw new Error(`Account mapping did not take effect on-chain for ${this.substrateAddress}. The map_account tx may have been dropped or hit a dispatch error \u2014 check the signer's balance for existential deposit + fees.`);
464
489
  }
465
490
  }
491
+ console.log(` Account: mapped`);
466
492
  try {
467
493
  setDeployAttribute("deploy.dotns_cli.version", _dotnsCliVersion);
468
494
  } catch {
@@ -785,7 +811,7 @@ var DotNS = class {
785
811
  console.log(`
786
812
  Registering ${label}.dot via dotns CLI...`);
787
813
  const authEnv = buildAuthEnv({ mnemonic: this._mnemonic ?? void 0, keyUri: this._keyUri ?? void 0 });
788
- const userSetBuffer = !!process.env.DOTNS_COMMITMENT_BUFFER;
814
+ const userSetBuffer = isExplicitCommitmentBuffer(process.env.DOTNS_COMMITMENT_BUFFER);
789
815
  if (!userSetBuffer) {
790
816
  authEnv.DOTNS_COMMITMENT_BUFFER = "30";
791
817
  }
@@ -857,6 +883,7 @@ export {
857
883
  sanitizeDomainLabel,
858
884
  validateDomainLabel,
859
885
  isCommitmentMature,
886
+ isExplicitCommitmentBuffer,
860
887
  classifyDotnsLabel,
861
888
  canRegister,
862
889
  simulateUserStatus,
@@ -6,7 +6,7 @@ import * as path from "path";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "bulletin-deploy",
9
- version: "0.7.4",
9
+ version: "0.7.6",
10
10
  private: false,
11
11
  repository: {
12
12
  type: "git",
@@ -20,7 +20,8 @@ var package_default = {
20
20
  main: "./dist/index.js",
21
21
  types: "./dist/index.d.ts",
22
22
  bin: {
23
- "bulletin-deploy": "./bin/bulletin-deploy"
23
+ "bulletin-deploy": "./bin/bulletin-deploy",
24
+ "bulletin-bootstrap": "./bin/bulletin-bootstrap"
24
25
  },
25
26
  exports: {
26
27
  ".": {
@@ -30,12 +31,13 @@ var package_default = {
30
31
  },
31
32
  files: [
32
33
  "dist",
33
- "bin"
34
+ "bin",
35
+ "docs"
34
36
  ],
35
37
  scripts: {
36
38
  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
39
  prepare: "npm run build",
38
- test: "npm run build && node --test test/test.js test/pool.test.js test/helpers/e2e-helpers.test.js",
40
+ test: "npm run build && node --test test/test.js test/cli-help.test.js test/helpers/e2e-helpers.test.js",
39
41
  "test:e2e": "npm run build && node --test test/e2e.test.js",
40
42
  "test:e2e:smoke": "bash scripts/e2e-pass.sh smoke",
41
43
  "test:e2e:pr": "bash scripts/e2e-pass.sh pr",