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