bulletin-deploy 0.7.15 → 0.7.16-rc.2

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.
package/README.md CHANGED
@@ -103,6 +103,7 @@ The runtime uses a 24-hour cache at `${XDG_CACHE_HOME:-~/.cache}/bulletin-deploy
103
103
  | `--js-merkle` | Use pure-JS merkleization instead of the Kubo binary. |
104
104
  | `--tag "..."` | Attach a free-form telemetry label. Also readable from `DEPLOY_TAG`. |
105
105
  | `--gh-pages-mirror` | After a successful deploy, push the generated CAR to the current repo's `gh-pages` branch as an HTTP mirror. |
106
+ | `--input-car <path>` | Deploy from a pre-built CAR file instead of a build directory. Skips merkleization; reads the root CID from the CAR header. Usage: `bulletin-deploy --input-car site.car my-app.dot` |
106
107
  | `--version` | Print the CLI version. |
107
108
  | `--help` | Show help. |
108
109
 
@@ -201,6 +202,8 @@ await deploy("./dist", "my-app00.dot", { jsMerkle: true });
201
202
  | `DOTNS_STATUS` | `full` on testnet, `none` on mainnet | PoP level to self-grant before registration: `none`, `lite`, or `full` |
202
203
  | `IPFS_CID` | unset | Skip storage and reuse an existing CID |
203
204
  | `DEPLOY_TAG` | unset | Telemetry label equivalent to `--tag` |
205
+ | `BULLETIN_DEPLOY_HOST_APP` | unset | Name of the host app embedding bulletin-deploy (e.g. `playground-cli`). Sets `deploy.host_app` on telemetry spans. |
206
+ | `BULLETIN_DEPLOY_HOST_APP_VERSION` | unset | Version of the host app. Sets `deploy.host_app_version` on telemetry spans when `BULLETIN_DEPLOY_HOST_APP` is also set. |
204
207
 
205
208
  ## Troubleshooting
206
209
 
@@ -27,6 +27,7 @@ for (let i = 0; i < args.length; i++) {
27
27
  else if (args[i] === "--refresh-environments") { flags.refreshEnvironments = true; }
28
28
  else if (args[i] === "--password") { flags.password = args[++i]; }
29
29
  else if (args[i] === "--js-merkle") { flags.jsMerkle = true; }
30
+ else if (args[i] === "--input-car") { flags.inputCar = args[++i]; }
30
31
  else if (args[i] === "--tag") { flags.tag = args[++i]; }
31
32
  else if (args[i] === "--name") { flags.name = args[++i]; }
32
33
  else if (args[i] === "--description") { flags.description = args[++i]; }
@@ -95,6 +96,8 @@ Options:
95
96
  --pool-size N Number of pool accounts (default: 10)
96
97
  --password "..." Encrypt SPA content (users will be prompted to decrypt)
97
98
  --js-merkle Use pure-JS merkleization (no IPFS Kubo binary required)
99
+ --input-car <path> Deploy a pre-built CAR file; skips directory scan and merkleization.
100
+ Usage: bulletin-deploy --input-car <file.car> <domain.dot>
98
101
  --tag "..." Label deploy in telemetry (or set DEPLOY_TAG env var); see Telemetry in README
99
102
  --name "..." Optional. Sets the "name" text record on the domain.
100
103
  --description "..." Optional. Sets the "description" text record (≤100 chars recommended).
@@ -210,10 +213,20 @@ if (!flags.help && !flags.version) {
210
213
  }
211
214
 
212
215
  try {
213
- const [buildDir, domain] = positional;
214
- if (!buildDir) { console.error("Error: build directory required"); process.exit(1); }
215
- if (!domain) { console.error("Error: domain required (e.g. my-app.dot)"); process.exit(1); }
216
- if (!fs.existsSync(buildDir)) { console.error(`Error: ${buildDir} does not exist`); process.exit(1); }
216
+ let buildDir, domain;
217
+ if (flags.inputCar) {
218
+ // --input-car mode: positional[0] is the domain; no build directory needed.
219
+ [domain] = positional;
220
+ if (!flags.inputCar) { console.error("Error: --input-car requires a path argument"); process.exit(1); }
221
+ if (!domain) { console.error("Error: domain required (e.g. my-app.dot)"); process.exit(1); }
222
+ if (!fs.existsSync(flags.inputCar)) { console.error(`Error: ${flags.inputCar} does not exist`); process.exit(1); }
223
+ buildDir = flags.inputCar; // passed as `content` to deploy(); inputCar branch handles it
224
+ } else {
225
+ [buildDir, domain] = positional;
226
+ if (!buildDir) { console.error("Error: build directory required"); process.exit(1); }
227
+ if (!domain) { console.error("Error: domain required (e.g. my-app.dot)"); process.exit(1); }
228
+ if (!fs.existsSync(buildDir)) { console.error(`Error: ${buildDir} does not exist`); process.exit(1); }
229
+ }
217
230
 
218
231
  // --refresh-environments composed with a deploy: bust the cache before
219
232
  // deploy() loads it, so deploy() picks up fresh data on the next loadEnvironments().
@@ -254,6 +267,7 @@ try {
254
267
  poolSize: flags.poolSize,
255
268
  password: flags.password,
256
269
  jsMerkle: flags.jsMerkle,
270
+ inputCar: flags.inputCar,
257
271
  tag: flags.tag,
258
272
  ghPagesMirror: flags.ghPagesMirror,
259
273
  name: flags.name,
@@ -9,10 +9,10 @@ import {
9
9
  offerBugReport,
10
10
  scrubSecrets,
11
11
  setDeployContext
12
- } from "./chunk-AQUBKPSF.js";
13
- import "./chunk-NE5MN2M2.js";
14
- import "./chunk-KMRYJR4E.js";
15
- import "./chunk-ZGU6FOLO.js";
12
+ } from "./chunk-S2WGWNP5.js";
13
+ import "./chunk-MGTWZ6XS.js";
14
+ import "./chunk-5P7EZSOU.js";
15
+ import "./chunk-2IMBCUB2.js";
16
16
  import "./chunk-QGM4M3NI.js";
17
17
  export {
18
18
  buildCliFlagsSummary,
@@ -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.15",
9
+ version: "0.7.16-rc.2",
10
10
  private: false,
11
11
  repository: {
12
12
  type: "git",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-ZGU6FOLO.js";
4
+ } from "./chunk-2IMBCUB2.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -13,8 +13,23 @@ import * as v8 from "v8";
13
13
  import { execSync } from "child_process";
14
14
  import { createHash } from "crypto";
15
15
  import * as fs from "fs";
16
+ import { createRequire } from "module";
16
17
  import * as path from "path";
17
18
  var VERSION = package_default.version;
19
+ function resolveDotnsCliVersion() {
20
+ try {
21
+ const require2 = createRequire(import.meta.url);
22
+ const mainPath = require2.resolve("@parity/dotns-cli");
23
+ const marker = `${path.sep}node_modules${path.sep}@parity${path.sep}dotns-cli${path.sep}`;
24
+ const idx = mainPath.lastIndexOf(marker);
25
+ if (idx === -1) return "unknown";
26
+ const pkgJson = path.join(mainPath.slice(0, idx + marker.length), "package.json");
27
+ return JSON.parse(fs.readFileSync(pkgJson, "utf-8")).version ?? "unknown";
28
+ } catch {
29
+ return "unknown";
30
+ }
31
+ }
32
+ var DOTNS_CLI_VERSION = resolveDotnsCliVersion();
18
33
  var DEFAULT_DSN = "https://e021c025d79c4c3ade2862a11f13c40b@o4511059872841728.ingest.de.sentry.io/4511093597405264";
19
34
  var INTERNAL_ORG_RE = /^(paritytech|w3f|polkadot-fellows)\//i;
20
35
  var PARITY_HOST_APPS = /* @__PURE__ */ new Set(["playground-cli"]);
@@ -211,6 +226,9 @@ function getDeployAttributes(domain) {
211
226
  "deploy.dotns.toppedup": "false"
212
227
  };
213
228
  if (hostApp) attrs["deploy.host_app"] = hostApp;
229
+ const hostAppVersion = process.env.BULLETIN_DEPLOY_HOST_APP_VERSION;
230
+ if (hostAppVersion) attrs["deploy.host_app_version"] = hostAppVersion;
231
+ attrs["deploy.dotns_cli_version"] = DOTNS_CLI_VERSION;
214
232
  return attrs;
215
233
  }
216
234
  function isExpectedError(msg) {
@@ -452,7 +470,7 @@ async function flush() {
452
470
  function isBunRuntime() {
453
471
  return typeof globalThis.Bun !== "undefined" || typeof process.versions.bun === "string";
454
472
  }
455
- var DEFAULT_THRESHOLD_MB = 1500;
473
+ var DEFAULT_THRESHOLD_MB = 800;
456
474
  function toMb2(bytes) {
457
475
  return Math.round(bytes / 1024 / 1024 * 100) / 100;
458
476
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureWarning
3
- } from "./chunk-KMRYJR4E.js";
3
+ } from "./chunk-5P7EZSOU.js";
4
4
 
5
5
  // src/chunk-probe.ts
6
6
  import { Twox128, Blake2128Concat, decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-KMRYJR4E.js";
3
+ } from "./chunk-5P7EZSOU.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
7
7
  import { createInterface } from "readline";
8
8
  var REGISTRY_URL = "https://registry.npmjs.org/bulletin-deploy/latest";
9
- var KILL_SWITCH_URL = "https://raw.githubusercontent.com/paritytech/bulletin-deploy/main/min-version.json";
9
+ var KILL_SWITCH_URL = "https://raw.githubusercontent.com/paritytech/triangle-deploy/main/min-version.json";
10
10
  var FETCH_TIMEOUT = 3e3;
11
11
  function compareSemver(a, b) {
12
12
  const [coreA, preA] = a.split("-", 2);
@@ -5,7 +5,7 @@ import {
5
5
  captureWarning,
6
6
  setDeployAttribute,
7
7
  withSpan
8
- } from "./chunk-KMRYJR4E.js";
8
+ } from "./chunk-5P7EZSOU.js";
9
9
 
10
10
  // src/dotns.ts
11
11
  import { spawn } from "child_process";
@@ -362,6 +362,9 @@ function isExplicitCommitmentBuffer(envValue) {
362
362
  const parsed = Number(envValue);
363
363
  return Number.isFinite(parsed) && parsed > 0;
364
364
  }
365
+ function isLikelyCommitmentRace(msg) {
366
+ return /CommitmentTooNew/i.test(msg) || /Revive\.ContractReverted/i.test(msg);
367
+ }
365
368
  function classifyDotnsLabel(label) {
366
369
  const totalLength = label.length;
367
370
  const trailingDigits = countTrailingDigits(label);
@@ -1518,7 +1521,7 @@ var DotNS = class {
1518
1521
  }
1519
1522
  return { ...candidate, signerFreeBalance: effectiveBalance, feeFloor, toppedUp };
1520
1523
  }
1521
- async register(label, options = {}) {
1524
+ async register(label, options = {}, _cli = runDotnsCli) {
1522
1525
  return withSpan("deploy.dotns.register", `2a. register ${label}.dot`, {}, async () => {
1523
1526
  if (!this.connected) await this.connect(options);
1524
1527
  label = validateDomainLabel(label);
@@ -1580,7 +1583,7 @@ var DotNS = class {
1580
1583
  }
1581
1584
  const runRegister = async (bufferSeconds) => {
1582
1585
  const env = bufferSeconds ? { ...authEnv, DOTNS_COMMITMENT_BUFFER: bufferSeconds } : authEnv;
1583
- return await runDotnsCli(
1586
+ return await _cli(
1584
1587
  ["register", "domain", "-n", label, "--json", ...statusArgs, ...reverseArgs, ...rpcFlag(this.rpc)],
1585
1588
  env
1586
1589
  );
@@ -1590,17 +1593,16 @@ var DotNS = class {
1590
1593
  result = await runRegister();
1591
1594
  } catch (err) {
1592
1595
  const msg = err.message;
1593
- if (!/CommitmentTooNew/i.test(msg)) throw err;
1596
+ if (!isLikelyCommitmentRace(msg)) throw err;
1594
1597
  const retryBuffer = userSetBuffer ? void 0 : "60";
1595
- console.log(` CommitmentTooNew on first attempt \u2014 retrying register once${retryBuffer ? ` with DOTNS_COMMITMENT_BUFFER=${retryBuffer}s` : ""} (block-time lag typically resolves within a block or two).`);
1598
+ console.log(` register reverted on first attempt \u2014 retrying once${retryBuffer ? ` with DOTNS_COMMITMENT_BUFFER=${retryBuffer}s` : ""} (commit-reveal race; block-time lag typically resolves within a block or two).`);
1596
1599
  try {
1597
1600
  result = await runRegister(retryBuffer);
1598
1601
  } catch (retryErr) {
1599
1602
  const retryMsg = retryErr.message;
1600
- if (!/CommitmentTooNew/i.test(retryMsg)) throw retryErr;
1601
- throw new Error(
1602
- `DotNS commit-reveal race: the chain's block timestamps are lagging wall-clock by more than 30s across two register attempts. This is usually a transient block-production slowdown. Retry in a minute, or set DOTNS_COMMITMENT_BUFFER=60 (or higher) to absorb longer drift. Upstream fix tracked at paritytech/dotns-sdk#105. Underlying: ${retryMsg}`
1603
- );
1603
+ if (!isLikelyCommitmentRace(retryMsg)) throw retryErr;
1604
+ const likelyCause = /CommitmentTooNew/i.test(retryMsg) ? `the chain's block timestamps are lagging wall-clock by more than 30s across two register attempts. This is usually a transient block-production slowdown. Retry in a minute, or set DOTNS_COMMITMENT_BUFFER=60 (or higher) to absorb longer drift. Upstream fix tracked at paritytech/dotns-sdk#105.` : `the register tx reverted twice in a row. This is typically a commit-reveal timing race (block-production slowdown), but could also be a contract rejection (label already taken, PoP status mismatch). Try again in a minute; if it persists, verify the label is available. Upstream: paritytech/dotns-sdk#105.`;
1605
+ throw new Error(`DotNS register failed after retry: ${likelyCause} Underlying: ${retryMsg}`);
1604
1606
  }
1605
1607
  }
1606
1608
  console.log(`
@@ -1651,6 +1653,7 @@ export {
1651
1653
  validateDomainLabel,
1652
1654
  isCommitmentMature,
1653
1655
  isExplicitCommitmentBuffer,
1656
+ isLikelyCommitmentRace,
1654
1657
  classifyDotnsLabel,
1655
1658
  canRegister,
1656
1659
  simulateUserStatus,
@@ -2,11 +2,11 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-NE5MN2M2.js";
5
+ } from "./chunk-MGTWZ6XS.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId
9
- } from "./chunk-KMRYJR4E.js";
9
+ } from "./chunk-5P7EZSOU.js";
10
10
 
11
11
  // src/bug-report.ts
12
12
  import { execSync, execFileSync } from "child_process";
@@ -18,10 +18,10 @@ import {
18
18
  } from "./chunk-S7EM5VMW.js";
19
19
  import {
20
20
  setDeployContext
21
- } from "./chunk-AQUBKPSF.js";
21
+ } from "./chunk-S2WGWNP5.js";
22
22
  import {
23
23
  probeChunks
24
- } from "./chunk-Y2OSXJIZ.js";
24
+ } from "./chunk-GHBHQP4S.js";
25
25
  import {
26
26
  packSection
27
27
  } from "./chunk-C2TS5MER.js";
@@ -32,7 +32,7 @@ import {
32
32
  parseDomainName,
33
33
  popStatusName,
34
34
  verifyNonceAdvanced
35
- } from "./chunk-5TW653QP.js";
35
+ } from "./chunk-OLU6VQG2.js";
36
36
  import {
37
37
  derivePoolAccounts,
38
38
  detectTestnet,
@@ -54,12 +54,12 @@ import {
54
54
  truncateAddress,
55
55
  withDeploySpan,
56
56
  withSpan
57
- } from "./chunk-KMRYJR4E.js";
57
+ } from "./chunk-5P7EZSOU.js";
58
58
  import {
59
59
  DEFAULT_ENV_ID,
60
60
  loadEnvironments,
61
61
  resolveEndpoints
62
- } from "./chunk-2VYG7NXN.js";
62
+ } from "./chunk-X3F7WHSF.js";
63
63
  import {
64
64
  NonRetryableError
65
65
  } from "./chunk-ZOC4GITL.js";
@@ -74,7 +74,7 @@ import * as fs2 from "fs";
74
74
  import * as path2 from "path";
75
75
  import { execSync as execSync2 } from "child_process";
76
76
  import { importer } from "ipfs-unixfs-importer";
77
- import { CarReader } from "@ipld/car/reader";
77
+ import { CarReader as CarReader2 } from "@ipld/car/reader";
78
78
  import { CarWriter } from "@ipld/car/writer";
79
79
  import { CID as CID2 } from "multiformats/cid";
80
80
  import * as dagPB2 from "@ipld/dag-pb";
@@ -101,6 +101,7 @@ import { cryptoWaitReady } from "@polkadot/util-crypto";
101
101
  import { getPolkadotSigner } from "polkadot-api/signer";
102
102
  import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
103
103
  import { mnemonicToEntropy, entropyToMiniSecret, ss58Address } from "@polkadot-labs/hdkd-helpers";
104
+ import { CarReader } from "@ipld/car/reader";
104
105
  function friendlyChainError(msg) {
105
106
  if (/"type":\s*"Invalid"[\s\S]*?"type":\s*"Payment"/i.test(msg)) {
106
107
  return "Bulletin quota exhausted (signed extension rejected the tx \u2014 signer is out of allowed txs or bytes; grant quota on-chain)";
@@ -406,6 +407,23 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
406
407
  ss58 = provider.ss58;
407
408
  ownsClient = true;
408
409
  }
410
+ if (existingClient && reconnect) {
411
+ try {
412
+ await unsafeApi.query.System.Number.getValue();
413
+ } catch (e) {
414
+ if (isConnectionError(e)) {
415
+ console.log("\n Connection lost (stale client detected by pre-upload probe), reconnecting...");
416
+ const fresh = await reconnect();
417
+ client = fresh.client;
418
+ unsafeApi = fresh.unsafeApi;
419
+ signer = fresh.signer;
420
+ ss58 = fresh.ss58;
421
+ ownsClient = true;
422
+ } else {
423
+ throw e;
424
+ }
425
+ }
426
+ }
409
427
  const requiredTxs = BigInt(chunks.length + 1);
410
428
  const requiredBytes = BigInt(totalBytes);
411
429
  const [uploadAuth, currentBlockNum] = await Promise.all([
@@ -607,6 +625,9 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
607
625
  }
608
626
  }
609
627
  if (!retried) {
628
+ if (isConnectionError(fail.error) && reconnectionsUsed >= MAX_RECONNECTIONS) {
629
+ throw new Error(`Connection lost and max reconnections (${MAX_RECONNECTIONS}) exhausted`);
630
+ }
610
631
  throw new Error(`Chunk ${fail.index + 1} failed after ${MAX_CHUNK_RETRIES} retries: ${fail.error?.message?.slice(0, 100)}`);
611
632
  }
612
633
  }
@@ -699,7 +720,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
699
720
  }
700
721
  }
701
722
  if (ownsClient) client.destroy();
702
- return { storageCid: result, tier2Verified, tier2Inconclusive, tier2Fallback: fallbackStored.length };
723
+ return { storageCid: result, tier2Verified, tier2Inconclusive, tier2Fallback: fallbackStored.length, liveProvider: { client, unsafeApi, signer, ss58 } };
703
724
  } catch (e) {
704
725
  if (ownsClient) client.destroy();
705
726
  throw e;
@@ -972,6 +993,7 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
972
993
  totalSize: `${(phaseA.carBytes.length / 1024 / 1024).toFixed(2)} MB`
973
994
  });
974
995
  const carMbA = String(Math.round(phaseA.carBytes.length / 1024 / 1024 * 100) / 100);
996
+ let phaseALiveProvider = provider;
975
997
  await withSpan("deploy.chunk-upload", "1b. chunk-upload (phase A)", {
976
998
  "deploy.chunks.total": carChunksA.length,
977
999
  "deploy.chunks.skipped": skipCids.size,
@@ -979,7 +1001,8 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
979
1001
  "deploy.car.mb": carMbA
980
1002
  }, async () => {
981
1003
  sampleMemory("chunk_upload_start");
982
- await storeChunkedContent(carChunksA, { ...provider, gateway, skipCids, probeFailedCids: probeFailedCidsA });
1004
+ const phaseAUpload = await storeChunkedContent(carChunksA, { ...provider, gateway, skipCids, probeFailedCids: probeFailedCidsA });
1005
+ phaseALiveProvider = { ...provider, ...phaseAUpload.liveProvider };
983
1006
  sampleMemory("chunk_upload_end");
984
1007
  });
985
1008
  const phaseAKnownPresent = new Set(carChunkCidsA);
@@ -1013,6 +1036,9 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1013
1036
  blocks: blocksList,
1014
1037
  chunks: chunksMap
1015
1038
  });
1039
+ phaseA.blocks.clear();
1040
+ carChunksA.length = 0;
1041
+ phaseA.carBytes = new Uint8Array(0);
1016
1042
  const phaseB = await withSpan("deploy.merkleize", "1c. merkleize (js, finalise)", { "deploy.directory": dirBasename }, async () => {
1017
1043
  const r = await merkleizeWithStableOrder(directoryPath, phaseA.stableOrder, { useKubo });
1018
1044
  sampleMemory("merkleize_finalise_end");
@@ -1038,7 +1064,7 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1038
1064
  "deploy.car.mb": carMbB
1039
1065
  }, async () => {
1040
1066
  sampleMemory("chunk_upload_b_start");
1041
- const r = await storeChunkedContent(carChunksB, { ...provider, gateway, skipCids: skipCidsB, probeFailedCids: probeFailedCidsA });
1067
+ const r = await storeChunkedContent(carChunksB, { ...phaseALiveProvider, gateway, skipCids: skipCidsB, probeFailedCids: probeFailedCidsA });
1042
1068
  sampleMemory("chunk_upload_b_end");
1043
1069
  return r;
1044
1070
  });
@@ -1175,7 +1201,8 @@ async function deploy(content, domainName = null, options = {}) {
1175
1201
  console.log("=".repeat(60));
1176
1202
  console.log(` Domain: ${name}.dot`);
1177
1203
  if (deployTag) console.log(` Tag: ${deployTag}`);
1178
- if (typeof content === "string") console.log(` Build dir: ${path.resolve(content)}`);
1204
+ if (options.inputCar) console.log(` Input CAR: ${path.resolve(options.inputCar)}`);
1205
+ else if (typeof content === "string") console.log(` Build dir: ${path.resolve(content)}`);
1179
1206
  if (process.env.CI) console.log(` Runner: ${resolveRunner()} (${resolveRunnerType()})`);
1180
1207
  if (options.password) console.log(` Encrypted: yes`);
1181
1208
  let provider;
@@ -1243,7 +1270,38 @@ async function deploy(content, domainName = null, options = {}) {
1243
1270
  console.log("Storage");
1244
1271
  console.log("=".repeat(60));
1245
1272
  await withSpan("deploy.storage", "1. storage", {}, async () => {
1246
- if (process.env.IPFS_CID) {
1273
+ if (options.inputCar) {
1274
+ const carPath = path.resolve(options.inputCar);
1275
+ if (!fs.existsSync(carPath)) throw new Error(`CAR file not found: ${carPath}`);
1276
+ console.log(`
1277
+ Mode: Pre-built CAR`);
1278
+ console.log(` Path: ${carPath}`);
1279
+ let carContent = fs.readFileSync(carPath);
1280
+ console.log(` Size: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
1281
+ const reader = await CarReader.fromBytes(carContent);
1282
+ const roots = await reader.getRoots();
1283
+ if (roots.length === 0) throw new Error("CAR file has no roots");
1284
+ ipfsCid = roots[0].toString();
1285
+ console.log(` Root CID: ${ipfsCid}`);
1286
+ if (options.password) {
1287
+ console.log(` Encrypting CAR file...`);
1288
+ carContent = await encryptContent(carContent, options.password);
1289
+ console.log(` Encrypted: ${(carContent.length / 1024 / 1024).toFixed(2)} MB`);
1290
+ }
1291
+ const carChunks = chunk(carContent, CHUNK_SIZE);
1292
+ const predictedStorageCid = computeStorageCid(carChunks);
1293
+ if (options.ghPagesMirror) {
1294
+ mirrorPromise = mirrorToGitHubPages({
1295
+ domain: name,
1296
+ carBytes: carContent,
1297
+ cid: predictedStorageCid,
1298
+ toolVersion: VERSION,
1299
+ bulletinRpc: BULLETIN_ENDPOINTS[0],
1300
+ encrypted: Boolean(options.password)
1301
+ }).catch((err) => err instanceof Error ? err : new Error(String(err)));
1302
+ }
1303
+ cid = (await storeChunkedContent(carChunks, providerWithReconnect)).storageCid;
1304
+ } else if (process.env.IPFS_CID) {
1247
1305
  if (options.password) {
1248
1306
  throw new Error(
1249
1307
  "IPFS_CID and --password are mutually exclusive: IPFS_CID skips the upload step, so there is nothing to encrypt. Either unset IPFS_CID to upload and encrypt fresh content, or remove --password to reuse the existing CID as-is."
@@ -1591,7 +1649,7 @@ async function merkleizeKuboBackend(directoryPath) {
1591
1649
  ).trim();
1592
1650
  execSync2(`ipfs dag export ${cidStr} > "${carPath}"`);
1593
1651
  const carBytes = fs2.readFileSync(carPath);
1594
- const reader = await CarReader.fromBytes(carBytes);
1652
+ const reader = await CarReader2.fromBytes(carBytes);
1595
1653
  const blocks = /* @__PURE__ */ new Map();
1596
1654
  for await (const { cid, bytes } of reader.blocks()) {
1597
1655
  blocks.set(cid.toString(), bytes);
@@ -7,7 +7,7 @@ import * as fs from "fs/promises";
7
7
  import * as path from "path";
8
8
  import * as os from "os";
9
9
  import { fileURLToPath } from "url";
10
- var DEFAULT_ENVIRONMENTS_URL = "https://raw.githubusercontent.com/paritytech/bulletin-deploy/main/assets/environments.json";
10
+ var DEFAULT_ENVIRONMENTS_URL = "https://raw.githubusercontent.com/paritytech/triangle-deploy/main/assets/environments.json";
11
11
  var DEFAULT_ENV_ID = "paseo-next";
12
12
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
13
13
  var FETCH_TIMEOUT_MS = 5e3;
@@ -5,9 +5,9 @@ import {
5
5
  _decodeStorageValue,
6
6
  _resetProbeSession,
7
7
  probeChunks
8
- } from "./chunk-Y2OSXJIZ.js";
9
- import "./chunk-KMRYJR4E.js";
10
- import "./chunk-ZGU6FOLO.js";
8
+ } from "./chunk-GHBHQP4S.js";
9
+ import "./chunk-5P7EZSOU.js";
10
+ import "./chunk-2IMBCUB2.js";
11
11
  import "./chunk-QGM4M3NI.js";
12
12
  export {
13
13
  ChainProbeCrossValidationError,
package/dist/deploy.d.ts CHANGED
@@ -68,6 +68,7 @@ declare function storeChunkedContent(chunks: Uint8Array[], { client: existingCli
68
68
  tier2Verified: number;
69
69
  tier2Inconclusive: number;
70
70
  tier2Fallback: number;
71
+ liveProvider: ExistingProvider;
71
72
  }>;
72
73
  declare function chunk(data: Uint8Array, size?: number): Uint8Array[];
73
74
  declare function hasIPFS(): boolean;
@@ -170,6 +171,13 @@ interface DeployOptions {
170
171
  description?: string;
171
172
  /** Skip the 500 MiB abort guard and allow oversized deploys. */
172
173
  allowLargeDeploy?: boolean;
174
+ /**
175
+ * Filesystem path to a pre-built `.car` file. When set, skips directory
176
+ * scanning and merkleization; the CAR bytes are read from disk, the root
177
+ * CID is parsed from the CAR header, and the file is uploaded directly.
178
+ * The positional `<build-dir>` argument is not required when this is set.
179
+ */
180
+ inputCar?: string;
173
181
  /**
174
182
  * Pin the `deployedAt` timestamp for byte-identical rebuilds.
175
183
  * Values: "commit" (git committer date), "epoch:<N>" (Unix epoch seconds),
package/dist/deploy.js CHANGED
@@ -32,20 +32,20 @@ import {
32
32
  storeDirectory,
33
33
  storeDirectoryV2,
34
34
  storeFile
35
- } from "./chunk-VQHM3R6N.js";
35
+ } from "./chunk-WMSBC5B2.js";
36
36
  import "./chunk-MJTQOXBC.js";
37
37
  import "./chunk-KOSF5FDO.js";
38
38
  import "./chunk-5MRZ3V4A.js";
39
39
  import "./chunk-S7EM5VMW.js";
40
- import "./chunk-AQUBKPSF.js";
41
- import "./chunk-NE5MN2M2.js";
42
- import "./chunk-Y2OSXJIZ.js";
40
+ import "./chunk-S2WGWNP5.js";
41
+ import "./chunk-MGTWZ6XS.js";
42
+ import "./chunk-GHBHQP4S.js";
43
43
  import "./chunk-C2TS5MER.js";
44
- import "./chunk-5TW653QP.js";
44
+ import "./chunk-OLU6VQG2.js";
45
45
  import "./chunk-VOEFHED3.js";
46
- import "./chunk-KMRYJR4E.js";
47
- import "./chunk-ZGU6FOLO.js";
48
- import "./chunk-2VYG7NXN.js";
46
+ import "./chunk-5P7EZSOU.js";
47
+ import "./chunk-2IMBCUB2.js";
48
+ import "./chunk-X3F7WHSF.js";
49
49
  import {
50
50
  EXIT_CODE_NO_RETRY,
51
51
  NonRetryableError
package/dist/dotns.d.ts CHANGED
@@ -122,6 +122,7 @@ declare function sanitizeDomainLabel(label: string): string;
122
122
  declare function validateDomainLabel(label: string): string;
123
123
  declare function isCommitmentMature(chainNowSeconds: number, commitTimestampSeconds: number, minimumAgeSeconds: number): boolean;
124
124
  declare function isExplicitCommitmentBuffer(envValue: string | undefined): boolean;
125
+ declare function isLikelyCommitmentRace(msg: string): boolean;
125
126
  declare function classifyDotnsLabel(label: string): {
126
127
  status: number;
127
128
  message: string;
@@ -233,7 +234,7 @@ declare class DotNS {
233
234
  register(label: string, options?: DotNSConnectOptions & {
234
235
  status?: string;
235
236
  reverse?: boolean;
236
- }): Promise<{
237
+ }, _cli?: (argv: string[], env?: Record<string, string>) => Promise<unknown>): Promise<{
237
238
  label: string;
238
239
  owner: string;
239
240
  }>;
@@ -241,4 +242,4 @@ declare class DotNS {
241
242
  }
242
243
  declare const dotns: DotNS;
243
244
 
244
- export { CONNECTION_TIMEOUT_MS, CONTRACTS, DECIMALS, DEFAULT_MNEMONIC, DOTNS_TX_MAX_ATTEMPTS, DOT_NODE, DotNS, type DotNSConnectOptions, type DotnsCliInvocation, type DotnsCliInvocationSource, type DotnsPreflightResult, type DotnsSuccessAction, NATIVE_TO_ETH_RATIO, OPERATION_TIMEOUT_MS, type OwnershipResult, type ParsedDomainName, type PriceValidationResult, ProofOfPersonhoodStatus, RPC_ENDPOINTS, TX_CHAIN_TIME_BUDGET_MS, TX_TIMEOUT_MS, TX_WALL_CLOCK_CEILING_MS, WS_HEARTBEAT_TIMEOUT_MS, canRegister, classifyDotnsLabel, classifyTxRetryDecision, computeDomainTokenId, convertWeiToNative, countTrailingDigits, dotns, feeFloorFor, fetchNonce, fmtPas, isCommitmentMature, isExplicitCommitmentBuffer, parseDomainName, parseProofOfPersonhoodStatus, popStatusName, resolveDotnsCliInvocation, runDotnsCli, sanitizeDomainLabel, simulateUserStatus, stripTrailingDigits, validateDomainLabel, verifyNonceAdvanced };
245
+ export { CONNECTION_TIMEOUT_MS, CONTRACTS, DECIMALS, DEFAULT_MNEMONIC, DOTNS_TX_MAX_ATTEMPTS, DOT_NODE, DotNS, type DotNSConnectOptions, type DotnsCliInvocation, type DotnsCliInvocationSource, type DotnsPreflightResult, type DotnsSuccessAction, NATIVE_TO_ETH_RATIO, OPERATION_TIMEOUT_MS, type OwnershipResult, type ParsedDomainName, type PriceValidationResult, ProofOfPersonhoodStatus, RPC_ENDPOINTS, TX_CHAIN_TIME_BUDGET_MS, TX_TIMEOUT_MS, TX_WALL_CLOCK_CEILING_MS, WS_HEARTBEAT_TIMEOUT_MS, canRegister, classifyDotnsLabel, classifyTxRetryDecision, computeDomainTokenId, convertWeiToNative, countTrailingDigits, dotns, feeFloorFor, fetchNonce, fmtPas, isCommitmentMature, isExplicitCommitmentBuffer, isLikelyCommitmentRace, parseDomainName, parseProofOfPersonhoodStatus, popStatusName, resolveDotnsCliInvocation, runDotnsCli, sanitizeDomainLabel, simulateUserStatus, stripTrailingDigits, validateDomainLabel, verifyNonceAdvanced };
package/dist/dotns.js CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  fmtPas,
27
27
  isCommitmentMature,
28
28
  isExplicitCommitmentBuffer,
29
+ isLikelyCommitmentRace,
29
30
  parseDomainName,
30
31
  parseProofOfPersonhoodStatus,
31
32
  popStatusName,
@@ -36,10 +37,10 @@ import {
36
37
  stripTrailingDigits,
37
38
  validateDomainLabel,
38
39
  verifyNonceAdvanced
39
- } from "./chunk-5TW653QP.js";
40
+ } from "./chunk-OLU6VQG2.js";
40
41
  import "./chunk-VOEFHED3.js";
41
- import "./chunk-KMRYJR4E.js";
42
- import "./chunk-ZGU6FOLO.js";
42
+ import "./chunk-5P7EZSOU.js";
43
+ import "./chunk-2IMBCUB2.js";
43
44
  import "./chunk-QGM4M3NI.js";
44
45
  export {
45
46
  CONNECTION_TIMEOUT_MS,
@@ -69,6 +70,7 @@ export {
69
70
  fmtPas,
70
71
  isCommitmentMature,
71
72
  isExplicitCommitmentBuffer,
73
+ isLikelyCommitmentRace,
72
74
  parseDomainName,
73
75
  parseProofOfPersonhoodStatus,
74
76
  popStatusName,
@@ -1,4 +1,4 @@
1
- declare const DEFAULT_ENVIRONMENTS_URL = "https://raw.githubusercontent.com/paritytech/bulletin-deploy/main/assets/environments.json";
1
+ declare const DEFAULT_ENVIRONMENTS_URL = "https://raw.githubusercontent.com/paritytech/triangle-deploy/main/assets/environments.json";
2
2
  declare const DEFAULT_ENV_ID = "paseo-next";
3
3
  declare const CACHE_TTL_MS: number;
4
4
  declare const FETCH_TIMEOUT_MS = 5000;
@@ -10,7 +10,7 @@ import {
10
10
  listEnvironments,
11
11
  loadEnvironments,
12
12
  resolveEndpoints
13
- } from "./chunk-2VYG7NXN.js";
13
+ } from "./chunk-X3F7WHSF.js";
14
14
  import "./chunk-ZOC4GITL.js";
15
15
  import "./chunk-QGM4M3NI.js";
16
16
  export {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  deploy,
3
3
  merkleizeJS,
4
4
  merkleizeWithStableOrder
5
- } from "./chunk-VQHM3R6N.js";
5
+ } from "./chunk-WMSBC5B2.js";
6
6
  import {
7
7
  computeStats,
8
8
  renderSummary,
@@ -24,16 +24,16 @@ import {
24
24
  isVolatilePath,
25
25
  parseManifest
26
26
  } from "./chunk-S7EM5VMW.js";
27
- import "./chunk-AQUBKPSF.js";
28
- import "./chunk-NE5MN2M2.js";
27
+ import "./chunk-S2WGWNP5.js";
28
+ import "./chunk-MGTWZ6XS.js";
29
29
  import {
30
30
  probeChunks
31
- } from "./chunk-Y2OSXJIZ.js";
31
+ } from "./chunk-GHBHQP4S.js";
32
32
  import "./chunk-C2TS5MER.js";
33
33
  import {
34
34
  DotNS,
35
35
  parseDomainName
36
- } from "./chunk-5TW653QP.js";
36
+ } from "./chunk-OLU6VQG2.js";
37
37
  import {
38
38
  bootstrapPool,
39
39
  derivePoolAccounts,
@@ -41,7 +41,7 @@ import {
41
41
  fetchPoolAuthorizations,
42
42
  selectAccount
43
43
  } from "./chunk-VOEFHED3.js";
44
- import "./chunk-KMRYJR4E.js";
44
+ import "./chunk-5P7EZSOU.js";
45
45
  import {
46
46
  VERSION,
47
47
  loadRunState,
@@ -51,8 +51,8 @@ import {
51
51
  shouldSkipStaleWarning,
52
52
  stateFilePath,
53
53
  writeRunState
54
- } from "./chunk-ZGU6FOLO.js";
55
- import "./chunk-2VYG7NXN.js";
54
+ } from "./chunk-2IMBCUB2.js";
55
+ import "./chunk-X3F7WHSF.js";
56
56
  import "./chunk-ZOC4GITL.js";
57
57
  import "./chunk-HOTQDYHD.js";
58
58
  import "./chunk-QGM4M3NI.js";
@@ -1,7 +1,7 @@
1
1
  import * as v8 from 'node:v8';
2
2
 
3
3
  declare function isBunRuntime(): boolean;
4
- declare const DEFAULT_THRESHOLD_MB = 1500;
4
+ declare const DEFAULT_THRESHOLD_MB = 800;
5
5
  interface MemorySampleMb {
6
6
  rssMb: number;
7
7
  heapUsedMb: number;
@@ -5,8 +5,8 @@ import {
5
5
  maybeWriteMemoryReport,
6
6
  safeHeap,
7
7
  sampleFromBytes
8
- } from "./chunk-KMRYJR4E.js";
9
- import "./chunk-ZGU6FOLO.js";
8
+ } from "./chunk-5P7EZSOU.js";
9
+ import "./chunk-2IMBCUB2.js";
10
10
  import "./chunk-QGM4M3NI.js";
11
11
  export {
12
12
  DEFAULT_THRESHOLD_MB,
package/dist/merkle.js CHANGED
@@ -5,20 +5,20 @@ import {
5
5
  merkleizeJSBackend,
6
6
  merkleizeKuboBackend,
7
7
  merkleizeWithStableOrder
8
- } from "./chunk-VQHM3R6N.js";
8
+ } from "./chunk-WMSBC5B2.js";
9
9
  import "./chunk-MJTQOXBC.js";
10
10
  import "./chunk-KOSF5FDO.js";
11
11
  import "./chunk-5MRZ3V4A.js";
12
12
  import "./chunk-S7EM5VMW.js";
13
- import "./chunk-AQUBKPSF.js";
14
- import "./chunk-NE5MN2M2.js";
15
- import "./chunk-Y2OSXJIZ.js";
13
+ import "./chunk-S2WGWNP5.js";
14
+ import "./chunk-MGTWZ6XS.js";
15
+ import "./chunk-GHBHQP4S.js";
16
16
  import "./chunk-C2TS5MER.js";
17
- import "./chunk-5TW653QP.js";
17
+ import "./chunk-OLU6VQG2.js";
18
18
  import "./chunk-VOEFHED3.js";
19
- import "./chunk-KMRYJR4E.js";
20
- import "./chunk-ZGU6FOLO.js";
21
- import "./chunk-2VYG7NXN.js";
19
+ import "./chunk-5P7EZSOU.js";
20
+ import "./chunk-2IMBCUB2.js";
21
+ import "./chunk-X3F7WHSF.js";
22
22
  import "./chunk-ZOC4GITL.js";
23
23
  import "./chunk-HOTQDYHD.js";
24
24
  import "./chunk-QGM4M3NI.js";
package/dist/run-state.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  shouldSkipStaleWarning,
8
8
  stateFilePath,
9
9
  writeRunState
10
- } from "./chunk-ZGU6FOLO.js";
10
+ } from "./chunk-2IMBCUB2.js";
11
11
  import "./chunk-QGM4M3NI.js";
12
12
  export {
13
13
  VERSION,
package/dist/telemetry.js CHANGED
@@ -27,8 +27,8 @@ import {
27
27
  truncateAddress,
28
28
  withDeploySpan,
29
29
  withSpan
30
- } from "./chunk-KMRYJR4E.js";
31
- import "./chunk-ZGU6FOLO.js";
30
+ } from "./chunk-5P7EZSOU.js";
31
+ import "./chunk-2IMBCUB2.js";
32
32
  import "./chunk-QGM4M3NI.js";
33
33
  export {
34
34
  VERSION,
@@ -8,9 +8,9 @@ import {
8
8
  isPreReleaseVersion,
9
9
  preReleaseWarning,
10
10
  promptYesNo
11
- } from "./chunk-NE5MN2M2.js";
12
- import "./chunk-KMRYJR4E.js";
13
- import "./chunk-ZGU6FOLO.js";
11
+ } from "./chunk-MGTWZ6XS.js";
12
+ import "./chunk-5P7EZSOU.js";
13
+ import "./chunk-2IMBCUB2.js";
14
14
  import "./chunk-QGM4M3NI.js";
15
15
  export {
16
16
  assessVersion,
package/docs/telemetry.md CHANGED
@@ -29,8 +29,11 @@ If another app embeds `bulletin-deploy` and already owns Sentry initialization,
29
29
  ```sh
30
30
  BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1
31
31
  BULLETIN_DEPLOY_HOST_APP=<your-app-name>
32
+ BULLETIN_DEPLOY_HOST_APP_VERSION=<your-app-version>
32
33
  ```
33
34
 
35
+ `BULLETIN_DEPLOY_HOST_APP_VERSION` is optional but recommended — it populates `deploy.host_app_version` on every span, enabling version-correlated triage in the dashboard.
36
+
34
37
  That makes `bulletin-deploy` reuse the existing Sentry client instead of calling its own `Sentry.init()`.
35
38
 
36
39
  Requirements:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulletin-deploy",
3
- "version": "0.7.15",
3
+ "version": "0.7.16-rc.2",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",