bulletin-deploy 0.7.5 → 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-KQ75CSJJ.js → chunk-A2J6R5PD.js} +8 -8
- package/dist/{chunk-XOKNNK6E.js → chunk-EECNTEAE.js} +1 -1
- package/dist/{chunk-SAMH7JFG.js → chunk-G6CVI6U2.js} +2 -2
- package/dist/{chunk-GQFH2NRB.js → chunk-HHY32NGJ.js} +6 -4
- package/dist/{chunk-JHNW2EKY.js → chunk-WIBZPZSY.js} +44 -52
- package/dist/{chunk-WOJRQCQV.js → chunk-Y6CTPQS2.js} +1 -1
- package/dist/{chunk-UXKC7JAM.js → chunk-YX62STIA.js} +2 -3
- package/dist/deploy.js +7 -7
- package/dist/dotns.js +4 -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/README.md
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
# bulletin-deploy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`bulletin-deploy` publishes a static web app to Bulletin and binds it to a human-readable `.dot` domain.
|
|
4
|
+
|
|
5
|
+
The main CLI is deploy-only. Pool bootstrap and other operator setup live in [`bulletin-bootstrap`](docs/bootstrap.md).
|
|
4
6
|
|
|
5
7
|
## Quick Start
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
npm install -g bulletin-deploy
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
# Build your app first, then deploy it.
|
|
10
13
|
bulletin-deploy ./dist my-app00.dot
|
|
11
14
|
```
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
> **Stable vs release candidate.** `npm install -g bulletin-deploy` (and `@latest`) always resolves to the latest stable version — never an RC. Release candidates are published under the `rc` dist-tag and can be installed explicitly with `npm install -g bulletin-deploy@rc` (or a literal version pin like `@0.6.9-rc.0`). Only use RCs for testing.
|
|
16
|
+
On success, the CLI prints the CID and the `.dot` domain that now serves your app.
|
|
16
17
|
|
|
17
|
-
##
|
|
18
|
+
## Installation
|
|
18
19
|
|
|
19
20
|
- **Node.js 22+**
|
|
20
|
-
- **IPFS Kubo**
|
|
21
|
+
- **IPFS Kubo** if you want the default merkleization path
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
24
|
# macOS
|
|
@@ -31,7 +32,17 @@ sudo bash kubo/install.sh
|
|
|
31
32
|
ipfs init
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
If you do not want a Kubo dependency, pass `--js-merkle`.
|
|
36
|
+
|
|
37
|
+
Stable installs:
|
|
38
|
+
|
|
39
|
+
- `npm install -g bulletin-deploy`
|
|
40
|
+
- `npm install -g bulletin-deploy@latest`
|
|
41
|
+
|
|
42
|
+
Release candidates:
|
|
43
|
+
|
|
44
|
+
- `npm install -g bulletin-deploy@rc`
|
|
45
|
+
- `npm install -g bulletin-deploy@<exact-version>`
|
|
35
46
|
|
|
36
47
|
## CLI Usage
|
|
37
48
|
|
|
@@ -45,374 +56,109 @@ Examples:
|
|
|
45
56
|
# Basic deploy
|
|
46
57
|
bulletin-deploy ./dist my-app00.dot
|
|
47
58
|
|
|
48
|
-
#
|
|
49
|
-
bulletin-deploy
|
|
59
|
+
# Direct signer deploy
|
|
60
|
+
bulletin-deploy ./dist my-app00.dot --mnemonic "..."
|
|
61
|
+
|
|
62
|
+
# Custom Bulletin RPC
|
|
63
|
+
bulletin-deploy ./dist my-app00.dot --rpc wss://custom-bulletin.example.com
|
|
50
64
|
```
|
|
51
65
|
|
|
52
|
-
###
|
|
66
|
+
### Options
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
(see Telemetry section). Use to isolate test/benchmark/canary
|
|
67
|
-
runs from real-user traffic in Sentry dashboards. Also readable
|
|
68
|
-
from DEPLOY_TAG env var.
|
|
69
|
-
--gh-pages-mirror After a successful deploy, push the CAR to the current repo's
|
|
70
|
-
gh-pages branch as a fast-path HTTP cache for host apps. Opt-in.
|
|
71
|
-
See "GitHub Pages mirror" below.
|
|
72
|
-
--help Show help
|
|
73
|
-
```
|
|
68
|
+
| Flag | What it does |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `--rpc wss://...` | Override the Bulletin RPC endpoint. Also readable from `BULLETIN_RPC`. |
|
|
71
|
+
| `--mnemonic "..."` | Use a specific mnemonic as the direct signer for Bulletin uploads and DotNS updates. Also readable from `MNEMONIC`. |
|
|
72
|
+
| `--derivation-path "..."` | Apply a Substrate derivation path to `--mnemonic`, for example `//deploy/3`. |
|
|
73
|
+
| `--pool-size N` | Change the number of derived pool accounts available for pool-mode Bulletin uploads. Default: `10`. |
|
|
74
|
+
| `--password "..."` | Encrypt SPA content before upload. Consumers must provide the password to decrypt it. |
|
|
75
|
+
| `--js-merkle` | Use pure-JS merkleization instead of the Kubo binary. |
|
|
76
|
+
| `--tag "..."` | Attach a free-form telemetry label. Also readable from `DEPLOY_TAG`. |
|
|
77
|
+
| `--gh-pages-mirror` | After a successful deploy, push the generated CAR to the current repo's `gh-pages` branch as an HTTP mirror. |
|
|
78
|
+
| `--version` | Print the CLI version. |
|
|
79
|
+
| `--help` | Show help. |
|
|
74
80
|
|
|
75
|
-
|
|
81
|
+
## Concepts
|
|
76
82
|
|
|
77
|
-
|
|
83
|
+
- `Bulletin`: the chain that stores the app payload in chunked transaction storage.
|
|
84
|
+
- `.dot domain`: the DotNS name that points at the deployed content.
|
|
85
|
+
- `CAR`: the content-addressed archive produced from your build output before upload.
|
|
86
|
+
- `merkleization`: turning a directory into a content-addressed DAG and CAR file.
|
|
87
|
+
- `pool accounts`: derived Bulletin uploader accounts used to spread nonce and authorization load.
|
|
88
|
+
- `PoP`: Proof of Personhood, which some `.dot` names require before registration.
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
bulletin-deploy --gh-pages-mirror ./dist my-app.dot
|
|
81
|
-
```
|
|
90
|
+
## Domain Rules
|
|
82
91
|
|
|
83
|
-
|
|
92
|
+
DotNS classifies labels on-chain and may require a specific Proof of Personhood level before registration.
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
Typical cases:
|
|
95
|
+
|
|
96
|
+
| Domain pattern | Typical classification |
|
|
97
|
+
|---|---|
|
|
98
|
+
| Base name, for example `my-app.dot` | `ProofOfPersonhoodFull` |
|
|
99
|
+
| Name with trailing digits, for example `my-app00.dot` | often `NoStatus`, but not guaranteed |
|
|
89
100
|
|
|
90
|
-
|
|
101
|
+
Do not rely on the string shape alone. The on-chain classifier is authoritative.
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
Mirror: https://<owner>.github.io/<repo>/bulletin/my-app.dot.car
|
|
94
|
-
```
|
|
103
|
+
On testnets, `bulletin-deploy` self-grants PoP only when the classifier says it is needed. On mainnet, PoP cannot be self-granted.
|
|
95
104
|
|
|
96
|
-
|
|
105
|
+
## GitHub Pages Mirror
|
|
97
106
|
|
|
98
|
-
-
|
|
99
|
-
- **Push permissions.** In CI, set `permissions: contents: write` on the workflow. Locally, your regular git credentials suffice.
|
|
100
|
-
- **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.
|
|
101
|
-
- **Directory deploys only.** Pre-chunked `Array<Uint8Array>` / `Uint8Array` / single-file content doesn't produce a CAR and is skipped with a log line.
|
|
107
|
+
`--gh-pages-mirror` is an opt-in cache path for hosts that want an HTTP fetch path in addition to Bulletin.
|
|
102
108
|
|
|
103
|
-
|
|
109
|
+
```bash
|
|
110
|
+
bulletin-deploy ./dist my-app.dot --gh-pages-mirror
|
|
111
|
+
```
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
- **Encrypted deploys mirror encrypted bytes.** `--password` deploys need the password to decrypt from the mirror too.
|
|
107
|
-
- **Mirror failures are non-fatal.** The source of truth is Bulletin + DotNS; the mirror is a cache. Failures log and let the deploy succeed.
|
|
108
|
-
- **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
|
+
After a successful deploy, the CLI pushes:
|
|
109
114
|
|
|
110
|
-
|
|
115
|
+
- `bulletin/<domain>.dot.car`
|
|
116
|
+
- `bulletin/<domain>.dot.json`
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
2. Customize the **Build** step for your framework (Vite, Next.js, etc.)
|
|
114
|
-
3. Push and watch the deploy
|
|
118
|
+
to the current repo's `gh-pages` branch and prints the Pages URL.
|
|
115
119
|
|
|
116
|
-
The
|
|
117
|
-
- Deploys on push to main and on PRs
|
|
118
|
-
- Uses `nick-fields/retry@v3` for automatic retries on transient failures
|
|
119
|
-
- Posts a comment on PRs with the live URL
|
|
120
|
-
- Generates domain names as `<repo>-<branch>00.dot`
|
|
120
|
+
Use it when you want to validate or consume the mirror feature. The source of truth remains Bulletin plus DotNS.
|
|
121
121
|
|
|
122
122
|
## Programmatic API
|
|
123
123
|
|
|
124
|
-
```
|
|
125
|
-
import { deploy
|
|
124
|
+
```js
|
|
125
|
+
import { deploy } from "bulletin-deploy";
|
|
126
126
|
|
|
127
127
|
const result = await deploy("./dist", "my-app00.dot");
|
|
128
128
|
console.log(result.cid, result.domainName);
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
For environments without Kubo (WebContainers, serverless), use `jsMerkle`. The optional `attributes` field lets you inject telemetry context when git is unavailable:
|
|
131
|
+
For environments without Kubo:
|
|
134
132
|
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
jsMerkle: true,
|
|
138
|
-
attributes: {
|
|
139
|
-
"deploy.source": "revx",
|
|
140
|
-
"deploy.repo": "user/project",
|
|
141
|
-
},
|
|
142
|
-
});
|
|
133
|
+
```js
|
|
134
|
+
await deploy("./dist", "my-app00.dot", { jsMerkle: true });
|
|
143
135
|
```
|
|
144
136
|
|
|
145
|
-
## Domain Names and Proof of Personhood
|
|
146
|
-
|
|
147
|
-
DotNS domain names are classified by the PopOracle contract on Asset Hub. The classification determines what level of **Proof of Personhood (PoP)** is required to register. PopOracle is the authoritative source — query `POP_RULES.classifyName(label)` on chain for any name. As a rough guide:
|
|
148
|
-
|
|
149
|
-
| Domain pattern | Typical classification |
|
|
150
|
-
|---|---|
|
|
151
|
-
| Base name (no trailing digits) — e.g. `my-app.dot` | `ProofOfPersonhoodFull` required |
|
|
152
|
-
| Name with trailing digits — e.g. `my-app00.dot` | Usually `NoStatus` (open to all), **but see caveat below** |
|
|
153
|
-
|
|
154
|
-
**Caveat — the classifier is non-obvious.** Two names that both end in trailing digits can land in different buckets. Observed examples on Paseo: `rc069pool00` classifies as `NoStatus`, `rc069dir00` classifies as `ProofOfPersonhoodLite`. The safe approach is to let `POP_RULES.classifyName` tell you — don't assume from the string.
|
|
155
|
-
|
|
156
|
-
### Self-grant on testnets
|
|
157
|
-
|
|
158
|
-
`bulletin-deploy` self-grants PoP before registration **only when the classifier says it's needed**. Specifically, when `classifyName(label)` returns something other than `NoStatus`, the tool calls `setUserPopStatus(requiredLevel)` so the signer matches the requirement. If the classifier returns `NoStatus`, no self-grant happens and the signer's current status is used as-is.
|
|
159
|
-
|
|
160
|
-
On **mainnet** (Polkadot Hub, Kusama Hub), PoP cannot be self-granted (the contract enforces verification). You must have a verified account. Set `DOTNS_STATUS=none` explicitly or leave it unset.
|
|
161
|
-
|
|
162
|
-
You can force a specific self-grant level on any network with `DOTNS_STATUS=full|lite|none`. See `tools/check-pop-status.mjs` to query an address's current status.
|
|
163
|
-
|
|
164
|
-
If you see **"Requires Full Personhood verification"**, the deploy will fail early with an actionable error message before any gas is spent on commitment transactions.
|
|
165
|
-
|
|
166
137
|
## Environment Variables
|
|
167
138
|
|
|
168
139
|
| Variable | Default | Description |
|
|
169
140
|
|---|---|---|
|
|
170
141
|
| `BULLETIN_RPC` | `wss://paseo-bulletin-rpc.polkadot.io` | Bulletin chain WebSocket RPC |
|
|
171
|
-
| `BULLETIN_DEPLOY_TELEMETRY` |
|
|
172
|
-
| `BULLETIN_DEPLOY_UPDATE_CHECK` | `1`
|
|
173
|
-
| `DOTNS_STATUS` | `full`
|
|
174
|
-
| `IPFS_CID` |
|
|
175
|
-
|
|
176
|
-
## How It Works
|
|
177
|
-
|
|
178
|
-
```
|
|
179
|
-
Build output ──> Merkleize ──> CAR file ──> Chunk upload ──> DotNS
|
|
180
|
-
./dist (Kubo or JS) .car Bulletin Asset Hub
|
|
181
|
-
Storage Registry
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
1. **Merkleize** your build directory to produce a content-addressed CAR file
|
|
185
|
-
2. **Chunk and upload** the CAR file to Bulletin's TransactionStorage (1MB chunks, 2 per batch)
|
|
186
|
-
3. **Store the DAG root** that links all chunks together under a single CID
|
|
187
|
-
4. **Register or update** your `.dot` domain on Asset Hub with the new contenthash
|
|
188
|
-
|
|
189
|
-
Your site is immediately accessible at `https://your-domain.dot.li`
|
|
190
|
-
|
|
191
|
-
## Merkleization modes
|
|
192
|
-
|
|
193
|
-
bulletin-deploy supports two ways to merkleize your build directory into a CAR file:
|
|
194
|
-
|
|
195
|
-
| Mode | Flag | Requires | Best for |
|
|
196
|
-
|---|---|---|---|
|
|
197
|
-
| **Kubo** (default) | _(none)_ | IPFS Kubo binary installed | CI pipelines, local development |
|
|
198
|
-
| **JS** | `--js-merkle` | Nothing beyond Node.js | WebContainers, serverless, environments without system binaries |
|
|
199
|
-
|
|
200
|
-
Both modes produce valid IPFS UnixFS DAGs. The CIDs may differ between modes for the same input (different chunking implementations), but this is fine — each deploy sets a fresh contenthash on DotNS regardless.
|
|
201
|
-
|
|
202
|
-
### CLI
|
|
203
|
-
|
|
204
|
-
```bash
|
|
205
|
-
# Default (Kubo)
|
|
206
|
-
bulletin-deploy ./dist my-app00.dot
|
|
207
|
-
|
|
208
|
-
# JS merkleization
|
|
209
|
-
bulletin-deploy --js-merkle ./dist my-app00.dot
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Programmatic
|
|
213
|
-
|
|
214
|
-
```javascript
|
|
215
|
-
import { deploy } from "bulletin-deploy";
|
|
216
|
-
|
|
217
|
-
// Default (Kubo)
|
|
218
|
-
await deploy("./dist", "my-app00.dot");
|
|
219
|
-
|
|
220
|
-
// JS merkleization
|
|
221
|
-
await deploy("./dist", "my-app00.dot", { jsMerkle: true });
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
The JS mode uses `ipfs-unixfs-importer` (the same chunker Kubo uses internally) and `@ipld/car` for CAR serialization. It runs entirely in-memory with no temp files.
|
|
225
|
-
|
|
226
|
-
## Resilience Features
|
|
227
|
-
|
|
228
|
-
### Chunk-level retry
|
|
229
|
-
|
|
230
|
-
Each batch of chunks is submitted with `Promise.allSettled`. Failed chunks are retried up to 3 times with a fresh nonce, serialized to avoid nonce conflicts. If a chunk fails all retries, the deploy aborts with a clear error.
|
|
231
|
-
|
|
232
|
-
### Account pool
|
|
233
|
-
|
|
234
|
-
Instead of using a single account for all storage transactions, bulletin-deploy derives a pool of accounts from a mnemonic. Each deploy selects the account with the most remaining authorization capacity. This prevents nonce conflicts between concurrent deploys and distributes the storage authorization budget.
|
|
235
|
-
|
|
236
|
-
### Auto-authorization
|
|
237
|
-
|
|
238
|
-
When a pool account's authorization drops below thresholds (50 transactions or 50MB), bulletin-deploy automatically tops it up by submitting an `authorize_account` transaction from Alice.
|
|
239
|
-
|
|
240
|
-
## Version Checking
|
|
241
|
-
|
|
242
|
-
When a deploy fails, bulletin-deploy checks whether a newer version is available. This helps catch cases where the error was already fixed in a later release.
|
|
243
|
-
|
|
244
|
-
Two sources are checked in parallel:
|
|
245
|
-
- **npm registry** — reads the `minimumVersion` field from the latest published version
|
|
246
|
-
- **GitHub kill switch** (`min-version.json` in the repo) — allows emergency deprecation without a release
|
|
247
|
-
|
|
248
|
-
If either source indicates your version is below the minimum, the CLI tells you the version is unsupported and why. If you're simply outdated (but above the minimum), you get a suggestion to update.
|
|
249
|
-
|
|
250
|
-
For **paritytech** contributors in an interactive terminal, the CLI offers to update and retry automatically. For everyone else (external users, CI), it prints the update command and exits.
|
|
251
|
-
|
|
252
|
-
If you're already on the latest version and hit an error, the CLI offers to open a GitHub issue with collected debug info (paritytech contributors only).
|
|
253
|
-
|
|
254
|
-
Set `BULLETIN_DEPLOY_UPDATE_CHECK=0` to disable version checking.
|
|
255
|
-
|
|
256
|
-
## Telemetry
|
|
257
|
-
|
|
258
|
-
Sentry telemetry is **off by default for external users**. It's automatically on for deploys originating from inside Parity — either Parity CI, or local development trees whose git origin points at `paritytech/*`, `w3f/*`, or `polkadot-fellows/*`.
|
|
259
|
-
|
|
260
|
-
- `BULLETIN_DEPLOY_TELEMETRY=1` — explicit opt-in (useful if you want to help us debug an issue you're seeing).
|
|
261
|
-
- `BULLETIN_DEPLOY_TELEMETRY=0` — force off regardless of context.
|
|
262
|
-
- When running under **Bun**, the Parity-internal memory-report diagnostic bundle is skipped (basic deploy telemetry still works). The bundle relies on Node's `v8` module, which Bun implements only partially.
|
|
263
|
-
|
|
264
|
-
Detection signals (OR'd together):
|
|
265
|
-
1. `GITHUB_REPOSITORY` matches a known-internal org — Parity-owned CI workflow.
|
|
266
|
-
2. `RUNNER_NAME` starts with `parity-` — Parity self-hosted runner.
|
|
267
|
-
3. `git remote get-url origin` points at a known-internal org — internal local dev.
|
|
268
|
-
|
|
269
|
-
What's tracked:
|
|
270
|
-
- Deploy duration and success/failure
|
|
271
|
-
- Storage phase timing (merkleize, chunk upload, root node)
|
|
272
|
-
- DotNS phase timing (registration, contenthash update)
|
|
273
|
-
- Pool account selection
|
|
274
|
-
- Source metadata (repo, branch, PR number, CI vs local)
|
|
275
|
-
- Tool version (`deploy.tool_version`)
|
|
276
|
-
|
|
277
|
-
### Using bulletin-deploy as a library under your own Sentry
|
|
278
|
-
|
|
279
|
-
If your tool embeds bulletin-deploy as a library and already runs its own Sentry SDK (for example, Parity's `playground-cli`), you can route bulletin-deploy's deploy spans, tags, and diagnostics through your existing Sentry client rather than clobbering it with ours. Set two env vars **before** importing or invoking bulletin-deploy:
|
|
280
|
-
|
|
281
|
-
```sh
|
|
282
|
-
BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1
|
|
283
|
-
BULLETIN_DEPLOY_HOST_APP=<your-app-name>
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
What happens:
|
|
287
|
-
|
|
288
|
-
- `BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1` makes `initTelemetry()` skip its own `Sentry.init()` call. bulletin-deploy reuses the global Sentry client your app already configured. All deploy spans, breadcrumbs, `captureWarning`/`captureMessage` calls route to your DSN.
|
|
289
|
-
- `BULLETIN_DEPLOY_HOST_APP=<name>` attaches `deploy.host_app: <name>` to every deploy span **and** as a Sentry scope tag, so downstream events in the same process carry it too. Use it to facet dashboards by host.
|
|
290
|
-
|
|
291
|
-
**Requirements:**
|
|
292
|
-
|
|
293
|
-
- Your app must call its own `Sentry.init()` **before** importing or spawning bulletin-deploy; otherwise there is no ambient client to reuse and telemetry is effectively off.
|
|
294
|
-
- Your Sentry project should live in the same Sentry organisation as `bulletin-deploy` (`o4511059872841728.ingest.de.sentry.io`) if you want our cross-project dashboards to aggregate your traffic. Different org = different world; no cross-aggregation is possible.
|
|
295
|
-
- If your consumer app is maintained by Parity, we can add its name to the `PARITY_HOST_APPS` allowlist in `src/telemetry.ts` so end-user installs of the compiled binary qualify for the same diagnostics as our internal CI. Today: `playground-cli`.
|
|
296
|
-
|
|
297
|
-
**Gotchas:**
|
|
298
|
-
|
|
299
|
-
- Quotas are per-project. A traffic spike in your project will eat your quota and can drop bulletin-deploy spans routing through it without any signal in our dashboards.
|
|
300
|
-
- Issue-feed fingerprints don't dedupe across projects: the same error in your project and ours surfaces as two separate Sentry issues.
|
|
301
|
-
- `@sentry/node` major version must be compatible with ours (currently v8.x). Skew risks runtime errors on the first span call.
|
|
302
|
-
|
|
303
|
-
### Tagging test and benchmark runs
|
|
304
|
-
|
|
305
|
-
Real-user deploys and automated test/benchmark deploys share the same telemetry pipeline. Use `--tag` (or the `DEPLOY_TAG` env var) to label non-production runs so Sentry dashboards can filter them out:
|
|
306
|
-
|
|
307
|
-
```bash
|
|
308
|
-
bulletin-deploy --tag e2e-ci-pr ./build my-app.dot
|
|
309
|
-
DEPLOY_TAG=load-test bulletin-deploy ./build my-app.dot
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
The `tag` value is attached to the deploy span as `deploy.tag` (plus propagated through every child span).
|
|
313
|
-
|
|
314
|
-
Convention used in this repo:
|
|
315
|
-
- `e2e-ci-pr` / `e2e-ci-nightly` — CI-driven E2E runs (per-PR and nightly matrices).
|
|
316
|
-
- `e2e-local-smoke` / `e2e-local-pr` / `e2e-local-nightly` — maintainer-invoked E2E via `scripts/e2e-pass.sh`.
|
|
317
|
-
- Untagged — real-user deploys.
|
|
318
|
-
|
|
319
|
-
Sentry filter examples:
|
|
320
|
-
- Exclude all test traffic from a dashboard: `!has:deploy.tag`
|
|
321
|
-
- Only CI E2E: `deploy.tag:e2e-ci-*`
|
|
322
|
-
- Only nightly runs (local or CI): `deploy.tag:e2e-ci-nightly OR deploy.tag:e2e-local-nightly`
|
|
323
|
-
|
|
324
|
-
The shipped reusable workflow (`.github/workflows/deploy.yml`) exposes a `tag` input that feeds through to the CLI — pass it from caller workflows that do non-production deploys.
|
|
325
|
-
|
|
326
|
-
Dashboards:
|
|
327
|
-
- **Bulletin Deploy Health**: https://paritytech.sentry.io/dashboard/1669817/?project=4511093597405264 — overall deploy health across all consumer repos
|
|
328
|
-
- **Deploy Failures Detail**: https://paritytech.sentry.io/dashboard/1669818/?project=4511093597405264 — error drill-down
|
|
329
|
-
- **E2E Health (bulletin-deploy)**: https://paritytech.sentry.io/dashboard/1732713/?project=4511093597405264 — E2E suite pass rate, duration, and per-signer failure distribution (filtered by `deploy.tag:e2e-*`)
|
|
330
|
-
|
|
331
|
-
## Testing
|
|
332
|
-
|
|
333
|
-
Three layers of coverage: offline unit tests for pure helpers, live-testnet end-to-end tests that exercise the shipped reusable workflow, and CI matrices that run both per-PR and nightly.
|
|
334
|
-
|
|
335
|
-
### Offline unit tests
|
|
336
|
-
|
|
337
|
-
```bash
|
|
338
|
-
npm test
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
Runs `test/test.js` + `test/pool.test.js` + `test/helpers/e2e-helpers.test.js` via `node --test`. No network. ~5 seconds. Always runs on every PR via GitHub Actions.
|
|
342
|
-
|
|
343
|
-
### Live-testnet E2E
|
|
344
|
-
|
|
345
|
-
Four scenarios land on Paseo Bulletin:
|
|
346
|
-
|
|
347
|
-
- **S1** — happy path on a stable label (`e2epool.dot` / `e2edirect.dot`)
|
|
348
|
-
- **S2** — fresh registration via commit-reveal (nightly only)
|
|
349
|
-
- **S3** — deploy to `e2eowned.dot` (owned by a different account), expects `EXIT_CODE_NO_RETRY` (78) and the "owned by a different account" error message
|
|
350
|
-
- **S4** — deploy with `--gh-pages-mirror`, waits for GitHub Pages to serve the just-pushed manifest (CID-freshness check), then byte-compares the CAR on Pages against a pre-upload dump to confirm the mirror is an exact copy of what went to Bulletin
|
|
351
|
-
|
|
352
|
-
**Prerequisites** (one-time per testnet lifetime): see [`docs/e2e-bootstrap.md`](docs/e2e-bootstrap.md). Grants Alice PoP Full, funds+maps Bob, pre-registers `e2eowned.dot` to Bob via `dotns-cli`. S4 additionally needs GitHub Pages enabled on the repo with `gh-pages` as the source branch and a token with `contents: write` (the workflow's `GITHUB_TOKEN` provides this; locally your git credentials must be able to push).
|
|
353
|
-
|
|
354
|
-
**Local launchers:**
|
|
355
|
-
|
|
356
|
-
```bash
|
|
357
|
-
npm run test:e2e:smoke # 1 scenario (S1 pool/js) ~5 min
|
|
358
|
-
npm run test:e2e:pr # 4 scenarios (matches per-PR CI) ~20 min
|
|
359
|
-
npm run test:e2e:nightly # 8 scenarios (matches nightly CI) ~30–45 min
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
All three run through to completion even if one fails; a colored summary prints at the end with per-scenario pass/fail, timing, JUnit report paths, and a pre-filtered Sentry trace link.
|
|
363
|
-
|
|
364
|
-
**Agent-friendly / quiet mode** — `E2E_QUIET=1` suppresses the live streaming output (`node:test` spec reporter + bulletin-deploy stdio + build logs) while still printing the summary and writing structured reports. Works with every mode:
|
|
365
|
-
|
|
366
|
-
```bash
|
|
367
|
-
E2E_QUIET=1 npm run test:e2e:smoke
|
|
368
|
-
E2E_QUIET=1 npm run test:e2e:pr
|
|
369
|
-
E2E_QUIET=1 npm run test:e2e:nightly
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
Equivalent flag form: `bash scripts/e2e-pass.sh --quiet <mode>`.
|
|
373
|
-
|
|
374
|
-
**JUnit reports** — every scenario writes one to `e2e-reports/<scenario>-<signer>-<merkle>.xml` regardless of quiet mode. Consume via any JUnit-compatible tool, or just `cat` the XML — `<failure>` blocks carry the assertion message and stack.
|
|
375
|
-
|
|
376
|
-
### CI matrices (`.github/workflows/e2e.yml`)
|
|
377
|
-
|
|
378
|
-
The CI workflow **calls the shipped reusable `.github/workflows/deploy.yml`** for every scenario — so each E2E job runs exactly the code path real consumers hit. Per-PR tests HEAD via `bulletin-deploy-version: "git+https://...#<sha>"` (the `prepare` npm script builds `dist/` during install). Nightly leaves the version empty so it tests the latest published release.
|
|
379
|
-
|
|
380
|
-
- **Per-PR** (3 jobs): on `pull_request` and `push` to `main`. S1 pool/js + S1 direct/kubo + S3 negative. Posts a sticky comment on the PR with results and updates in place on re-runs.
|
|
381
|
-
- **Nightly** (11 jobs): scheduled `0 3 * * *` UTC. Full S1 signer×merkle×runner cube + S2 pool/direct fresh-registration + S3. Failures auto-open a GitHub issue.
|
|
382
|
-
|
|
383
|
-
Every deploy job passes `tag: e2e-ci-pr` or `tag: e2e-ci-nightly` through to the CLI, so the E2E traffic is tagged in Sentry and can be filtered out of real-user dashboards (see Telemetry → Tagging test and benchmark runs above).
|
|
384
|
-
|
|
385
|
-
### Tag convention in this repo
|
|
386
|
-
|
|
387
|
-
| Trigger | `deploy.tag` |
|
|
388
|
-
|---|---|
|
|
389
|
-
| `npm run test:e2e:smoke` | `e2e-local-smoke` |
|
|
390
|
-
| `npm run test:e2e:pr` | `e2e-local-pr` |
|
|
391
|
-
| `npm run test:e2e:nightly` | `e2e-local-nightly` |
|
|
392
|
-
| E2E workflow per-PR | `e2e-ci-pr` |
|
|
393
|
-
| E2E workflow nightly | `e2e-ci-nightly` |
|
|
394
|
-
| Real-user deploys | _none_ (`!has:deploy.tag`) |
|
|
142
|
+
| `BULLETIN_DEPLOY_TELEMETRY` | off for external users, on for internal users | `1` to opt in, `0` to force off |
|
|
143
|
+
| `BULLETIN_DEPLOY_UPDATE_CHECK` | `1` | Set to `0` to disable version checks on failure |
|
|
144
|
+
| `DOTNS_STATUS` | `full` on testnet, `none` on mainnet | PoP level to self-grant before registration: `none`, `lite`, or `full` |
|
|
145
|
+
| `IPFS_CID` | unset | Skip storage and reuse an existing CID |
|
|
146
|
+
| `DEPLOY_TAG` | unset | Telemetry label equivalent to `--tag` |
|
|
395
147
|
|
|
396
148
|
## Troubleshooting
|
|
397
149
|
|
|
398
|
-
| Error |
|
|
150
|
+
| Error | What to check |
|
|
399
151
|
|---|---|
|
|
400
|
-
| `Requires Full Personhood verification` |
|
|
401
|
-
| `
|
|
402
|
-
| `
|
|
403
|
-
| `
|
|
404
|
-
| `
|
|
405
|
-
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
-
|
|
412
|
-
-
|
|
413
|
-
|
|
414
|
-
To point at a different chain, set the `BULLETIN_RPC` environment variable:
|
|
415
|
-
|
|
416
|
-
```bash
|
|
417
|
-
BULLETIN_RPC=wss://your-bulletin-rpc.example.com bulletin-deploy ./dist my-app00.dot
|
|
418
|
-
```
|
|
152
|
+
| `Requires Full Personhood verification` | The chosen label needs a higher PoP level. |
|
|
153
|
+
| `Domain ... is owned by a different account` | The `.dot` name is already owned by the account indicated. Use that account as parameter or transfer the domain from that account to the new account you want to use |
|
|
154
|
+
| `Account ... is not authorized for Bulletin storage` | The uploader account is not authorized on Bulletin yet. For operator-managed pools, see [`bulletin-bootstrap`](docs/bootstrap.md). |
|
|
155
|
+
| `fetchNonce timed out` or connection errors | The Bulletin RPC may be unhealthy. Try another endpoint. |
|
|
156
|
+
| `IPFS CLI not installed` | Install Kubo or switch to `--js-merkle`. |
|
|
157
|
+
| Previous deploy did not exit cleanly / OOM hint | Retry with a larger Node heap, for example `NODE_OPTIONS='--max-old-space-size=8192'`. |
|
|
158
|
+
|
|
159
|
+
## More Docs
|
|
160
|
+
|
|
161
|
+
- [Bootstrap and operator setup](docs/bootstrap.md)
|
|
162
|
+
- [Testing](docs/testing.md)
|
|
163
|
+
- [Telemetry](docs/telemetry.md)
|
|
164
|
+
- [E2E one-time setup](docs/e2e-bootstrap.md)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE } from "../dist/deploy.js";
|
|
4
|
+
import { bootstrapPool } from "../dist/pool.js";
|
|
5
|
+
import { VERSION } from "../dist/telemetry.js";
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
|
|
9
|
+
const flags = {};
|
|
10
|
+
for (let i = 0; i < args.length; i++) {
|
|
11
|
+
if (args[i] === "--pool-size") { flags.poolSize = parseInt(args[++i], 10); }
|
|
12
|
+
else if (args[i] === "--mnemonic") { flags.mnemonic = args[++i]; }
|
|
13
|
+
else if (args[i] === "--rpc") { flags.rpc = args[++i]; }
|
|
14
|
+
else if (args[i] === "--version" || args[i] === "-V") { flags.version = true; }
|
|
15
|
+
else if (args[i] === "--help" || args[i] === "-h") { flags.help = true; }
|
|
16
|
+
else {
|
|
17
|
+
console.error(`Error: unknown argument ${args[i]}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (flags.version) {
|
|
23
|
+
console.log(`bulletin-bootstrap v${VERSION}`);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (flags.help) {
|
|
28
|
+
console.log(`bulletin-bootstrap v${VERSION}
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
bulletin-bootstrap
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--mnemonic "..." Pool root mnemonic (or set BULLETIN_POOL_MNEMONIC / MNEMONIC env var)
|
|
35
|
+
--rpc wss://... Bulletin RPC (or set BULLETIN_RPC env var)
|
|
36
|
+
--pool-size N Number of pool accounts to initialize (default: 10)
|
|
37
|
+
--version Show version
|
|
38
|
+
--help Show this help
|
|
39
|
+
|
|
40
|
+
Initialize pool accounts for Bulletin storage authorization.`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
|
|
45
|
+
const poolSize = flags.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
|
|
46
|
+
const mnemonic = flags.mnemonic ?? process.env.BULLETIN_POOL_MNEMONIC ?? process.env.MNEMONIC;
|
|
47
|
+
|
|
48
|
+
await bootstrapPool(rpc, poolSize, mnemonic);
|