bulletin-deploy 0.6.13 → 0.6.15

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
@@ -70,9 +70,47 @@ Options:
70
70
  (see Telemetry section). Use to isolate test/benchmark/canary
71
71
  runs from real-user traffic in Sentry dashboards. Also readable
72
72
  from DEPLOY_TAG env var.
73
+ --gh-pages-mirror After a successful deploy, push the CAR to the current repo's
74
+ gh-pages branch as a fast-path HTTP cache for host apps. Opt-in.
75
+ See "GitHub Pages mirror" below.
73
76
  --help Show help
74
77
  ```
75
78
 
79
+ ### GitHub Pages mirror (opt-in, experimental)
80
+
81
+ Loading a dapp via a smoldot light client is slow. As a short-term cache, `--gh-pages-mirror` pushes the CAR to the current repo's `gh-pages` branch so host apps (dot.li, desktop) can fetch it via HTTP while the chain path catches up.
82
+
83
+ ```bash
84
+ bulletin-deploy --gh-pages-mirror ./dist my-app.dot
85
+ ```
86
+
87
+ After the Bulletin upload + DotNS registration succeed, the CLI commits:
88
+
89
+ ```
90
+ bulletin/my-app.dot.car # the CAR, byte-for-byte identical to what's on Bulletin
91
+ bulletin/my-app.dot.json # {domain, cid, toolVersion, deployedAt, encrypted, bulletinRpc, sourceRepo, sourceCommit}
92
+ ```
93
+
94
+ to `gh-pages` and pushes. The mirror URL is printed at the end of the deploy:
95
+
96
+ ```
97
+ Mirror: https://<owner>.github.io/<repo>/bulletin/my-app.dot.car
98
+ ```
99
+
100
+ Requirements:
101
+
102
+ - **GitHub Pages enabled** on the repo with `gh-pages` as the source branch (one-time, via repo Settings → Pages).
103
+ - **Push permissions.** In CI, set `permissions: contents: write` on the workflow. Locally, your regular git credentials suffice.
104
+ - **CAR ≤ 100 MB** (GitHub's single-file soft limit). Larger CARs are skipped with a log line; a Releases fallback for bigger CARs is a planned follow-up.
105
+ - **Directory deploys only.** Pre-chunked `Array<Uint8Array>` / `Uint8Array` / single-file content doesn't produce a CAR and is skipped with a log line.
106
+
107
+ Limitations / follow-ups:
108
+
109
+ - **Discoverability is the caller's problem today.** The URL is printed at deploy time; host apps must be told which `<owner>/<repo>` to fetch from. A DotNS text record (or similar on-chain pointer) is the clean long-term answer and lives in a follow-up.
110
+ - **Encrypted deploys mirror encrypted bytes.** `--password` deploys need the password to decrypt from the mirror too.
111
+ - **Mirror failures are non-fatal.** The source of truth is Bulletin + DotNS; the mirror is a cache. Failures log and let the deploy succeed.
112
+ - **GitHub Pages build latency.** The CAR lands on `gh-pages` immediately; Pages serves it after the build completes (~1–2 min in practice). Hosts should fall back to Bulletin while the 404 window lasts.
113
+
76
114
  ### Playground registry
77
115
 
78
116
  By default, deploys only upload to Bulletin storage and register the DotNS domain. The **Playground remix registry** is an on-chain app directory that makes your deploy visible in [Polkadot Playground](https://playground.polkadot.cloud).
@@ -21,6 +21,7 @@ for (let i = 0; i < args.length; i++) {
21
21
  else if (args[i] === "--playground") { flags.playground = true; }
22
22
  else if (args[i] === "--js-merkle") { flags.jsMerkle = true; }
23
23
  else if (args[i] === "--tag") { flags.tag = args[++i]; }
24
+ else if (args[i] === "--gh-pages-mirror") { flags.ghPagesMirror = true; }
24
25
  else if (args[i] === "--version" || args[i] === "-V") { flags.version = true; }
25
26
  else if (args[i] === "--help" || args[i] === "-h") { flags.help = true; }
26
27
  else { positional.push(args[i]); }
@@ -47,6 +48,8 @@ Options:
47
48
  --playground Publish to the playground remix registry
48
49
  --js-merkle Use pure-JS merkleization (no IPFS Kubo binary required)
49
50
  --tag "..." Label deploy in telemetry (or set DEPLOY_TAG env var); see Telemetry in README
51
+ --gh-pages-mirror After deploy, push the CAR to the current repo's gh-pages branch
52
+ at bulletin/<domain>.dot.car (opt-in; also set GH_PAGES_MIRROR=1)
50
53
  --version Show version
51
54
  --help Show this help`);
52
55
  process.exit(0);
@@ -83,6 +86,7 @@ try {
83
86
  password: flags.password,
84
87
  jsMerkle: flags.jsMerkle,
85
88
  tag: flags.tag,
89
+ ghPagesMirror: flags.ghPagesMirror,
86
90
  });
87
91
 
88
92
  const output = process.env.GITHUB_OUTPUT;
@@ -2,10 +2,10 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-KV2NA7JZ.js";
5
+ } from "./chunk-SR723AU4.js";
6
6
  import {
7
7
  VERSION
8
- } from "./chunk-BCFGXQRW.js";
8
+ } from "./chunk-ZPUAL4HM.js";
9
9
  import "./chunk-QGM4M3NI.js";
10
10
 
11
11
  // src/bug-report.ts
@@ -5,10 +5,14 @@ import {
5
5
  fetchNonce,
6
6
  popStatusName,
7
7
  validateDomainLabel
8
- } from "./chunk-I6TQVB4Q.js";
8
+ } from "./chunk-ONWIYUHB.js";
9
+ import {
10
+ MirrorSkipped,
11
+ mirrorToGitHubPages
12
+ } from "./chunk-UZVOH3HB.js";
9
13
  import {
10
14
  merkleizeJS
11
- } from "./chunk-GZ5UUECB.js";
15
+ } from "./chunk-QILGABSF.js";
12
16
  import {
13
17
  derivePoolAccounts,
14
18
  detectTestnet,
@@ -28,7 +32,7 @@ import {
28
32
  truncateAddress,
29
33
  withDeploySpan,
30
34
  withSpan
31
- } from "./chunk-BCFGXQRW.js";
35
+ } from "./chunk-ZPUAL4HM.js";
32
36
 
33
37
  // src/deploy.ts
34
38
  import { Buffer } from "buffer";
@@ -849,7 +853,7 @@ async function storeDirectory(directoryPath, provider = {}, password, jsMerkle)
849
853
  const storageCid = await withSpan("deploy.chunk-upload", "1b. chunk-upload", { "deploy.chunks.total": carChunks.length, "deploy.car.bytes": carContent.length }, async () => {
850
854
  return storeChunkedContent(carChunks, provider);
851
855
  });
852
- return { storageCid, ipfsCid };
856
+ return { storageCid, ipfsCid, carBytes: carContent };
853
857
  }
854
858
  async function estimateUploadBytes(content) {
855
859
  try {
@@ -892,6 +896,7 @@ async function deploy(content, domainName = null, options = {}) {
892
896
  }
893
897
  let cid;
894
898
  let ipfsCid;
899
+ let mirrorCarBytes;
895
900
  console.log("\n" + "=".repeat(60));
896
901
  console.log(`DEPLOYING TO TESTNET v${VERSION}`);
897
902
  console.log("=".repeat(60));
@@ -995,6 +1000,7 @@ async function deploy(content, domainName = null, options = {}) {
995
1000
  const dirResult = await storeDirectory(contentPath, providerWithReconnect, options.password, options.jsMerkle);
996
1001
  cid = dirResult.storageCid;
997
1002
  ipfsCid = dirResult.ipfsCid;
1003
+ mirrorCarBytes = dirResult.carBytes;
998
1004
  } else {
999
1005
  console.log(`
1000
1006
  Mode: File`);
@@ -1056,6 +1062,38 @@ async function deploy(content, domainName = null, options = {}) {
1056
1062
  await dotns.setContenthash(name, contenthashHex);
1057
1063
  dotns.disconnect();
1058
1064
  });
1065
+ if (options.ghPagesMirror) {
1066
+ 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
+ }
1059
1097
  if (options.playground) {
1060
1098
  console.log("\n" + "=".repeat(60));
1061
1099
  console.log("Playground Registry");
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  captureWarning,
6
6
  withSpan
7
- } from "./chunk-BCFGXQRW.js";
7
+ } from "./chunk-ZPUAL4HM.js";
8
8
 
9
9
  // src/dotns.ts
10
10
  import crypto from "crypto";
@@ -220,10 +220,18 @@ function classifyDotnsLabel(label) {
220
220
  const totalLength = label.length;
221
221
  const trailingDigits = countTrailingDigits(label);
222
222
  if (trailingDigits > 2) {
223
- return { status: ProofOfPersonhoodStatus.Reserved, message: "Name can have maximum 2 digit suffix" };
223
+ return {
224
+ status: ProofOfPersonhoodStatus.Reserved,
225
+ message: `Name has ${trailingDigits} trailing digits; DotNS allows at most 2 trailing digits. Use a base name with 0-2 trailing digits.`
226
+ };
224
227
  }
225
228
  const baselength = totalLength - trailingDigits;
226
- if (baselength <= 5) return { status: ProofOfPersonhoodStatus.Reserved, message: "Reserved for Governance" };
229
+ if (baselength <= 5) {
230
+ return {
231
+ status: ProofOfPersonhoodStatus.Reserved,
232
+ message: `Base name is ${baselength} char${baselength === 1 ? "" : "s"}; DotNS reserves base names of 5 chars or fewer for governance (PopRules). Use a base name of 6+ chars \u2014 role prefixes like 'rc<N>pool' / 'rc<N>dir' / 'nightly-<role>' work well.`
233
+ };
234
+ }
227
235
  if (baselength >= 6 && baselength <= 8) {
228
236
  if (trailingDigits === 2) return { status: ProofOfPersonhoodStatus.ProofOfPersonhoodLite, message: "Requires Light personhood verification" };
229
237
  return { status: ProofOfPersonhoodStatus.ProofOfPersonhoodFull, message: "Requires Full personhood verification" };
@@ -730,6 +738,16 @@ var DotNS = class {
730
738
  }
731
739
  console.log(` Owner: ${actualOwner}`);
732
740
  }
741
+ async getContenthash(domainName) {
742
+ this.ensureConnected();
743
+ const node = namehash(`${domainName}.dot`);
744
+ const result = await withTimeout(
745
+ this.contractCall(CONTRACTS.DOTNS_CONTENT_RESOLVER, DOTNS_CONTENT_RESOLVER_ABI, "contenthash", [node]),
746
+ 3e4,
747
+ "contenthash"
748
+ );
749
+ return typeof result === "string" ? result : result?.toString?.() ?? String(result);
750
+ }
733
751
  async setContenthash(domainName, contenthashHex) {
734
752
  return withSpan("deploy.dotns.set-contenthash", "2b. set-contenthash", {}, async () => {
735
753
  this.ensureConnected();
@@ -745,7 +763,15 @@ var DotNS = class {
745
763
  console.log(` Setting contenthash: ${ipfsCid || contenthashHex}`);
746
764
  const txHash = await this.contractTransaction(CONTRACTS.DOTNS_CONTENT_RESOLVER, 0n, DOTNS_CONTENT_RESOLVER_ABI, "setContenthash", [node, contenthashHex], (s) => console.log(` ${s}`), { useNoncePolling: true });
747
765
  console.log(` Tx: ${txHash}`);
748
- console.log(` Contenthash set successfully!
766
+ const onChainRaw = await this.getContenthash(domainName);
767
+ const onChain = (onChainRaw || "0x").toLowerCase();
768
+ const expected = contenthashHex.toLowerCase();
769
+ if (onChain !== expected) {
770
+ throw new Error(
771
+ `Post-deploy verification failed for ${domainName}.dot: on-chain contenthash is ${onChain}, not the ${expected} we just wrote. The setContenthash tx may have silently failed, or another party overwrote the domain. Re-run the deploy to retry.`
772
+ );
773
+ }
774
+ console.log(` Verified on-chain: ${ipfsCid || contenthashHex}
749
775
  `);
750
776
  return { node };
751
777
  });
@@ -761,6 +787,7 @@ var DotNS = class {
761
787
  const baselength = validated.length - trailingDigits;
762
788
  const classification = classifyDotnsLabel(validated);
763
789
  if (classification.status === ProofOfPersonhoodStatus.Reserved) {
790
+ const sanitizeTrail = label !== validated ? `Input "${label}" was sanitized to "${validated}" (excess trailing digits trimmed). ` : "";
764
791
  return {
765
792
  label: validated,
766
793
  classification,
@@ -773,7 +800,7 @@ var DotNS = class {
773
800
  reservationOwner: null,
774
801
  isTestnet: false,
775
802
  canProceed: false,
776
- reason: classification.message,
803
+ reason: `${sanitizeTrail}${classification.message}`,
777
804
  plannedAction: "abort",
778
805
  needsPopUpgrade: false
779
806
  };
@@ -2,8 +2,17 @@
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import { importer } from "ipfs-unixfs-importer";
5
- import { MemoryBlockstore } from "blockstore-core/memory";
6
5
  import { CarWriter } from "@ipld/car/writer";
6
+ var CidPreservingBlockstore = class {
7
+ data = /* @__PURE__ */ new Map();
8
+ async put(cid, bytes) {
9
+ this.data.set(cid.toString(), { cid, bytes });
10
+ return cid;
11
+ }
12
+ *all() {
13
+ yield* this.data.values();
14
+ }
15
+ };
7
16
  function walkDirectory(dirPath, prefix = "") {
8
17
  let dirents;
9
18
  try {
@@ -43,7 +52,7 @@ async function collectBytes(iter) {
43
52
  async function merkleizeJS(directoryPath) {
44
53
  console.log(` Merkleizing (JS): ${directoryPath}`);
45
54
  const files = walkDirectory(directoryPath);
46
- const blockstore = new MemoryBlockstore();
55
+ const blockstore = new CidPreservingBlockstore();
47
56
  const source = files.map((file) => ({
48
57
  path: file.path,
49
58
  content: (async function* () {
@@ -63,8 +72,8 @@ async function merkleizeJS(directoryPath) {
63
72
  }
64
73
  const { writer, out } = CarWriter.create([rootCid]);
65
74
  const collectPromise = collectBytes(out);
66
- for await (const { cid, bytes } of blockstore.getAll()) {
67
- await writer.put({ cid, bytes: await collectBytes(bytes) });
75
+ for (const { cid, bytes } of blockstore.all()) {
76
+ await writer.put({ cid, bytes });
68
77
  }
69
78
  await writer.close();
70
79
  const carBytes = await collectPromise;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-BCFGXQRW.js";
3
+ } from "./chunk-ZPUAL4HM.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
@@ -0,0 +1,183 @@
1
+ // src/gh-pages-mirror.ts
2
+ import { execSync } from "child_process";
3
+ import * as fs from "fs";
4
+ import * as os from "os";
5
+ import * as path from "path";
6
+ var GH_PAGES_MIRROR_MAX_BYTES = 100 * 1024 * 1024;
7
+ var GH_PAGES_MIRROR_DIR = "bulletin";
8
+ var GH_PAGES_MIRROR_BRANCH = "gh-pages";
9
+ var MirrorSkipped = class extends Error {
10
+ constructor(reason) {
11
+ super(reason);
12
+ this.name = "MirrorSkipped";
13
+ }
14
+ };
15
+ function parseGitRemoteUrl(url) {
16
+ const trimmed = url.trim();
17
+ const ssh = trimmed.match(/^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/);
18
+ if (ssh) return { owner: ssh[1], repo: ssh[2] };
19
+ const https = trimmed.match(/^https?:\/\/(?:[^@]*@)?[^/]+\/([^/]+)\/(.+?)(?:\.git)?$/);
20
+ if (https) return { owner: https[1], repo: https[2] };
21
+ return null;
22
+ }
23
+ function resolveOwnerRepo(repoPath) {
24
+ const envRepo = process.env.GITHUB_REPOSITORY;
25
+ if (envRepo && envRepo.includes("/")) {
26
+ const [owner, repo] = envRepo.split("/");
27
+ if (owner && repo) return { owner, repo };
28
+ }
29
+ try {
30
+ const url = execSync("git config --get remote.origin.url", {
31
+ cwd: repoPath,
32
+ encoding: "utf-8",
33
+ stdio: ["ignore", "pipe", "ignore"]
34
+ }).trim();
35
+ return parseGitRemoteUrl(url);
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ function resolveSourceCommit(repoPath) {
41
+ if (process.env.GITHUB_SHA) return process.env.GITHUB_SHA;
42
+ try {
43
+ return execSync("git rev-parse HEAD", {
44
+ cwd: repoPath,
45
+ encoding: "utf-8",
46
+ stdio: ["ignore", "pipe", "ignore"]
47
+ }).trim();
48
+ } catch {
49
+ return void 0;
50
+ }
51
+ }
52
+ function normalizeDomainFilename(domain) {
53
+ const label = domain.endsWith(".dot") ? domain.slice(0, -4) : domain;
54
+ if (!/^[a-z0-9-]+$/.test(label)) {
55
+ throw new Error(`Invalid domain label for mirror filename: ${JSON.stringify(domain)}`);
56
+ }
57
+ return `${label}.dot`;
58
+ }
59
+ function mirrorUrl(owner, repo, domainFilename) {
60
+ return `https://${owner}.github.io/${repo}/${GH_PAGES_MIRROR_DIR}/${domainFilename}.car`;
61
+ }
62
+ function buildManifest(input) {
63
+ return {
64
+ domain: normalizeDomainFilename(input.domain),
65
+ cid: input.cid,
66
+ toolVersion: input.toolVersion,
67
+ deployedAt: input.deployedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
68
+ encrypted: input.encrypted,
69
+ bulletinRpc: input.bulletinRpc,
70
+ sourceRepo: input.sourceRepo,
71
+ sourceCommit: input.sourceCommit
72
+ };
73
+ }
74
+ function runGit(args, cwd, extraEnv = {}) {
75
+ return execSync(`git ${args.join(" ")}`, {
76
+ cwd,
77
+ encoding: "utf-8",
78
+ stdio: ["ignore", "pipe", "pipe"],
79
+ env: { ...process.env, ...extraEnv }
80
+ }).trim();
81
+ }
82
+ function pushRemoteUrl(owner, repo, token) {
83
+ const authedOwner = token ? `x-access-token:${token}@github.com` : "github.com";
84
+ return `https://${authedOwner}/${owner}/${repo}.git`;
85
+ }
86
+ async function mirrorToGitHubPages(input) {
87
+ const repoPath = input.repoPath ?? process.cwd();
88
+ const ownerRepo = resolveOwnerRepo(repoPath);
89
+ if (!ownerRepo) {
90
+ throw new MirrorSkipped("no GitHub repo detected (GITHUB_REPOSITORY unset and no github.com remote)");
91
+ }
92
+ if (input.carBytes.length > GH_PAGES_MIRROR_MAX_BYTES) {
93
+ const mb = (input.carBytes.length / 1024 / 1024).toFixed(1);
94
+ throw new MirrorSkipped(`CAR is ${mb} MB; GitHub limits single files to 100 MB. Mirror skipped.`);
95
+ }
96
+ const domainFilename = normalizeDomainFilename(input.domain);
97
+ const { owner, repo } = ownerRepo;
98
+ const sourceCommit = input.sourceCommit ?? resolveSourceCommit(repoPath);
99
+ const sourceRepo = input.sourceRepo ?? `${owner}/${repo}`;
100
+ const manifest = buildManifest({ ...input, sourceCommit, sourceRepo });
101
+ const workTree = fs.mkdtempSync(path.join(os.tmpdir(), "bulletin-mirror-"));
102
+ const token = input.githubToken ?? process.env.GITHUB_TOKEN;
103
+ try {
104
+ let branchExists = false;
105
+ try {
106
+ execSync(`git ls-remote --exit-code --heads origin ${GH_PAGES_MIRROR_BRANCH}`, {
107
+ cwd: repoPath,
108
+ stdio: ["ignore", "ignore", "ignore"]
109
+ });
110
+ branchExists = true;
111
+ } catch {
112
+ branchExists = false;
113
+ }
114
+ if (branchExists) {
115
+ runGit(["fetch", "origin", `${GH_PAGES_MIRROR_BRANCH}:${GH_PAGES_MIRROR_BRANCH}`, "--depth=1"], repoPath);
116
+ runGit(["worktree", "add", workTree, GH_PAGES_MIRROR_BRANCH], repoPath);
117
+ } else {
118
+ runGit(["worktree", "add", "--detach", workTree, "HEAD"], repoPath);
119
+ runGit(["checkout", "--orphan", GH_PAGES_MIRROR_BRANCH], workTree);
120
+ runGit(["rm", "-rf", "--quiet", "."], workTree);
121
+ }
122
+ const mirrorDir = path.join(workTree, GH_PAGES_MIRROR_DIR);
123
+ fs.mkdirSync(mirrorDir, { recursive: true });
124
+ const carPath = path.join(mirrorDir, `${domainFilename}.car`);
125
+ const manifestPath = path.join(mirrorDir, `${domainFilename}.json`);
126
+ fs.writeFileSync(carPath, input.carBytes);
127
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
128
+ runGit(["-c", `user.email=bulletin-deploy@noreply.github.com`, "-c", "user.name=bulletin-deploy", "add", GH_PAGES_MIRROR_DIR], workTree);
129
+ const status = runGit(["status", "--porcelain"], workTree);
130
+ if (status.length === 0) {
131
+ return {
132
+ url: mirrorUrl(owner, repo, domainFilename),
133
+ owner,
134
+ repo,
135
+ carPath: path.posix.join(GH_PAGES_MIRROR_DIR, `${domainFilename}.car`),
136
+ manifestPath: path.posix.join(GH_PAGES_MIRROR_DIR, `${domainFilename}.json`)
137
+ };
138
+ }
139
+ runGit(
140
+ [
141
+ "-c",
142
+ "user.email=bulletin-deploy@noreply.github.com",
143
+ "-c",
144
+ "user.name=bulletin-deploy",
145
+ "commit",
146
+ "-m",
147
+ `"mirror(bulletin): ${domainFilename} @ ${input.cid.slice(0, 12)}"`
148
+ ],
149
+ workTree
150
+ );
151
+ runGit(["push", pushRemoteUrl(owner, repo, token), `HEAD:${GH_PAGES_MIRROR_BRANCH}`], workTree);
152
+ return {
153
+ url: mirrorUrl(owner, repo, domainFilename),
154
+ owner,
155
+ repo,
156
+ carPath: path.posix.join(GH_PAGES_MIRROR_DIR, `${domainFilename}.car`),
157
+ manifestPath: path.posix.join(GH_PAGES_MIRROR_DIR, `${domainFilename}.json`)
158
+ };
159
+ } finally {
160
+ try {
161
+ runGit(["worktree", "remove", "--force", workTree], repoPath);
162
+ } catch {
163
+ }
164
+ try {
165
+ fs.rmSync(workTree, { recursive: true, force: true });
166
+ } catch {
167
+ }
168
+ }
169
+ }
170
+
171
+ export {
172
+ GH_PAGES_MIRROR_MAX_BYTES,
173
+ GH_PAGES_MIRROR_DIR,
174
+ GH_PAGES_MIRROR_BRANCH,
175
+ MirrorSkipped,
176
+ parseGitRemoteUrl,
177
+ resolveOwnerRepo,
178
+ resolveSourceCommit,
179
+ normalizeDomainFilename,
180
+ mirrorUrl,
181
+ buildManifest,
182
+ mirrorToGitHubPages
183
+ };
@@ -7,7 +7,7 @@ import * as path from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "bulletin-deploy",
10
- version: "0.6.13",
10
+ version: "0.6.15",
11
11
  private: false,
12
12
  repository: {
13
13
  type: "git",
@@ -35,7 +35,7 @@ var package_default = {
35
35
  "cdm.json"
36
36
  ],
37
37
  scripts: {
38
- build: "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/merkle.ts src/version-check.ts src/bug-report.ts --format esm --dts --clean --target node22",
38
+ build: "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts --format esm --dts --clean --target node22",
39
39
  prepare: "npm run build",
40
40
  test: "npm run build && node --test test/test.js test/pool.test.js test/helpers/e2e-helpers.test.js",
41
41
  "test:e2e": "npm run build && node --test test/e2e.test.js",
@@ -55,7 +55,6 @@ var package_default = {
55
55
  "@polkadot/keyring": "^13.0.0",
56
56
  "@polkadot/util-crypto": "^13.0.0",
57
57
  "@sentry/node": "^9.14.0",
58
- "blockstore-core": "^6.1.3",
59
58
  "ipfs-unixfs": "^11.2.0",
60
59
  "ipfs-unixfs-importer": "^16.1.4",
61
60
  multiformats: "^13.4.1",
package/dist/deploy.d.ts CHANGED
@@ -53,6 +53,7 @@ declare function merkleize(directoryPath: string, outputCarPath: string): Promis
53
53
  declare function storeDirectory(directoryPath: string, provider?: ExistingProvider, password?: string, jsMerkle?: boolean): Promise<{
54
54
  storageCid: string;
55
55
  ipfsCid: string;
56
+ carBytes: Uint8Array;
56
57
  }>;
57
58
  interface DeployOptions {
58
59
  playground?: boolean;
@@ -76,6 +77,14 @@ interface DeployOptions {
76
77
  tag?: string;
77
78
  /** Custom telemetry attributes, merged into the deploy span. Overrides auto-detected values. */
78
79
  attributes?: Record<string, string>;
80
+ /**
81
+ * Opt-in: after a successful deploy, push the CAR to the current repo's
82
+ * `gh-pages` branch under `bulletin/<domain>.dot.car` so hosts can fetch it
83
+ * via `https://<owner>.github.io/<repo>/bulletin/<domain>.dot.car` as a
84
+ * fast-path cache. Non-fatal on failure. See docs/… for the discoverability
85
+ * caveat.
86
+ */
87
+ ghPagesMirror?: boolean;
79
88
  }
80
89
  declare function estimateUploadBytes(content: DeployContent): Promise<number | null>;
81
90
  declare function deploy(content: DeployContent, domainName?: string | null, options?: DeployOptions): Promise<DeployResult>;
package/dist/deploy.js CHANGED
@@ -23,11 +23,12 @@ import {
23
23
  storeChunkedContent,
24
24
  storeDirectory,
25
25
  storeFile
26
- } from "./chunk-KAZ2RBCU.js";
27
- import "./chunk-I6TQVB4Q.js";
28
- import "./chunk-GZ5UUECB.js";
26
+ } from "./chunk-CEONE52K.js";
27
+ import "./chunk-ONWIYUHB.js";
28
+ import "./chunk-UZVOH3HB.js";
29
+ import "./chunk-QILGABSF.js";
29
30
  import "./chunk-JHNW2EKY.js";
30
- import "./chunk-BCFGXQRW.js";
31
+ import "./chunk-ZPUAL4HM.js";
31
32
  import "./chunk-QGM4M3NI.js";
32
33
  export {
33
34
  DEFAULT_BULLETIN_RPC,
package/dist/dotns.d.ts CHANGED
@@ -153,6 +153,7 @@ declare class DotNS {
153
153
  getPriceAndValidate(label: string): Promise<PriceValidationResult>;
154
154
  finalizeRegistration(registration: any, priceWei: bigint): Promise<void>;
155
155
  verifyOwnership(label: string): Promise<void>;
156
+ getContenthash(domainName: string): Promise<string>;
156
157
  setContenthash(domainName: string, contenthashHex: string): Promise<{
157
158
  node: string;
158
159
  }>;
package/dist/dotns.js CHANGED
@@ -29,9 +29,9 @@ import {
29
29
  simulateUserStatus,
30
30
  stripTrailingDigits,
31
31
  validateDomainLabel
32
- } from "./chunk-I6TQVB4Q.js";
32
+ } from "./chunk-ONWIYUHB.js";
33
33
  import "./chunk-JHNW2EKY.js";
34
- import "./chunk-BCFGXQRW.js";
34
+ import "./chunk-ZPUAL4HM.js";
35
35
  import "./chunk-QGM4M3NI.js";
36
36
  export {
37
37
  CONNECTION_TIMEOUT_MS,
@@ -0,0 +1,51 @@
1
+ declare const GH_PAGES_MIRROR_MAX_BYTES: number;
2
+ declare const GH_PAGES_MIRROR_DIR = "bulletin";
3
+ declare const GH_PAGES_MIRROR_BRANCH = "gh-pages";
4
+ interface MirrorInput {
5
+ domain: string;
6
+ carBytes: Uint8Array;
7
+ cid: string;
8
+ toolVersion: string;
9
+ bulletinRpc: string;
10
+ encrypted: boolean;
11
+ deployedAt?: string;
12
+ sourceCommit?: string;
13
+ sourceRepo?: string;
14
+ repoPath?: string;
15
+ githubToken?: string;
16
+ }
17
+ interface MirrorResult {
18
+ url: string;
19
+ owner: string;
20
+ repo: string;
21
+ carPath: string;
22
+ manifestPath: string;
23
+ }
24
+ interface MirrorManifest {
25
+ domain: string;
26
+ cid: string;
27
+ toolVersion: string;
28
+ deployedAt: string;
29
+ encrypted: boolean;
30
+ bulletinRpc: string;
31
+ sourceRepo?: string;
32
+ sourceCommit?: string;
33
+ }
34
+ declare class MirrorSkipped extends Error {
35
+ constructor(reason: string);
36
+ }
37
+ declare function parseGitRemoteUrl(url: string): {
38
+ owner: string;
39
+ repo: string;
40
+ } | null;
41
+ declare function resolveOwnerRepo(repoPath: string): {
42
+ owner: string;
43
+ repo: string;
44
+ } | null;
45
+ declare function resolveSourceCommit(repoPath: string): string | undefined;
46
+ declare function normalizeDomainFilename(domain: string): string;
47
+ declare function mirrorUrl(owner: string, repo: string, domainFilename: string): string;
48
+ declare function buildManifest(input: Omit<MirrorInput, "carBytes" | "repoPath" | "githubToken">): MirrorManifest;
49
+ declare function mirrorToGitHubPages(input: MirrorInput): Promise<MirrorResult>;
50
+
51
+ export { GH_PAGES_MIRROR_BRANCH, GH_PAGES_MIRROR_DIR, GH_PAGES_MIRROR_MAX_BYTES, type MirrorInput, type MirrorManifest, type MirrorResult, MirrorSkipped, buildManifest, mirrorToGitHubPages, mirrorUrl, normalizeDomainFilename, parseGitRemoteUrl, resolveOwnerRepo, resolveSourceCommit };
@@ -0,0 +1,27 @@
1
+ import {
2
+ GH_PAGES_MIRROR_BRANCH,
3
+ GH_PAGES_MIRROR_DIR,
4
+ GH_PAGES_MIRROR_MAX_BYTES,
5
+ MirrorSkipped,
6
+ buildManifest,
7
+ mirrorToGitHubPages,
8
+ mirrorUrl,
9
+ normalizeDomainFilename,
10
+ parseGitRemoteUrl,
11
+ resolveOwnerRepo,
12
+ resolveSourceCommit
13
+ } from "./chunk-UZVOH3HB.js";
14
+ import "./chunk-QGM4M3NI.js";
15
+ export {
16
+ GH_PAGES_MIRROR_BRANCH,
17
+ GH_PAGES_MIRROR_DIR,
18
+ GH_PAGES_MIRROR_MAX_BYTES,
19
+ MirrorSkipped,
20
+ buildManifest,
21
+ mirrorToGitHubPages,
22
+ mirrorUrl,
23
+ normalizeDomainFilename,
24
+ parseGitRemoteUrl,
25
+ resolveOwnerRepo,
26
+ resolveSourceCommit
27
+ };
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  deploy
3
- } from "./chunk-KAZ2RBCU.js";
3
+ } from "./chunk-CEONE52K.js";
4
4
  import {
5
5
  DotNS
6
- } from "./chunk-I6TQVB4Q.js";
6
+ } from "./chunk-ONWIYUHB.js";
7
+ import "./chunk-UZVOH3HB.js";
7
8
  import {
8
9
  merkleizeJS
9
- } from "./chunk-GZ5UUECB.js";
10
+ } from "./chunk-QILGABSF.js";
10
11
  import {
11
12
  bootstrapPool,
12
13
  derivePoolAccounts,
@@ -14,7 +15,7 @@ import {
14
15
  fetchPoolAuthorizations,
15
16
  selectAccount
16
17
  } from "./chunk-JHNW2EKY.js";
17
- import "./chunk-BCFGXQRW.js";
18
+ import "./chunk-ZPUAL4HM.js";
18
19
  import "./chunk-QGM4M3NI.js";
19
20
  export {
20
21
  DotNS,
package/dist/merkle.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  merkleizeJS
3
- } from "./chunk-GZ5UUECB.js";
3
+ } from "./chunk-QILGABSF.js";
4
4
  import "./chunk-QGM4M3NI.js";
5
5
  export {
6
6
  merkleizeJS
package/dist/telemetry.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  truncateAddress,
19
19
  withDeploySpan,
20
20
  withSpan
21
- } from "./chunk-BCFGXQRW.js";
21
+ } from "./chunk-ZPUAL4HM.js";
22
22
  import "./chunk-QGM4M3NI.js";
23
23
  export {
24
24
  VERSION,
@@ -8,8 +8,8 @@ import {
8
8
  isPreReleaseVersion,
9
9
  preReleaseWarning,
10
10
  promptYesNo
11
- } from "./chunk-KV2NA7JZ.js";
12
- import "./chunk-BCFGXQRW.js";
11
+ } from "./chunk-SR723AU4.js";
12
+ import "./chunk-ZPUAL4HM.js";
13
13
  import "./chunk-QGM4M3NI.js";
14
14
  export {
15
15
  assessVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulletin-deploy",
3
- "version": "0.6.13",
3
+ "version": "0.6.15",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "cdm.json"
29
29
  ],
30
30
  "scripts": {
31
- "build": "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/merkle.ts src/version-check.ts src/bug-report.ts --format esm --dts --clean --target node22",
31
+ "build": "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts --format esm --dts --clean --target node22",
32
32
  "prepare": "npm run build",
33
33
  "test": "npm run build && node --test test/test.js test/pool.test.js test/helpers/e2e-helpers.test.js",
34
34
  "test:e2e": "npm run build && node --test test/e2e.test.js",
@@ -48,7 +48,6 @@
48
48
  "@polkadot/keyring": "^13.0.0",
49
49
  "@polkadot/util-crypto": "^13.0.0",
50
50
  "@sentry/node": "^9.14.0",
51
- "blockstore-core": "^6.1.3",
52
51
  "ipfs-unixfs": "^11.2.0",
53
52
  "ipfs-unixfs-importer": "^16.1.4",
54
53
  "multiformats": "^13.4.1",