@parity/product-deploy 0.7.28-rc.0
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/LICENSE +201 -0
- package/README.md +233 -0
- package/assets/environments.json +313 -0
- package/bin/bulletin-bootstrap +84 -0
- package/bin/bulletin-deploy +429 -0
- package/dist/bug-report.d.ts +29 -0
- package/dist/bug-report.js +27 -0
- package/dist/chunk-2VAUMZB2.js +284 -0
- package/dist/chunk-43HLT335.js +232 -0
- package/dist/chunk-5VZQ2KSU.js +231 -0
- package/dist/chunk-ADNBLFDP.js +225 -0
- package/dist/chunk-BMAEWZYV.js +24 -0
- package/dist/chunk-C2TS5MER.js +64 -0
- package/dist/chunk-DNXH4QTI.js +2336 -0
- package/dist/chunk-FZWJV5AD.js +231 -0
- package/dist/chunk-GZD2UFLR.js +8 -0
- package/dist/chunk-HOTQDYHD.js +219 -0
- package/dist/chunk-IDYGYIMH.js +207 -0
- package/dist/chunk-KHVTYIIX.js +146 -0
- package/dist/chunk-KJH2T5TQ.js +172 -0
- package/dist/chunk-KOSF5FDO.js +49 -0
- package/dist/chunk-LZJMVPYW.js +156 -0
- package/dist/chunk-MFTODIIT.js +725 -0
- package/dist/chunk-MMAZFJDG.js +91 -0
- package/dist/chunk-NF2FL4ZO.js +164 -0
- package/dist/chunk-OITUIM2E.js +524 -0
- package/dist/chunk-P6CHOMN3.js +2368 -0
- package/dist/chunk-QMYW3D6E.js +316 -0
- package/dist/chunk-QTZNULSH.js +185 -0
- package/dist/chunk-RI3ZLNPN.js +71 -0
- package/dist/chunk-S7EM5VMW.js +108 -0
- package/dist/chunk-T7EEVWNU.js +32 -0
- package/dist/chunk-UPWEOGLQ.js +37 -0
- package/dist/chunk-ZOC4GITL.js +13 -0
- package/dist/chunk-ZYVGHDMU.js +117 -0
- package/dist/chunk-probe.d.ts +37 -0
- package/dist/chunk-probe.js +18 -0
- package/dist/chunker.d.ts +8 -0
- package/dist/chunker.js +10 -0
- package/dist/deploy.d.ts +299 -0
- package/dist/deploy.js +96 -0
- package/dist/dotns.d.ts +506 -0
- package/dist/dotns.js +101 -0
- package/dist/environments.d.ts +104 -0
- package/dist/environments.js +23 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +8 -0
- package/dist/gh-pages-mirror.d.ts +76 -0
- package/dist/gh-pages-mirror.js +30 -0
- package/dist/incremental-stats.d.ts +69 -0
- package/dist/incremental-stats.js +10 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +146 -0
- package/dist/manifest/byte-budget.d.ts +46 -0
- package/dist/manifest/byte-budget.js +14 -0
- package/dist/manifest/config-load.d.ts +36 -0
- package/dist/manifest/config-load.js +10 -0
- package/dist/manifest/publish.d.ts +54 -0
- package/dist/manifest/publish.js +23 -0
- package/dist/manifest/schema.d.ts +29 -0
- package/dist/manifest/schema.js +10 -0
- package/dist/manifest/types.d.ts +90 -0
- package/dist/manifest/types.js +6 -0
- package/dist/manifest-embed.d.ts +18 -0
- package/dist/manifest-embed.js +9 -0
- package/dist/manifest-fetch.d.ts +32 -0
- package/dist/manifest-fetch.js +21 -0
- package/dist/manifest-roundtrip.d.ts +15 -0
- package/dist/manifest-roundtrip.js +55 -0
- package/dist/manifest.d.ts +44 -0
- package/dist/manifest.js +20 -0
- package/dist/memory-report.d.ts +95 -0
- package/dist/memory-report.js +17 -0
- package/dist/merkle.d.ts +50 -0
- package/dist/merkle.js +33 -0
- package/dist/personhood/bind-paid-alias.d.ts +43 -0
- package/dist/personhood/bind-paid-alias.js +10 -0
- package/dist/personhood/bind-personal-id.d.ts +55 -0
- package/dist/personhood/bind-personal-id.js +12 -0
- package/dist/personhood/bootstrap.d.ts +85 -0
- package/dist/personhood/bootstrap.js +245 -0
- package/dist/personhood/claim-pgas.d.ts +61 -0
- package/dist/personhood/claim-pgas.js +12 -0
- package/dist/personhood/constants.d.ts +23 -0
- package/dist/personhood/constants.js +22 -0
- package/dist/personhood/encoding.d.ts +49 -0
- package/dist/personhood/encoding.js +24 -0
- package/dist/personhood/hashing.d.ts +4 -0
- package/dist/personhood/hashing.js +8 -0
- package/dist/personhood/member-key.d.ts +12 -0
- package/dist/personhood/member-key.js +10 -0
- package/dist/personhood/people-client.d.ts +14 -0
- package/dist/personhood/people-client.js +48 -0
- package/dist/personhood/reprove.d.ts +43 -0
- package/dist/personhood/reprove.js +225 -0
- package/dist/pool.d.ts +51 -0
- package/dist/pool.js +30 -0
- package/dist/run-state.d.ts +22 -0
- package/dist/run-state.js +20 -0
- package/dist/telemetry.d.ts +56 -0
- package/dist/telemetry.js +71 -0
- package/dist/version-check.d.ts +38 -0
- package/dist/version-check.js +30 -0
- package/docs/bootstrap.md +49 -0
- package/docs/e2e-bootstrap.md +154 -0
- package/docs/telemetry.md +62 -0
- package/docs/testing.md +44 -0
- package/package.json +82 -0
- package/tools/release-retry-wrapper.mjs +74 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PEOPLE_MEMBER_IDENTIFIER_HEX,
|
|
3
|
+
PGAS_ASSET_ID,
|
|
4
|
+
PROOF_BYTES
|
|
5
|
+
} from "./chunk-T7EEVWNU.js";
|
|
6
|
+
import {
|
|
7
|
+
buildImplicationMessage,
|
|
8
|
+
buildV5GeneralExtrinsic,
|
|
9
|
+
bytesToHex,
|
|
10
|
+
encodeMembers,
|
|
11
|
+
hexToBytes,
|
|
12
|
+
readExtensionOrder,
|
|
13
|
+
toHex
|
|
14
|
+
} from "./chunk-ZYVGHDMU.js";
|
|
15
|
+
|
|
16
|
+
// src/personhood/claim-pgas.ts
|
|
17
|
+
import { Enum } from "polkadot-api";
|
|
18
|
+
var PgasClaimError = class extends Error {
|
|
19
|
+
kind;
|
|
20
|
+
dispatchError;
|
|
21
|
+
constructor(message, options = {}) {
|
|
22
|
+
super(message, options);
|
|
23
|
+
this.name = "PgasClaimError";
|
|
24
|
+
this.kind = options.kind ?? "Unknown";
|
|
25
|
+
this.dispatchError = options.dispatchError;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var SECS_PER_DAY = 86400n;
|
|
29
|
+
var IMPLICATION_EXCLUDE = /* @__PURE__ */ new Set([
|
|
30
|
+
"AuthorizeCall",
|
|
31
|
+
"AsPgas",
|
|
32
|
+
"StorageWeightReclaim"
|
|
33
|
+
]);
|
|
34
|
+
var PGAS_CONTEXT_PREFIX = new TextEncoder().encode("pop:gas:");
|
|
35
|
+
function buildGasContext(day, slotIndex) {
|
|
36
|
+
const out = new Uint8Array(32);
|
|
37
|
+
out.set(PGAS_CONTEXT_PREFIX, 0);
|
|
38
|
+
const dv = new DataView(out.buffer);
|
|
39
|
+
dv.setUint32(8, day, true);
|
|
40
|
+
dv.setUint32(12, slotIndex, true);
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function buildAsPgasClaimExtensionValue(proof, ringIndex, revision, day) {
|
|
44
|
+
return Enum("Claim", {
|
|
45
|
+
proof: bytesToHex(proof),
|
|
46
|
+
ring_index: ringIndex,
|
|
47
|
+
revision,
|
|
48
|
+
collection: Enum("People"),
|
|
49
|
+
day
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
var claimPgas = async ({
|
|
53
|
+
peopleUnsafeApi,
|
|
54
|
+
ahUnsafeApi,
|
|
55
|
+
ahClient,
|
|
56
|
+
target,
|
|
57
|
+
memberKey,
|
|
58
|
+
buildRingProof,
|
|
59
|
+
slotIndex = 0,
|
|
60
|
+
progress
|
|
61
|
+
}) => {
|
|
62
|
+
const people = peopleUnsafeApi;
|
|
63
|
+
const ah = ahUnsafeApi;
|
|
64
|
+
if (memberKey.length !== 32) {
|
|
65
|
+
throw new PgasClaimError("memberKey must be 32 bytes", { kind: "ClientError" });
|
|
66
|
+
}
|
|
67
|
+
const asset = await ah.query.Assets.Asset.getValue(PGAS_ASSET_ID, { at: "best" });
|
|
68
|
+
if (!asset) {
|
|
69
|
+
throw new PgasClaimError(
|
|
70
|
+
"PGAS asset (id 2_000_000_000) does not exist on AH",
|
|
71
|
+
{ kind: "PgasAssetNotCreated" }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const position = await people.query.Members.Members.getValue(
|
|
75
|
+
PEOPLE_MEMBER_IDENTIFIER_HEX,
|
|
76
|
+
bytesToHex(memberKey),
|
|
77
|
+
{ at: "best" }
|
|
78
|
+
);
|
|
79
|
+
if (!position) {
|
|
80
|
+
throw new PgasClaimError(
|
|
81
|
+
"member key not present on People \u2014 recognize first",
|
|
82
|
+
{ kind: "NotARecognizedPerson" }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (position.type !== "Included") {
|
|
86
|
+
throw new PgasClaimError(
|
|
87
|
+
`member position is '${position.type}', expected 'Included'`,
|
|
88
|
+
{ kind: "NotARecognizedPerson" }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const ringIndex = position.value.ring_index;
|
|
92
|
+
const allEntries = await people.query.Members.RingKeys.getEntries({ at: "best" });
|
|
93
|
+
const pages = [];
|
|
94
|
+
const identHex = PEOPLE_MEMBER_IDENTIFIER_HEX.toLowerCase();
|
|
95
|
+
for (const entry of allEntries) {
|
|
96
|
+
if (entry.keyArgs[0].toLowerCase() !== identHex) continue;
|
|
97
|
+
if (Number(entry.keyArgs[1]) !== ringIndex) continue;
|
|
98
|
+
pages.push([Number(entry.keyArgs[2]), [...entry.value]]);
|
|
99
|
+
}
|
|
100
|
+
pages.sort((a, b) => a[0] - b[0]);
|
|
101
|
+
const ringKeys = pages.flatMap(([, ks]) => ks);
|
|
102
|
+
if (ringKeys.length === 0) {
|
|
103
|
+
throw new PgasClaimError("ring has no members on People", {
|
|
104
|
+
kind: "ClientError"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const membersBytes = encodeMembers(ringKeys.map((k) => hexToBytes(k)));
|
|
108
|
+
const collectionId = await ah.constants.AliasAccounts.PeopleCollectionIdentifier();
|
|
109
|
+
const ringExp = await ah.constants.AliasAccounts.PeopleRingExponent();
|
|
110
|
+
const ringExponent = ringExp.type === "R2e9" ? 9 : ringExp.type === "R2e10" ? 10 : 14;
|
|
111
|
+
const ringRoots = await ah.query.MembersSubscriber.RingRoots.getValue(
|
|
112
|
+
collectionId,
|
|
113
|
+
ringIndex,
|
|
114
|
+
{ at: "best" }
|
|
115
|
+
);
|
|
116
|
+
if (!ringRoots || ringRoots.length === 0) {
|
|
117
|
+
throw new PgasClaimError(
|
|
118
|
+
"AH has no RingRoots for this (collection, ring_index)",
|
|
119
|
+
{ kind: "RingRootNotFound" }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const latest = ringRoots[ringRoots.length - 1];
|
|
123
|
+
const revision = latest.revision;
|
|
124
|
+
const nowRaw = await ah.query.Timestamp.Now.getValue({ at: "best" });
|
|
125
|
+
const nowSec = nowRaw > 10000000000n ? nowRaw / 1000n : nowRaw;
|
|
126
|
+
const day = Number(nowSec / SECS_PER_DAY);
|
|
127
|
+
const contextBytes = buildGasContext(day, slotIndex);
|
|
128
|
+
const innerTx = ah.tx.Pgas.claim_pgas({ slot_index: slotIndex, target });
|
|
129
|
+
const callBytes = await innerTx.getEncodedData();
|
|
130
|
+
const passEmpty = await capturePass(innerTx, void 0);
|
|
131
|
+
const msg = buildImplicationMessage(
|
|
132
|
+
callBytes,
|
|
133
|
+
passEmpty.extensions,
|
|
134
|
+
IMPLICATION_EXCLUDE
|
|
135
|
+
);
|
|
136
|
+
const { proof, alias } = await buildRingProof({
|
|
137
|
+
ringExponent,
|
|
138
|
+
members: membersBytes,
|
|
139
|
+
context: contextBytes,
|
|
140
|
+
msg
|
|
141
|
+
});
|
|
142
|
+
if (proof.length !== PROOF_BYTES) {
|
|
143
|
+
throw new PgasClaimError(
|
|
144
|
+
`ring proof must be ${PROOF_BYTES} bytes, got ${proof.length}`,
|
|
145
|
+
{ kind: "ClientError" }
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const asPgasValue = Enum("Claim", {
|
|
149
|
+
proof: bytesToHex(proof),
|
|
150
|
+
ring_index: ringIndex,
|
|
151
|
+
revision,
|
|
152
|
+
collection: Enum("People"),
|
|
153
|
+
day
|
|
154
|
+
});
|
|
155
|
+
const passProof = await capturePass(innerTx, asPgasValue);
|
|
156
|
+
const extrinsicBytes = buildV5GeneralExtrinsic(callBytes, passProof.extensions);
|
|
157
|
+
const extrinsicHex = toHex(extrinsicBytes);
|
|
158
|
+
const blockHash = await submitExtrinsic(ahClient, extrinsicHex, progress);
|
|
159
|
+
const amount = await ah.constants.Pgas.PgasClaimAmount();
|
|
160
|
+
return { blockHash, amount, alias };
|
|
161
|
+
};
|
|
162
|
+
var capturePass = async (innerTx, asPgasValue) => {
|
|
163
|
+
let captured = null;
|
|
164
|
+
const sentinel = new Error("__pgas_capture_sentinel__");
|
|
165
|
+
const signer = {
|
|
166
|
+
publicKey: new Uint8Array(32),
|
|
167
|
+
signTx: async (callData, signedExtensions, metadata) => {
|
|
168
|
+
const order = await readExtensionOrder(metadata);
|
|
169
|
+
const byIdentifier = {};
|
|
170
|
+
for (const id of order) {
|
|
171
|
+
const ext = signedExtensions[id];
|
|
172
|
+
byIdentifier[id] = {
|
|
173
|
+
value: ext.value,
|
|
174
|
+
additionalSigned: ext.additionalSigned
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
captured = { callData, extensions: { order, byIdentifier } };
|
|
178
|
+
throw sentinel;
|
|
179
|
+
},
|
|
180
|
+
signBytes: async () => new Uint8Array(64)
|
|
181
|
+
};
|
|
182
|
+
try {
|
|
183
|
+
await innerTx.sign(signer, {
|
|
184
|
+
mortality: { mortal: false },
|
|
185
|
+
customSignedExtensions: {
|
|
186
|
+
AsPgas: {
|
|
187
|
+
value: asPgasValue,
|
|
188
|
+
additionalSigned: new Uint8Array()
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err !== sentinel) throw err;
|
|
194
|
+
}
|
|
195
|
+
if (captured === null) {
|
|
196
|
+
throw new PgasClaimError("extension capture failed", { kind: "ClientError" });
|
|
197
|
+
}
|
|
198
|
+
return captured;
|
|
199
|
+
};
|
|
200
|
+
var narrowDispatchError = (dispatchError) => {
|
|
201
|
+
if (typeof dispatchError === "object" && dispatchError !== null && "type" in dispatchError) {
|
|
202
|
+
const d = dispatchError;
|
|
203
|
+
if (d.type === "Invalid" && typeof d.value === "object" && d.value !== null) {
|
|
204
|
+
const v = d.value;
|
|
205
|
+
if (v.type === "BadProof") return "BadProof";
|
|
206
|
+
if (v.type === "Custom") {
|
|
207
|
+
switch (v.value) {
|
|
208
|
+
case 230:
|
|
209
|
+
return "InvalidClaimSlot";
|
|
210
|
+
case 231:
|
|
211
|
+
return "InvalidClaimDay";
|
|
212
|
+
case 232:
|
|
213
|
+
return "AlreadyClaimedToday";
|
|
214
|
+
case 233:
|
|
215
|
+
return "PgasAssetNotCreated";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return "DispatchError";
|
|
221
|
+
};
|
|
222
|
+
var submitExtrinsic = (client, extrinsicHex, progress) => {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
let settled = false;
|
|
225
|
+
const fail = (err) => {
|
|
226
|
+
if (settled) return;
|
|
227
|
+
settled = true;
|
|
228
|
+
reject(err);
|
|
229
|
+
};
|
|
230
|
+
const succeed = (h) => {
|
|
231
|
+
if (settled) return;
|
|
232
|
+
settled = true;
|
|
233
|
+
resolve(h);
|
|
234
|
+
};
|
|
235
|
+
client.submitAndWatch(hexToBytes(extrinsicHex)).subscribe({
|
|
236
|
+
next: (event) => {
|
|
237
|
+
if (settled) return;
|
|
238
|
+
if (event.type === "broadcasted") {
|
|
239
|
+
progress?.onBroadcasted?.();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (event.type === "txBestBlocksState" && event.found) {
|
|
243
|
+
if (event.ok === false) {
|
|
244
|
+
fail(
|
|
245
|
+
new PgasClaimError("claim_pgas dispatched but failed in-block", {
|
|
246
|
+
kind: narrowDispatchError(event.dispatchError),
|
|
247
|
+
dispatchError: event.dispatchError
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
progress?.onBestBlock?.(event.block.hash);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (event.type === "finalized") {
|
|
256
|
+
if (event.ok === false) {
|
|
257
|
+
fail(
|
|
258
|
+
new PgasClaimError("claim_pgas failed at finalization", {
|
|
259
|
+
kind: narrowDispatchError(event.dispatchError),
|
|
260
|
+
dispatchError: event.dispatchError
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
succeed(event.block.hash);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
error: (err) => {
|
|
269
|
+
fail(
|
|
270
|
+
err instanceof PgasClaimError ? err : new PgasClaimError(
|
|
271
|
+
err instanceof Error ? `RPC rejected extrinsic: ${err.message}` : "RPC error during submitAndWatch",
|
|
272
|
+
{ cause: err, kind: "RpcError" }
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export {
|
|
281
|
+
PgasClaimError,
|
|
282
|
+
buildAsPgasClaimExtensionValue,
|
|
283
|
+
claimPgas
|
|
284
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VERSION
|
|
3
|
+
} from "./chunk-MFTODIIT.js";
|
|
4
|
+
|
|
5
|
+
// src/version-check.ts
|
|
6
|
+
import { execSync, execFileSync } from "child_process";
|
|
7
|
+
import { createInterface } from "readline";
|
|
8
|
+
var REGISTRY_URL = "https://registry.npmjs.org/bulletin-deploy/latest";
|
|
9
|
+
var KILL_SWITCH_URL = "https://raw.githubusercontent.com/paritytech/triangle-deploy/main/min-version.json";
|
|
10
|
+
var FETCH_TIMEOUT = 3e3;
|
|
11
|
+
function checkNodeVersion(enginesNode, currentVersion) {
|
|
12
|
+
const match = enginesNode.match(/(\d+)/);
|
|
13
|
+
if (!match) return null;
|
|
14
|
+
const required = parseInt(match[1], 10);
|
|
15
|
+
const actual = parseInt(currentVersion.replace(/^v/, "").split(".")[0], 10);
|
|
16
|
+
if (actual < required) {
|
|
17
|
+
return `bulletin-deploy requires Node.js ${enginesNode} (running ${currentVersion}).
|
|
18
|
+
Download a supported version at https://nodejs.org/`;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function compareSemver(a, b) {
|
|
23
|
+
const [coreA, preA] = a.split("-", 2);
|
|
24
|
+
const [coreB, preB] = b.split("-", 2);
|
|
25
|
+
const pa = coreA.split(".").map(Number);
|
|
26
|
+
const pb = coreB.split(".").map(Number);
|
|
27
|
+
for (let i = 0; i < 3; i++) {
|
|
28
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
29
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
30
|
+
}
|
|
31
|
+
if (preA && !preB) return -1;
|
|
32
|
+
if (!preA && preB) return 1;
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
function isPreReleaseVersion(version) {
|
|
36
|
+
return version.includes("-");
|
|
37
|
+
}
|
|
38
|
+
function preReleaseWarning(version) {
|
|
39
|
+
if (!isPreReleaseVersion(version)) return null;
|
|
40
|
+
return [
|
|
41
|
+
"",
|
|
42
|
+
`\u26A0\uFE0F Running bulletin-deploy ${version} (release candidate).`,
|
|
43
|
+
" This version is not recommended for production deploys.",
|
|
44
|
+
" For stable: npm install -g bulletin-deploy@latest",
|
|
45
|
+
""
|
|
46
|
+
].join("\n");
|
|
47
|
+
}
|
|
48
|
+
async function fetchJson(url) {
|
|
49
|
+
try {
|
|
50
|
+
const controller = new AbortController();
|
|
51
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
|
52
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
if (!res.ok) return null;
|
|
55
|
+
return await res.json();
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function fetchVersionInfo() {
|
|
61
|
+
const [registry, killSwitch] = await Promise.all([
|
|
62
|
+
fetchJson(REGISTRY_URL),
|
|
63
|
+
fetchJson(KILL_SWITCH_URL)
|
|
64
|
+
]);
|
|
65
|
+
if (!registry && !killSwitch) return null;
|
|
66
|
+
return {
|
|
67
|
+
latest: registry?.version ?? VERSION,
|
|
68
|
+
minimumFromRegistry: registry?.minimumVersion ?? null,
|
|
69
|
+
minimumFromKillSwitch: killSwitch?.minimumVersion ?? null,
|
|
70
|
+
killSwitchMessage: killSwitch?.message ?? null
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function handlePreflightVersionCheck(info) {
|
|
74
|
+
if (!info) return "ok";
|
|
75
|
+
const effectiveMinimum = [info.minimumFromRegistry, info.minimumFromKillSwitch].filter(Boolean).sort((a, b) => compareSemver(b, a))[0] ?? null;
|
|
76
|
+
if (effectiveMinimum && compareSemver(VERSION, effectiveMinimum) < 0) {
|
|
77
|
+
console.error(`
|
|
78
|
+
bulletin-deploy ${VERSION} is no longer supported (minimum: ${effectiveMinimum}).`);
|
|
79
|
+
const ks = info.killSwitchMessage;
|
|
80
|
+
if (ks) console.error(` Reason: ${ks}`);
|
|
81
|
+
console.error(` Please update: npm install -g bulletin-deploy@latest
|
|
82
|
+
`);
|
|
83
|
+
return "abort";
|
|
84
|
+
}
|
|
85
|
+
if (compareSemver(VERSION, info.latest) < 0) {
|
|
86
|
+
console.error(`
|
|
87
|
+
A newer version of bulletin-deploy is available (${VERSION} \u2192 ${info.latest}).`);
|
|
88
|
+
console.error(` Run: npm install -g bulletin-deploy@latest
|
|
89
|
+
`);
|
|
90
|
+
return "nudge";
|
|
91
|
+
}
|
|
92
|
+
return "ok";
|
|
93
|
+
}
|
|
94
|
+
function isInternalUser(cwd) {
|
|
95
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
96
|
+
if (repo?.startsWith("paritytech/")) return true;
|
|
97
|
+
const opts = { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], ...cwd ? { cwd } : {} };
|
|
98
|
+
try {
|
|
99
|
+
const remote = execSync("git remote get-url origin", opts).trim();
|
|
100
|
+
if (remote.includes("paritytech/")) return true;
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const email = execSync("git config user.email", opts).trim();
|
|
105
|
+
if (email.endsWith("@parity.io")) return true;
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
function isInteractive() {
|
|
111
|
+
return Boolean(process.stdin.isTTY && !process.env.CI);
|
|
112
|
+
}
|
|
113
|
+
async function promptYesNo(question, input) {
|
|
114
|
+
const stdin = input ?? process.stdin;
|
|
115
|
+
if (!input && !process.stdin.isTTY) return false;
|
|
116
|
+
const rl = createInterface({ input: stdin, output: process.stderr });
|
|
117
|
+
let answered = false;
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
rl.on("close", () => {
|
|
120
|
+
if (!answered) {
|
|
121
|
+
answered = true;
|
|
122
|
+
resolve(false);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
rl.question(question, (answer) => {
|
|
126
|
+
if (answered) return;
|
|
127
|
+
const a = answer.trim().toLowerCase();
|
|
128
|
+
if (a === "" || a === "y" || a === "yes") {
|
|
129
|
+
answered = true;
|
|
130
|
+
rl.close();
|
|
131
|
+
resolve(true);
|
|
132
|
+
} else if (a === "n" || a === "no") {
|
|
133
|
+
answered = true;
|
|
134
|
+
rl.close();
|
|
135
|
+
resolve(false);
|
|
136
|
+
} else {
|
|
137
|
+
rl.question(" Please answer Y or N: ", (retry) => {
|
|
138
|
+
if (answered) return;
|
|
139
|
+
const r = retry.trim().toLowerCase();
|
|
140
|
+
answered = true;
|
|
141
|
+
rl.close();
|
|
142
|
+
resolve(r === "" || r === "y" || r === "yes");
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function updateAndRetry() {
|
|
149
|
+
console.error("\n Updating bulletin-deploy...");
|
|
150
|
+
try {
|
|
151
|
+
execSync("npm install -g bulletin-deploy@latest", { stdio: "inherit" });
|
|
152
|
+
console.error(" Updated. Retrying deploy...\n");
|
|
153
|
+
execFileSync(process.argv[0], process.argv.slice(1), { stdio: "inherit" });
|
|
154
|
+
process.exit(0);
|
|
155
|
+
} catch {
|
|
156
|
+
console.error(" Update failed. Please run: npm install -g bulletin-deploy@latest");
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function classifyErrorArea(msg) {
|
|
161
|
+
if (/personhood|owned by|owner mismatch|reserved for original|domain|dotns|commit-reveal/i.test(msg)) return "area:dotns";
|
|
162
|
+
if (/chunk|storage|authorized|authorization|pool|alice/i.test(msg)) return "area:storage";
|
|
163
|
+
if (/ipfs|cid|pin|dag/i.test(msg)) return "area:ipfs";
|
|
164
|
+
if (/connect|timeout|websocket|rpc|ECONNREFUSED|ENOTFOUND/i.test(msg)) return "area:network";
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function assessVersion(currentVersion, info, internal) {
|
|
168
|
+
const effectiveMinimum = [info.minimumFromRegistry, info.minimumFromKillSwitch].filter(Boolean).sort((a, b) => compareSemver(b, a))[0] ?? null;
|
|
169
|
+
if (effectiveMinimum && compareSemver(currentVersion, effectiveMinimum) < 0) {
|
|
170
|
+
return { action: "forced_update", currentVersion, minimumVersion: effectiveMinimum, message: info.killSwitchMessage };
|
|
171
|
+
}
|
|
172
|
+
if (compareSemver(currentVersion, info.latest) < 0) {
|
|
173
|
+
return { action: "suggest_update", currentVersion, latestVersion: info.latest, internal };
|
|
174
|
+
}
|
|
175
|
+
if (internal) {
|
|
176
|
+
return { action: "bug_report", internal: true };
|
|
177
|
+
}
|
|
178
|
+
return { action: "none" };
|
|
179
|
+
}
|
|
180
|
+
async function handleFailedDeploy(error) {
|
|
181
|
+
if (process.env.BULLETIN_DEPLOY_UPDATE_CHECK === "0") return;
|
|
182
|
+
const info = await fetchVersionInfo();
|
|
183
|
+
if (!info) return;
|
|
184
|
+
let verdict = assessVersion(VERSION, info, false);
|
|
185
|
+
if (verdict.action === "suggest_update" || verdict.action === "none") {
|
|
186
|
+
const internal = isInternalUser();
|
|
187
|
+
if (internal) verdict = assessVersion(VERSION, info, true);
|
|
188
|
+
}
|
|
189
|
+
switch (verdict.action) {
|
|
190
|
+
case "forced_update":
|
|
191
|
+
console.error(`
|
|
192
|
+
bulletin-deploy ${verdict.currentVersion} is no longer supported (minimum: ${verdict.minimumVersion}).`);
|
|
193
|
+
if (verdict.message) console.error(` Reason: ${verdict.message}`);
|
|
194
|
+
console.error(` Please update: npm install -g bulletin-deploy@latest
|
|
195
|
+
`);
|
|
196
|
+
break;
|
|
197
|
+
case "suggest_update":
|
|
198
|
+
if (isInteractive()) {
|
|
199
|
+
const yes = await promptYesNo(`
|
|
200
|
+
bulletin-deploy ${verdict.currentVersion} \u2192 ${verdict.latestVersion} available. Update and retry? [Y/n] `);
|
|
201
|
+
if (yes) updateAndRetry();
|
|
202
|
+
else console.error(` Skipped. Run: npm install -g bulletin-deploy@latest
|
|
203
|
+
`);
|
|
204
|
+
} else {
|
|
205
|
+
console.error(`
|
|
206
|
+
A newer version of bulletin-deploy is available (${verdict.currentVersion} \u2192 ${verdict.latestVersion}).`);
|
|
207
|
+
console.error(` Run: npm install -g bulletin-deploy@latest
|
|
208
|
+
`);
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
case "bug_report": {
|
|
212
|
+
const { offerBugReport } = await import("./bug-report.js");
|
|
213
|
+
await offerBugReport(error);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export {
|
|
220
|
+
checkNodeVersion,
|
|
221
|
+
compareSemver,
|
|
222
|
+
isPreReleaseVersion,
|
|
223
|
+
preReleaseWarning,
|
|
224
|
+
fetchVersionInfo,
|
|
225
|
+
handlePreflightVersionCheck,
|
|
226
|
+
isInternalUser,
|
|
227
|
+
isInteractive,
|
|
228
|
+
promptYesNo,
|
|
229
|
+
classifyErrorArea,
|
|
230
|
+
assessVersion,
|
|
231
|
+
handleFailedDeploy
|
|
232
|
+
};
|