bulletin-deploy 0.7.27 → 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.
Files changed (52) hide show
  1. package/README.md +10 -0
  2. package/assets/environments.json +2 -1
  3. package/bin/bulletin-deploy +136 -7
  4. package/dist/bug-report.js +4 -4
  5. package/dist/{chunk-I7YT2IAO.js → chunk-2DFF7JON.js} +5 -2
  6. package/dist/{chunk-FW4HEUMH.js → chunk-FLSEVRJH.js} +2 -2
  7. package/dist/chunk-GZD2UFLR.js +8 -0
  8. package/dist/{chunk-AXIJLJZK.js → chunk-H7KX6PV6.js} +1 -1
  9. package/dist/{chunk-6TIWKKDS.js → chunk-ITK6QMTD.js} +4 -3
  10. package/dist/chunk-LZJMVPYW.js +156 -0
  11. package/dist/{chunk-JIKWTM5P.js → chunk-MEPT7ICD.js} +66 -8
  12. package/dist/chunk-MMAZFJDG.js +91 -0
  13. package/dist/{chunk-MGU5I7H5.js → chunk-OITUIM2E.js} +2 -1
  14. package/dist/chunk-QH3KQZT7.js +164 -0
  15. package/dist/chunk-RI3ZLNPN.js +71 -0
  16. package/dist/{chunk-TILLNN33.js → chunk-TFG4YBCU.js} +334 -29
  17. package/dist/{chunk-W4R7HNRX.js → chunk-X6YLSXNW.js} +1 -1
  18. package/dist/chunk-probe.js +3 -3
  19. package/dist/deploy.d.ts +23 -5
  20. package/dist/deploy.js +12 -10
  21. package/dist/dotns.d.ts +123 -1
  22. package/dist/dotns.js +12 -4
  23. package/dist/environments.js +1 -1
  24. package/dist/index.d.ts +5 -0
  25. package/dist/index.js +42 -8
  26. package/dist/manifest/byte-budget.d.ts +46 -0
  27. package/dist/manifest/byte-budget.js +14 -0
  28. package/dist/manifest/config-load.d.ts +36 -0
  29. package/dist/manifest/config-load.js +10 -0
  30. package/dist/manifest/publish.d.ts +54 -0
  31. package/dist/manifest/publish.js +23 -0
  32. package/dist/manifest/schema.d.ts +29 -0
  33. package/dist/manifest/schema.js +10 -0
  34. package/dist/manifest/types.d.ts +90 -0
  35. package/dist/manifest/types.js +6 -0
  36. package/dist/memory-report.js +2 -2
  37. package/dist/merkle.js +8 -8
  38. package/dist/personhood/bind-paid-alias.js +2 -2
  39. package/dist/personhood/bind-personal-id.js +2 -2
  40. package/dist/personhood/bootstrap.js +12 -12
  41. package/dist/personhood/claim-pgas.js +2 -2
  42. package/dist/personhood/member-key.js +2 -2
  43. package/dist/personhood/people-client.js +4 -4
  44. package/dist/personhood/reprove.js +5 -5
  45. package/dist/run-state.js +1 -1
  46. package/dist/telemetry.js +2 -2
  47. package/dist/version-check.js +3 -3
  48. package/package.json +4 -3
  49. package/dist/{chunk-74ETPOKH.js → chunk-2VAUMZB2.js} +5 -5
  50. package/dist/{chunk-QHOZEY5X.js → chunk-5VZQ2KSU.js} +7 -7
  51. package/dist/{chunk-EJ5TNGAY.js → chunk-BMAEWZYV.js} +3 -3
  52. package/dist/{chunk-A5IQ5MKO.js → chunk-IDYGYIMH.js} +3 -3
package/README.md CHANGED
@@ -215,6 +215,16 @@ await deploy("./dist", "my-app00.dot", { jsMerkle: true });
215
215
  | `IPFS CLI not installed` | Install Kubo or switch to `--js-merkle`. |
216
216
  | Previous deploy did not exit cleanly / OOM hint | Retry with a larger Node heap, for example `NODE_OPTIONS='--max-old-space-size=8192'`. |
217
217
 
218
+ ## Contributing
219
+
220
+ New to the codebase? Start with **[ONBOARDING.md](ONBOARDING.md)** — it covers install, the mental model, the first task, and the working conventions for this repo (worktree-per-branch, squash-merge policy, never-delete-tests, where the per-directory rules live).
221
+
222
+ The team uses Claude Code as a primary tool. The repo ships team-shared Claude configuration: `.claude/skills/` (project-specific commands like `/e2e-local`, `/dotns-diagnose`, `/sentry-query`), `.claude/settings.json` (Bash allowlist), and per-directory `CLAUDE.md` files in `src/`, `sentry/`, `test/`, `tools/` that load on demand. Running `claude` inside this repo picks all of that up automatically.
223
+
224
+ Already have Claude Code installed? Clone, `npm install`, open `ONBOARDING.md`. New to Claude Code itself? The same doc covers install.
225
+
226
+ For maintainers and engineers familiar with the release flow, the canonical procedures live in the root [`CLAUDE.md`](CLAUDE.md): change workflow, dual-stage RC → stable release, post-release Sentry monitoring, and the squash-merge convention that satisfies branch protection without per-commit GPG signing.
227
+
218
228
  ## More Docs
219
229
 
220
230
  - [Bootstrap and operator setup](docs/bootstrap.md)
@@ -80,7 +80,8 @@
80
80
  "POP_RULES": "0x2002C1c15b88632Ad01c7770f6EbE1Ca05c8472E",
81
81
  "STORE_FACTORY": "0x0DE5De70d61cc6b44B45d6595afDe8dB9b55bc31",
82
82
  "LABEL_STORE_BEACON": "0xD033F7Ada687E8BC776928AB239505F9f0479Ce7",
83
- "USER_STORE_BEACON": "0x7eD9b7D137Fa535965048F93b3B0248fEd2fcd32"
83
+ "USER_STORE_BEACON": "0x7eD9b7D137Fa535965048F93b3B0248fEd2fcd32",
84
+ "PUBLISHER": "0x1307fc02d308f879a16b1ae3a49b4927aed53649"
84
85
  }
85
86
  },
86
87
  {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { deploy, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, NonRetryableError, EXIT_CODE_NO_RETRY, isConnectionError } from "../dist/deploy.js";
3
+ import { deploy, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, NonRetryableError, EXIT_CODE_NO_RETRY, isConnectionError, unpublish } from "../dist/deploy.js";
4
4
  import { VERSION, setDeployAttribute, captureWarning, closeTelemetry, setRunStateActive, markRelaunchOomHintShown } from "../dist/telemetry.js";
5
5
  import { handleFailedDeploy, handlePreflightVersionCheck, fetchVersionInfo, preReleaseWarning, checkNodeVersion } from "../dist/version-check.js";
6
6
  import { setDeployContext, installLogCapture, buildCliFlagsSummary } from "../dist/bug-report.js";
@@ -36,19 +36,26 @@ for (let i = 0; i < args.length; i++) {
36
36
  else if (args[i] === "--js-merkle") { flags.jsMerkle = true; }
37
37
  else if (args[i] === "--input-car") { flags.inputCar = args[++i]; }
38
38
  else if (args[i] === "--tag") { flags.tag = args[++i]; }
39
- else if (args[i] === "--name") { flags.name = args[++i]; }
40
- else if (args[i] === "--description") { flags.description = args[++i]; }
39
+ else if (args[i] === "--config") { flags.config = args[++i]; }
41
40
  else if (args[i] === "--gh-pages-mirror") { flags.ghPagesMirror = true; }
42
41
  else if (args[i] === "--allow-large-deploy") { flags.allowLargeDeploy = true; }
43
42
  else if (args[i] === "--reproducible") { flags.reproducibleSource = "commit"; }
44
43
  else if (args[i].startsWith("--reproducible=")) { flags.reproducibleSource = args[i].slice("--reproducible=".length); }
45
44
  else if (args[i] === "--dump-car") { flags.dumpCar = true; }
46
45
  else if (args[i].startsWith("--dump-car=")) { flags.dumpCar = args[i].slice("--dump-car=".length); }
46
+ else if (args[i] === "--publish") { flags.publish = true; }
47
+ else if (args[i] === "--unpublish") { flags.unpublish = true; }
48
+ else if (args[i] === "--fail-on-publish-error") { flags.failOnPublishError = true; }
47
49
  else if (args[i] === "--version" || args[i] === "-V") { flags.version = true; }
48
50
  else if (args[i] === "--help" || args[i] === "-h") { flags.help = true; }
49
51
  else { positional.push(args[i]); }
50
52
  }
51
53
 
54
+ if (flags.publish && flags.unpublish) {
55
+ console.error("Error: --publish and --unpublish are mutually exclusive.");
56
+ process.exit(1);
57
+ }
58
+
52
59
  if (flags.version) {
53
60
  console.log(`bulletin-deploy v${VERSION}`);
54
61
  process.exit(0);
@@ -72,7 +79,74 @@ if (flags.listEnvironments) {
72
79
  }
73
80
  }
74
81
 
82
+ if (flags.unpublish) {
83
+ if (!flags.mnemonic && !process.env.MNEMONIC) {
84
+ console.error("Error: --unpublish requires --mnemonic (or MNEMONIC env var).");
85
+ process.exit(1);
86
+ }
87
+ const [domain] = positional;
88
+ if (!domain) {
89
+ console.error("Error: --unpublish requires a domain (e.g. my-app.dot)");
90
+ process.exit(1);
91
+ }
92
+ try {
93
+ const result = await unpublish(domain, {
94
+ mnemonic: flags.mnemonic,
95
+ derivationPath: flags.derivationPath,
96
+ rpc: flags.rpc,
97
+ env: flags.env,
98
+ });
99
+ console.log(`Domain: ${result.domainName}`);
100
+ console.log(`Status: ${result.status}`);
101
+ process.exit(0);
102
+ } catch (e) {
103
+ console.error(`Unpublish failed:`, e?.message ?? e);
104
+ process.exit(1);
105
+ }
106
+ }
75
107
 
108
+ // `product` subcommand. Only `validate` is wired today. `publish` and `resolve` arrive in later phases.
109
+ if (positional[0] === "product") {
110
+ const verb = positional[1];
111
+ if (verb === "validate") {
112
+ try {
113
+ const { loadProductConfig, pessimisticSizePreflight, getTextRecordBudgetBytes } =
114
+ await import("../dist/index.js");
115
+ const explicitPath = positional[2];
116
+ const { config, sourcePath } = explicitPath
117
+ ? await loadProductConfig({ path: explicitPath })
118
+ : await loadProductConfig();
119
+ console.log(`✓ Loaded ${sourcePath}`);
120
+ console.log(`✓ Schema ${config.domain} (${config.executables.length} executable${config.executables.length === 1 ? "" : "s"})`);
121
+ const budget = getTextRecordBudgetBytes();
122
+ const report = pessimisticSizePreflight(config, budget);
123
+ for (const check of report.checks) {
124
+ const tag = check.ok ? "✓" : "✗";
125
+ console.log(`${tag} ${check.key.padEnd(40)} ${check.bytes} B / ${check.budget} B`);
126
+ }
127
+ if (!report.ok) {
128
+ console.error("Pessimistic size preflight failed — at least one manifest exceeds the dotNS text-record budget.");
129
+ process.exit(EXIT_CODE_NO_RETRY);
130
+ }
131
+ console.log("Validate complete.");
132
+ process.exit(0);
133
+ } catch (err) {
134
+ console.error(`Error: ${err?.message ?? err}`);
135
+ process.exit(err instanceof NonRetryableError ? EXIT_CODE_NO_RETRY : 1);
136
+ }
137
+ }
138
+ if (verb === "publish" || verb === "resolve") {
139
+ console.error(`'product ${verb}' is not implemented yet (planned for a later phase).`);
140
+ process.exit(2);
141
+ }
142
+ console.error(`Unknown product subcommand: ${verb ?? "(none)"}. Try: bulletin-deploy product validate [<config-path>]`);
143
+ process.exit(2);
144
+ }
145
+
146
+ if (flags.publish && !flags.mnemonic && !process.env.MNEMONIC) {
147
+ console.error("Error: --publish requires --mnemonic (or MNEMONIC env var).");
148
+ process.exit(1);
149
+ }
76
150
 
77
151
  if (flags.help || positional.length === 0) {
78
152
  console.log(`bulletin-deploy v${VERSION}
@@ -97,10 +171,23 @@ Options:
97
171
  --dump-car[=<path>] Save the pre-upload CAR file to disk. Default path: <buildDir>.bulletin.car.
98
172
  Override path with =<path>. Also settable via BULLETIN_DEPLOY_DUMP_CAR env var.
99
173
  --tag "..." Label deploy in telemetry (or set DEPLOY_TAG env var); see Telemetry in README
100
- --name "..." Optional. Sets the "name" text record on the domain.
101
- --description "..." Optional. Sets the "description" text record (≤100 chars recommended).
174
+ --config <path> Explicit path to a bulletin-deploy.config.ts (default: walk up from
175
+ <build-dir> looking for bulletin-deploy.config.{ts,js,mjs}). When a
176
+ config is found, deploy ALSO writes the manifest + executable text
177
+ records on <domain> and its app/widget/worker subnames.
102
178
  --gh-pages-mirror After deploy, push the CAR to the current repo's gh-pages branch
103
179
  at bulletin/<domain>.dot.car (opt-in; also set GH_PAGES_MIRROR=1)
180
+ --publish After deploy, list <domain> in the on-chain Publisher
181
+ registry (Publisher.publish). Only takes effect on envs
182
+ with a deployed Publisher (currently: paseo-next-v2).
183
+ Requires --mnemonic (the signer must own the label).
184
+ --unpublish Standalone mode: removes <domain> from the Publisher
185
+ registry (Publisher.unpublish). Skips the deploy.
186
+ Usage: bulletin-deploy --unpublish <domain.dot>
187
+ Requires --mnemonic. Mutually exclusive with --publish.
188
+ --fail-on-publish-error
189
+ Exit non-zero if --publish fails after a successful deploy.
190
+ Default: non-fatal (warning logged, exit 0).
104
191
  --version Show version
105
192
  --help Show this help`);
106
193
  process.exit(0);
@@ -269,11 +356,11 @@ try {
269
356
  inputCar: flags.inputCar,
270
357
  tag: flags.tag,
271
358
  ghPagesMirror: flags.ghPagesMirror,
272
- name: flags.name,
273
- description: flags.description,
274
359
  allowLargeDeploy: flags.allowLargeDeploy,
275
360
  reproducibleSource: flags.reproducibleSource,
276
361
  dumpCar: flags.dumpCar,
362
+ publish: flags.publish,
363
+ failOnPublishError: flags.failOnPublishError,
277
364
  });
278
365
 
279
366
  const output = process.env.GITHUB_OUTPUT;
@@ -285,6 +372,48 @@ try {
285
372
  console.log(`CID: ${result.cid}`);
286
373
  console.log(`Domain: ${result.domainName}`);
287
374
 
375
+ // Opt-in manifest publish on top of the legacy contenthash deploy.
376
+ //
377
+ // Walks up from <build-dir> (or honours --config) looking for
378
+ // bulletin-deploy.config.{ts,js,mjs}. When found, writes the root + per-
379
+ // executable text records, otherwise the legacy flow returns unchanged.
380
+ {
381
+ const { tryLoadProductConfig, publishManifest } = await import("../dist/index.js");
382
+ const path = await import("node:path");
383
+ const buildDirAbs = path.resolve(buildDir);
384
+ const loaded = await tryLoadProductConfig(
385
+ flags.config ? { path: flags.config } : { cwd: buildDirAbs, walkUp: true },
386
+ );
387
+ if (loaded) {
388
+ try {
389
+ await publishManifest({
390
+ loaded,
391
+ domain,
392
+ buildDirCid: { absPath: buildDirAbs, cid: result.cid },
393
+ env: flags.env,
394
+ rpc: flags.rpc,
395
+ mnemonic: flags.mnemonic,
396
+ derivationPath: flags.derivationPath,
397
+ });
398
+ } catch (err) {
399
+ console.error(`Manifest publish failed: ${err?.message ?? err}`);
400
+ process.exit(err instanceof NonRetryableError ? EXIT_CODE_NO_RETRY : 1);
401
+ }
402
+ } else {
403
+ const where = flags.config
404
+ ? `--config ${flags.config}`
405
+ : `bulletin-deploy.config.{ts,js,mjs} via walking up from ${buildDirAbs}`;
406
+ console.log("");
407
+ console.log(`⚠ No bulletin-deploy.config.ts found (${where}).`);
408
+ console.log(` ${domain} was published as legacy contenthash only.`);
409
+ console.log(" Add a bulletin-deploy.config.ts to enable the product manifest:");
410
+ console.log(" • product icon, displayName, description on the base name");
411
+ console.log(" • per-modality subnames (app.<id>.dot, widget.<id>.dot, worker.<id>.dot)");
412
+ console.log(" • each modality’s archive CID on its subname contenthash");
413
+ console.log(" See: https://github.com/paritytech/triangle-js-sdks (Product Manifest RFC)");
414
+ }
415
+ }
416
+
288
417
  if (!flags.help && !flags.version) {
289
418
  try { writeRunState({ status: "succeeded", endedAt: Date.now() }); } catch {}
290
419
  }
@@ -9,10 +9,10 @@ import {
9
9
  offerBugReport,
10
10
  scrubSecrets,
11
11
  setDeployContext
12
- } from "./chunk-I7YT2IAO.js";
13
- import "./chunk-AXIJLJZK.js";
14
- import "./chunk-FW4HEUMH.js";
15
- import "./chunk-6TIWKKDS.js";
12
+ } from "./chunk-2DFF7JON.js";
13
+ import "./chunk-H7KX6PV6.js";
14
+ import "./chunk-FLSEVRJH.js";
15
+ import "./chunk-ITK6QMTD.js";
16
16
  export {
17
17
  buildCliFlagsSummary,
18
18
  buildLabels,
@@ -2,11 +2,11 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-AXIJLJZK.js";
5
+ } from "./chunk-H7KX6PV6.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId
9
- } from "./chunk-FW4HEUMH.js";
9
+ } from "./chunk-FLSEVRJH.js";
10
10
 
11
11
  // src/bug-report.ts
12
12
  import { execSync, execFileSync } from "child_process";
@@ -76,6 +76,9 @@ function buildCliFlagsSummary(flags) {
76
76
  const parts = [];
77
77
  if (flags.jsMerkle) parts.push("--js-merkle");
78
78
  if (flags.ghPagesMirror) parts.push("--gh-pages-mirror");
79
+ if (flags.publish) parts.push("--publish");
80
+ if (flags.unpublish) parts.push("--unpublish");
81
+ if (flags.failOnPublishError) parts.push("--fail-on-publish-error");
79
82
  if (flags.poolSize != null) parts.push(`--pool-size ${String(flags.poolSize)}`);
80
83
  if (typeof flags.tag === "string" && flags.tag) parts.push(`--tag ${flags.tag}`);
81
84
  if (flags.mnemonic) parts.push("--mnemonic <set>");
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-6TIWKKDS.js";
4
+ } from "./chunk-ITK6QMTD.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -299,7 +299,7 @@ function computeDeployOutcome(errorCategory, isSad, sadReason) {
299
299
  var ERROR_KIND_RULES = [
300
300
  [/Contract reverted|Contract execution would revert|revert(?:ed|ing)?\s*\(flags=[0-9]+\)/i, "contract-revert"],
301
301
  [/timed out after \d+s waiting for block|Transaction not included after \d+s|Transaction did not settle within/i, "chain-timeout"],
302
- [/\bstale\b.*nonce|nonce.*\bstale\b|"type"\s*:\s*"Future"|Invalid::Future|tx rejected by pool/i, "nonce-stale"],
302
+ [/\bstale\b.*nonce|nonce.*\bstale\b|"type"\s*:\s*"(?:Future|Stale)"|Invalid::Future|tx rejected by pool/i, "nonce-stale"],
303
303
  [/heartbeat timeout|WS halt|Unable to connect|ChainHead disjointed|websocket.*closed|socket closed|disconnect/i, "connection"],
304
304
  [/requires ProofOfPersonhoodFull,\s*but this signer is NoStatus/i, "naming.pop_required"],
305
305
  [/Domain\s+\S+\.dot\s+is already owned by\s+0x[a-fA-F0-9]+/i, "naming.already_owned"],
@@ -0,0 +1,8 @@
1
+ // src/manifest/types.ts
2
+ function defineConfig(config) {
3
+ return config;
4
+ }
5
+
6
+ export {
7
+ defineConfig
8
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-FW4HEUMH.js";
3
+ } from "./chunk-FLSEVRJH.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
@@ -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.27",
9
+ version: "0.7.28-rc.0",
10
10
  private: false,
11
11
  repository: {
12
12
  type: "git",
@@ -45,11 +45,11 @@ var package_default = {
45
45
  "tools/release-retry-wrapper.mjs"
46
46
  ],
47
47
  scripts: {
48
- 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 src/environments.ts src/errors.ts src/manifest.ts src/chunk-probe.ts src/manifest-embed.ts src/manifest-fetch.ts src/manifest-roundtrip.ts src/incremental-stats.ts src/chunker.ts src/personhood/encoding.ts src/personhood/hashing.ts src/personhood/constants.ts src/personhood/member-key.ts src/personhood/people-client.ts src/personhood/reprove.ts src/personhood/bind-personal-id.ts src/personhood/claim-pgas.ts src/personhood/bind-paid-alias.ts src/personhood/bootstrap.ts --format esm --dts --clean --target node22",
48
+ 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 src/environments.ts src/errors.ts src/manifest.ts src/chunk-probe.ts src/manifest-embed.ts src/manifest-fetch.ts src/manifest-roundtrip.ts src/incremental-stats.ts src/chunker.ts src/personhood/encoding.ts src/personhood/hashing.ts src/personhood/constants.ts src/personhood/member-key.ts src/personhood/people-client.ts src/personhood/reprove.ts src/personhood/bind-personal-id.ts src/personhood/claim-pgas.ts src/personhood/bind-paid-alias.ts src/personhood/bootstrap.ts src/manifest/types.ts src/manifest/schema.ts src/manifest/byte-budget.ts src/manifest/config-load.ts src/manifest/publish.ts --format esm --dts --clean --target node22",
49
49
  "refresh-environments": "node scripts/refresh-environments.mjs",
50
50
  "check:watched-dependencies": "node tools/check-watched-dependencies.mjs",
51
51
  prepare: "npm run build",
52
- test: "npm run build && node --test test/test.js test/cli-help.test.js test/helpers/e2e-helpers.test.js test/environments.test.js test/refresh-environments.test.js test/watched-dependencies.test.js test/chunk-sharing-report.test.js",
52
+ test: "npm run build && node --test test/test.js test/cli-help.test.js test/helpers/e2e-helpers.test.js test/environments.test.js test/refresh-environments.test.js test/watched-dependencies.test.js test/chunk-sharing-report.test.js test/product-manifest.test.js",
53
53
  "test:e2e": "npm run build && node --test test/e2e.test.js",
54
54
  "test:e2e:smoke": "bash scripts/e2e-pass.sh smoke",
55
55
  "test:e2e:pr": "bash scripts/e2e-pass.sh pr",
@@ -69,6 +69,7 @@ var package_default = {
69
69
  "@sentry/node": "^9.14.0",
70
70
  "ipfs-unixfs": "^11.2.0",
71
71
  "ipfs-unixfs-importer": "^16.1.4",
72
+ jiti: "^2.4.2",
72
73
  multiformats: "^13.4.1",
73
74
  "polkadot-api": "^2.1.3",
74
75
  verifiablejs: "^1.2.0",
@@ -0,0 +1,156 @@
1
+ // src/manifest/schema.ts
2
+ var ICON_FORMATS = ["jpeg", "png"];
3
+ var KIND_APP = "app";
4
+ var KIND_WIDGET = "widget";
5
+ var KIND_WORKER = "worker";
6
+ var EXECUTABLE_KINDS = [KIND_APP, KIND_WIDGET, KIND_WORKER];
7
+ var LABEL = String.raw`(?!-)[a-z0-9-]{1,63}(?<!-)`;
8
+ var DOMAIN_RE = new RegExp(`^${LABEL}(\\.${LABEL})*\\.dot$`, "i");
9
+ function isPlainObject(value) {
10
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11
+ }
12
+ function isNonEmptyString(value) {
13
+ return typeof value === "string" && value.length > 0;
14
+ }
15
+ function isAppVersion(value) {
16
+ if (!Array.isArray(value)) return false;
17
+ if (value.length !== 3 && value.length !== 4) return false;
18
+ if (!value.slice(0, 3).every((n) => typeof n === "number" && Number.isFinite(n) && n >= 0)) {
19
+ return false;
20
+ }
21
+ if (value.length === 4 && typeof value[3] !== "string") return false;
22
+ return true;
23
+ }
24
+ function validateWidgetFields(input, p) {
25
+ const errors = [];
26
+ if ("description" in input && input.description !== void 0 && typeof input.description !== "string") {
27
+ errors.push(`${p}description must be a string when present`);
28
+ }
29
+ if (!isPlainObject(input.dimensions)) {
30
+ errors.push(`${p}dimensions must be an object`);
31
+ return errors;
32
+ }
33
+ const dims = input.dimensions;
34
+ if (!Array.isArray(dims.height) || dims.height.length === 0 || !dims.height.every((h) => typeof h === "number" && Number.isInteger(h) && h > 0)) {
35
+ errors.push(`${p}dimensions.height must be a non-empty array of positive integers`);
36
+ }
37
+ if ("width" in dims && dims.width !== void 0 && !(typeof dims.width === "number" && Number.isInteger(dims.width) && dims.width > 0)) {
38
+ errors.push(`${p}dimensions.width must be a positive integer when present`);
39
+ }
40
+ return errors;
41
+ }
42
+ function validateWorkerFields(input, p) {
43
+ const errors = [];
44
+ if (!isNonEmptyString(input.entrypoint)) {
45
+ errors.push(`${p}entrypoint must be a non-empty string`);
46
+ } else if (input.entrypoint.startsWith("/") || input.entrypoint.split("/").includes("..")) {
47
+ errors.push(`${p}entrypoint must be a relative path with no '..' segments`);
48
+ }
49
+ if (!isPlainObject(input.includes)) {
50
+ errors.push(`${p}includes must be an object`);
51
+ return errors;
52
+ }
53
+ const inc = input.includes;
54
+ if (typeof inc.chat !== "boolean") errors.push(`${p}includes.chat must be a boolean`);
55
+ if (typeof inc.pocket !== "boolean") errors.push(`${p}includes.pocket must be a boolean`);
56
+ if (inc.chat === false && inc.pocket === false) {
57
+ errors.push(`${p}includes must have at least one of chat / pocket = true`);
58
+ }
59
+ return errors;
60
+ }
61
+ function validateRootManifest(input) {
62
+ const errors = [];
63
+ if (!isPlainObject(input)) {
64
+ return { ok: false, errors: ["root manifest must be an object"] };
65
+ }
66
+ if (input.$v !== 1) errors.push(`root manifest $v must be 1 (got ${JSON.stringify(input.$v)})`);
67
+ if (!isNonEmptyString(input.displayName)) errors.push("root manifest displayName must be a non-empty string");
68
+ if (typeof input.description !== "string") errors.push("root manifest description must be a string");
69
+ if (!isPlainObject(input.icon)) {
70
+ errors.push("root manifest icon must be an object");
71
+ } else {
72
+ if (!isNonEmptyString(input.icon.cid)) errors.push("root manifest icon.cid must be a non-empty string");
73
+ if (!ICON_FORMATS.includes(input.icon.format)) {
74
+ errors.push(`root manifest icon.format must be one of ${ICON_FORMATS.join(", ")} (got ${JSON.stringify(input.icon.format)})`);
75
+ }
76
+ }
77
+ return errors.length === 0 ? { ok: true, value: input } : { ok: false, errors };
78
+ }
79
+ function validateExecutableManifest(input) {
80
+ const errors = [];
81
+ if (!isPlainObject(input)) {
82
+ return { ok: false, errors: ["executable manifest must be an object"] };
83
+ }
84
+ if (input.$v !== 1) errors.push(`executable manifest $v must be 1 (got ${JSON.stringify(input.$v)})`);
85
+ if (!isAppVersion(input.appVersion)) {
86
+ errors.push("executable manifest appVersion must be [major, minor, patch] or [major, minor, patch, build]");
87
+ }
88
+ const kind = input.kind;
89
+ const p = "executable manifest ";
90
+ if (kind === KIND_APP) {
91
+ } else if (kind === KIND_WIDGET) {
92
+ errors.push(...validateWidgetFields(input, p));
93
+ } else if (kind === KIND_WORKER) {
94
+ errors.push(...validateWorkerFields(input, p));
95
+ } else {
96
+ errors.push(`${p}kind must be one of ${EXECUTABLE_KINDS.join(", ")} (got ${JSON.stringify(kind)})`);
97
+ }
98
+ return errors.length === 0 ? { ok: true, value: input } : { ok: false, errors };
99
+ }
100
+ function validateProductConfig(input) {
101
+ const errors = [];
102
+ if (!isPlainObject(input)) {
103
+ return { ok: false, errors: ["product config must be an object (did you forget `export default`?)"] };
104
+ }
105
+ if (!isNonEmptyString(input.domain) || !DOMAIN_RE.test(input.domain)) {
106
+ errors.push("product config domain must be a non-empty dotNS name ending in .dot");
107
+ }
108
+ if (!isNonEmptyString(input.displayName)) errors.push("product config displayName must be a non-empty string");
109
+ if (typeof input.description !== "string") errors.push("product config description must be a string");
110
+ if (!isPlainObject(input.icon)) {
111
+ errors.push("product config icon must be an object");
112
+ } else {
113
+ if (!isNonEmptyString(input.icon.path)) errors.push("product config icon.path must be a non-empty string");
114
+ if (!ICON_FORMATS.includes(input.icon.format)) {
115
+ errors.push(`product config icon.format must be one of ${ICON_FORMATS.join(", ")}`);
116
+ }
117
+ }
118
+ if (!Array.isArray(input.executables) || input.executables.length === 0) {
119
+ errors.push("product config executables must be a non-empty array");
120
+ } else {
121
+ const seenKinds = /* @__PURE__ */ new Set();
122
+ input.executables.forEach((exec, index) => {
123
+ errors.push(...validateExecutableConfig(exec, index));
124
+ if (isPlainObject(exec) && typeof exec.kind === "string") {
125
+ if (seenKinds.has(exec.kind)) errors.push(`executables[${index}]: duplicate kind '${exec.kind}'`);
126
+ seenKinds.add(exec.kind);
127
+ }
128
+ });
129
+ }
130
+ return errors.length === 0 ? { ok: true, value: input } : { ok: false, errors };
131
+ }
132
+ function validateExecutableConfig(input, index) {
133
+ const p = `executables[${index}].`;
134
+ if (!isPlainObject(input)) return [`executables[${index}] must be an object`];
135
+ const errors = [];
136
+ if (!isNonEmptyString(input.path)) errors.push(`${p}path must be a non-empty string`);
137
+ if (!isAppVersion(input.appVersion)) {
138
+ errors.push(`${p}appVersion must be [major, minor, patch] or [major, minor, patch, build]`);
139
+ }
140
+ const kind = input.kind;
141
+ if (kind === KIND_APP) {
142
+ } else if (kind === KIND_WIDGET) {
143
+ errors.push(...validateWidgetFields(input, p));
144
+ } else if (kind === KIND_WORKER) {
145
+ errors.push(...validateWorkerFields(input, p));
146
+ } else {
147
+ errors.push(`${p}kind must be one of ${EXECUTABLE_KINDS.join(", ")} (got ${JSON.stringify(kind)})`);
148
+ }
149
+ return errors;
150
+ }
151
+
152
+ export {
153
+ validateRootManifest,
154
+ validateExecutableManifest,
155
+ validateProductConfig
156
+ };
@@ -20,21 +20,22 @@ import {
20
20
  } from "./chunk-S7EM5VMW.js";
21
21
  import {
22
22
  setDeployContext
23
- } from "./chunk-I7YT2IAO.js";
23
+ } from "./chunk-2DFF7JON.js";
24
24
  import {
25
25
  probeChunks
26
- } from "./chunk-W4R7HNRX.js";
26
+ } from "./chunk-X6YLSXNW.js";
27
27
  import {
28
28
  packSection
29
29
  } from "./chunk-C2TS5MER.js";
30
30
  import {
31
31
  DotNS,
32
+ PublisherNotSupportedError,
32
33
  TX_TIMEOUT_MS,
33
34
  fetchNonce,
34
35
  parseDomainName,
35
36
  popStatusName,
36
37
  verifyNonceAdvanced
37
- } from "./chunk-TILLNN33.js";
38
+ } from "./chunk-TFG4YBCU.js";
38
39
  import {
39
40
  derivePoolAccounts,
40
41
  detectTestnet,
@@ -56,13 +57,13 @@ import {
56
57
  truncateAddress,
57
58
  withDeploySpan,
58
59
  withSpan
59
- } from "./chunk-FW4HEUMH.js";
60
+ } from "./chunk-FLSEVRJH.js";
60
61
  import {
61
62
  DEFAULT_ENV_ID,
62
63
  getPopSelfServeConfig,
63
64
  loadEnvironments,
64
65
  resolveEndpoints
65
- } from "./chunk-MGU5I7H5.js";
66
+ } from "./chunk-OITUIM2E.js";
66
67
  import {
67
68
  NonRetryableError
68
69
  } from "./chunk-ZOC4GITL.js";
@@ -1482,6 +1483,62 @@ function assertSubdomainOwnerMatchesSigner(result, signerEvmAddress, sublabel, p
1482
1483
  );
1483
1484
  }
1484
1485
  }
1486
+ async function publish(dotns, parsed, failOnError) {
1487
+ console.log("\n" + "=".repeat(60));
1488
+ console.log("Publish");
1489
+ console.log("=".repeat(60));
1490
+ if (parsed.isSubdomain) {
1491
+ console.log(` Subdomains are not supported by the Publisher registry \u2014 skipping.`);
1492
+ return;
1493
+ }
1494
+ try {
1495
+ const result = await dotns.publishLabel(parsed.label);
1496
+ setDeployAttribute("deploy.publish.status", result.status);
1497
+ if (result.txHash) setDeployAttribute("deploy.publish.tx", result.txHash);
1498
+ console.log(` Status: ${result.status}`);
1499
+ } catch (e) {
1500
+ if (e instanceof PublisherNotSupportedError) {
1501
+ console.log(` Skipped: ${e.message}`);
1502
+ return;
1503
+ }
1504
+ setDeployAttribute("deploy.publish.status", "failed");
1505
+ if (failOnError) throw e;
1506
+ const msg = e?.message ?? String(e);
1507
+ console.error(` Warning: publish failed (non-fatal): ${msg}`);
1508
+ captureWarning("publish failed", { error: msg.slice(0, 200) });
1509
+ }
1510
+ }
1511
+ async function unpublish(domainName, options = {}) {
1512
+ const envId = options.env ?? DEFAULT_ENV_ID;
1513
+ const { doc } = await loadEnvironments();
1514
+ const resolved = resolveEndpoints(doc, envId);
1515
+ const popSelfServe = getPopSelfServeConfig(doc, envId);
1516
+ const parsed = parseDomainName(domainName);
1517
+ if (parsed.isSubdomain) {
1518
+ throw new Error(`Subdomains are not supported by the Publisher registry. To unpublish ${parsed.parentLabel}.dot (which controls ${domainName}), pass that label directly.`);
1519
+ }
1520
+ const label = parsed.label;
1521
+ const dotns = new DotNS();
1522
+ try {
1523
+ await dotns.connect(resolveDotnsConnectOptions(
1524
+ { mnemonic: options.mnemonic, derivationPath: options.derivationPath },
1525
+ resolved.assetHub,
1526
+ resolved.autoAccountMapping,
1527
+ resolved.contracts,
1528
+ resolved.nativeToEthRatio,
1529
+ envId,
1530
+ popSelfServe,
1531
+ resolved.registerStorageDeposit
1532
+ ));
1533
+ const result = await dotns.unpublishLabel(label);
1534
+ return { domainName: `${label}.dot`, status: result.status, txHash: result.txHash };
1535
+ } finally {
1536
+ try {
1537
+ dotns.disconnect();
1538
+ } catch {
1539
+ }
1540
+ }
1541
+ }
1485
1542
  async function deploy(content, domainName = null, options = {}) {
1486
1543
  const envId = options.env ?? DEFAULT_ENV_ID;
1487
1544
  let envBulletin = [DEFAULT_BULLETIN_RPC];
@@ -1824,9 +1881,9 @@ async function deploy(content, domainName = null, options = {}) {
1824
1881
  }
1825
1882
  const contenthashHex = `0x${encodeContenthash(cid)}`;
1826
1883
  await dotns.setContenthash(name, contenthashHex);
1827
- const textRecordTarget = parsed?.isSubdomain && parsed.sublabel && parsed.parentLabel ? `${parsed.sublabel}.${parsed.parentLabel}` : name;
1828
- if (options.name !== void 0) await dotns.setTextRecord(textRecordTarget, "name", options.name);
1829
- if (options.description !== void 0) await dotns.setTextRecord(textRecordTarget, "description", options.description);
1884
+ if (options.publish && parsed) {
1885
+ await publish(dotns, parsed, options.failOnPublishError);
1886
+ }
1830
1887
  dotns.disconnect();
1831
1888
  });
1832
1889
  if (options.ghPagesMirror) {
@@ -2306,5 +2363,6 @@ export {
2306
2363
  resolveDotnsConnectOptions,
2307
2364
  estimateUploadBytes,
2308
2365
  assertSubdomainOwnerMatchesSigner,
2366
+ unpublish,
2309
2367
  deploy
2310
2368
  };