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 +38 -0
- package/bin/bulletin-deploy +4 -0
- package/dist/bug-report.js +2 -2
- package/dist/{chunk-KAZ2RBCU.js → chunk-CEONE52K.js} +42 -4
- package/dist/{chunk-I6TQVB4Q.js → chunk-ONWIYUHB.js} +32 -5
- package/dist/{chunk-GZ5UUECB.js → chunk-QILGABSF.js} +13 -4
- package/dist/{chunk-KV2NA7JZ.js → chunk-SR723AU4.js} +1 -1
- package/dist/chunk-UZVOH3HB.js +183 -0
- package/dist/{chunk-BCFGXQRW.js → chunk-ZPUAL4HM.js} +2 -3
- package/dist/deploy.d.ts +9 -0
- package/dist/deploy.js +5 -4
- package/dist/dotns.d.ts +1 -0
- package/dist/dotns.js +2 -2
- package/dist/gh-pages-mirror.d.ts +51 -0
- package/dist/gh-pages-mirror.js +27 -0
- package/dist/index.js +5 -4
- package/dist/merkle.js +1 -1
- package/dist/telemetry.js +1 -1
- package/dist/version-check.js +2 -2
- package/package.json +2 -3
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).
|
package/bin/bulletin-deploy
CHANGED
|
@@ -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;
|
package/dist/bug-report.js
CHANGED
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
classifyErrorArea,
|
|
3
3
|
isInteractive,
|
|
4
4
|
promptYesNo
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SR723AU4.js";
|
|
6
6
|
import {
|
|
7
7
|
VERSION
|
|
8
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
|
67
|
-
await writer.put({ cid, 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;
|
|
@@ -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.
|
|
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-
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
32
|
+
} from "./chunk-ONWIYUHB.js";
|
|
33
33
|
import "./chunk-JHNW2EKY.js";
|
|
34
|
-
import "./chunk-
|
|
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-
|
|
3
|
+
} from "./chunk-CEONE52K.js";
|
|
4
4
|
import {
|
|
5
5
|
DotNS
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ONWIYUHB.js";
|
|
7
|
+
import "./chunk-UZVOH3HB.js";
|
|
7
8
|
import {
|
|
8
9
|
merkleizeJS
|
|
9
|
-
} from "./chunk-
|
|
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-
|
|
18
|
+
import "./chunk-ZPUAL4HM.js";
|
|
18
19
|
import "./chunk-QGM4M3NI.js";
|
|
19
20
|
export {
|
|
20
21
|
DotNS,
|
package/dist/merkle.js
CHANGED
package/dist/telemetry.js
CHANGED
package/dist/version-check.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
isPreReleaseVersion,
|
|
9
9
|
preReleaseWarning,
|
|
10
10
|
promptYesNo
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
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.
|
|
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",
|