@crvouga/atlas 0.5.0 → 0.6.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/CHANGELOG.md +6 -0
- package/README.md +30 -11
- package/dist/core/src/cli.d.ts +1 -1
- package/dist/core/src/cli.js +8 -3
- package/dist/core/src/config.d.ts +7 -0
- package/dist/core/src/config.js +48 -14
- package/dist/core/src/run/execute.d.ts +6 -0
- package/dist/core/src/run/execute.js +4 -2
- package/dist/reports/src/doctor.d.ts +1 -1
- package/dist/reports/src/doctor.js +3 -1
- package/dist/reports/src/http-store.d.ts +4 -0
- package/dist/reports/src/http-store.js +6 -3
- package/dist/reports/src/init.js +6 -1
- package/dist/reports/src/live-reporter.d.ts +34 -0
- package/dist/reports/src/live-reporter.js +122 -0
- package/dist/reports/src/report-cli.d.ts +1 -1
- package/dist/reports/src/report-cli.js +3 -2
- package/dist/reports/src/store-url.js +1 -1
- package/dist/runners/src/codecept.d.ts +14 -0
- package/dist/runners/src/codecept.js +16 -0
- package/dist/runners/src/jest.d.ts +50 -0
- package/dist/runners/src/jest.js +52 -0
- package/dist/runners/src/mocha.d.ts +40 -0
- package/dist/runners/src/mocha.js +39 -0
- package/dist/runners/src/node-test.d.ts +28 -0
- package/dist/runners/src/node-test.js +44 -0
- package/dist/runners/src/publish.d.ts +30 -0
- package/dist/runners/src/publish.js +68 -0
- package/dist/schema/src/api.d.ts +1 -0
- package/dist/schema/src/api.js +1 -1
- package/dist/schema/src/index.d.ts +2 -0
- package/dist/schema/src/index.js +2 -0
- package/dist/schema/src/live-run.d.ts +123 -0
- package/dist/schema/src/live-run.js +30 -0
- package/dist/schema/src/manifest.d.ts +2 -2
- package/dist/schema/src/project.d.ts +20 -0
- package/dist/schema/src/project.js +10 -0
- package/dist/schema/src/report-sources.d.ts +1 -0
- package/dist/schema/src/report-sources.js +2 -1
- package/dist/schema/src/runs-index.d.ts +1 -1
- package/dist/schema/src/server-config.d.ts +84 -0
- package/dist/schema/src/server-config.js +12 -0
- package/dist/server/src/app.d.ts +10 -0
- package/dist/server/src/app.js +148 -10
- package/dist/server/src/config.d.ts +1 -1
- package/dist/server/src/config.js +20 -3
- package/dist/server/src/dev.d.ts +3 -3
- package/dist/server/src/dev.js +22 -13
- package/dist/server/src/events.d.ts +3 -3
- package/dist/server/src/events.js +1 -1
- package/dist/server/src/index.d.ts +2 -0
- package/dist/server/src/index.js +2 -0
- package/dist/server/src/live-runs.d.ts +52 -0
- package/dist/server/src/live-runs.js +229 -0
- package/dist/server/src/main.d.ts +1 -1
- package/dist/server/src/main.js +7 -5
- package/dist/server/src/openapi.d.ts +2 -5
- package/dist/server/src/openapi.js +71 -7
- package/dist/server/src/projects.d.ts +3 -0
- package/dist/server/src/projects.js +154 -0
- package/dist/server/src/settings-page.d.ts +9 -0
- package/dist/server/src/settings-page.js +93 -0
- package/dist/server/src/settings.d.ts +23 -0
- package/dist/server/src/settings.js +72 -0
- package/dist/server/src/site.d.ts +8 -6
- package/dist/server/src/site.js +45 -10
- package/dist/server/src/store-api.js +43 -22
- package/docs/deploying.md +19 -19
- package/docs/dogfooding.md +2 -2
- package/docs/extending.md +3 -3
- package/docs/integrating.md +9 -9
- package/docs/reference/cli.md +9 -7
- package/docs/reference/core.md +6 -0
- package/docs/reference/http-api.md +498 -2
- package/docs/reference/reports.md +55 -1
- package/docs/reference/runners.md +169 -0
- package/docs/reference/schema.md +111 -0
- package/docs/reference/server.md +154 -14
- package/docs/report-backends.md +8 -8
- package/docs/report-contract.md +7 -7
- package/docs/reporting-issues.md +3 -3
- package/docs/runners.md +69 -0
- package/docs/server.md +61 -25
- package/docs/statecharts.md +22 -20
- package/llms-full.txt +713 -105
- package/llms.txt +3 -1
- package/package.json +31 -9
- package/packages/runners/README.md +6 -0
- package/packages/runners/compat/codecept.cjs +2 -0
- package/packages/runners/compat/jest.cjs +2 -0
- package/packages/runners/compat/mocha.cjs +2 -0
- package/packages/schema/schemas/atlas-change.schema.json +4 -0
- package/packages/schema/schemas/hello-event.schema.json +4 -0
- package/packages/schema/schemas/live-run-start.schema.json +42 -0
- package/packages/schema/schemas/live-run-update.schema.json +93 -0
- package/packages/schema/schemas/live-run.schema.json +225 -0
- package/packages/schema/schemas/project-slug.schema.json +8 -0
- package/packages/schema/schemas/project.schema.json +22 -0
- package/packages/schema/schemas/projects.schema.json +33 -0
- package/packages/schema/schemas/server-config.schema.json +14 -0
- package/packages/schema/schemas/server-editable-settings.schema.json +132 -0
- package/packages/schema/schemas/server-settings-snapshot.schema.json +285 -0
- package/packages/server/openapi.json +5101 -779
- package/packages/visualizer/README.md +13 -13
- package/packages/visualizer/bin/atlas-client.mjs +176 -0
- package/packages/visualizer/bin/atlas-visualizer.mjs +2 -175
- package/packages/visualizer/src/app/AppShell.tsx +26 -17
- package/packages/visualizer/src/app/report-sources.tsx +2 -1
- package/packages/visualizer/src/components/ActivityIndicator.module.css +4 -0
- package/packages/visualizer/src/components/ActivityIndicator.tsx +6 -0
- package/packages/visualizer/src/components/Header.module.css +101 -227
- package/packages/visualizer/src/components/Header.tsx +79 -41
- package/packages/visualizer/src/components/ProjectPicker.tsx +64 -0
- package/packages/visualizer/src/data/adapters/report-backend.test.ts +3 -2
- package/packages/visualizer/src/data/adapters/report-backend.ts +1 -1
- package/packages/visualizer/src/data/queries.ts +14 -7
- package/packages/visualizer/src/evidence/api.ts +2 -1
- package/packages/visualizer/src/evidence/check-detail.tsx +19 -4
- package/packages/visualizer/src/evidence/document-page.tsx +6 -2
- package/packages/visualizer/src/evidence/evidence-context.tsx +17 -12
- package/packages/visualizer/src/evidence/evidence.module.css +11 -1
- package/packages/visualizer/src/evidence/feature-page.tsx +7 -3
- package/packages/visualizer/src/evidence/model-page.tsx +6 -2
- package/packages/visualizer/src/evidence/project-home.tsx +45 -0
- package/packages/visualizer/src/evidence/recording-page.tsx +4 -1
- package/packages/visualizer/src/evidence/spec-index.tsx +2 -1
- package/packages/visualizer/src/evidence/test-filter.tsx +21 -0
- package/packages/visualizer/src/live/live-query.ts +21 -0
- package/packages/visualizer/src/live/live-runs.tsx +69 -0
- package/packages/visualizer/src/live/live-stream.ts +30 -9
- package/packages/visualizer/src/live/report-progress.ts +62 -0
- package/packages/visualizer/src/live/test-progress.tsx +34 -0
- package/packages/visualizer/src/runtime.ts +17 -1
- package/packages/visualizer/src/state/ui-store.ts +3 -2
- package/packages/visualizer/src/views/ChartPage.tsx +2 -0
- package/packages/visualizer/src/views/ChartTests.tsx +43 -0
- package/packages/visualizer/src/views/SidePanel.tsx +2 -0
- package/packages/visualizer/vite/atlas-files.test.ts +4 -4
- package/packages/visualizer/vite/atlas-files.ts +1 -5
- package/packages/visualizer/vite/atlas-server-proxy.ts +2 -1
|
@@ -23,16 +23,21 @@ export async function readLimited(request, limit) {
|
|
|
23
23
|
const reader = request.body.getReader();
|
|
24
24
|
const chunks = [];
|
|
25
25
|
let size = 0;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
try {
|
|
27
|
+
for (;;) {
|
|
28
|
+
const { done, value } = await reader.read();
|
|
29
|
+
if (done)
|
|
30
|
+
break;
|
|
31
|
+
size += value.byteLength;
|
|
32
|
+
if (size > limit) {
|
|
33
|
+
await reader.cancel();
|
|
34
|
+
throw new Refusal(413, `The body is over the limit of ${limit} bytes`);
|
|
35
|
+
}
|
|
36
|
+
chunks.push(value);
|
|
34
37
|
}
|
|
35
|
-
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
reader.releaseLock();
|
|
36
41
|
}
|
|
37
42
|
const out = new Uint8Array(size);
|
|
38
43
|
let at = 0;
|
|
@@ -69,6 +74,20 @@ export function storeApi(options) {
|
|
|
69
74
|
const { store, auth } = options;
|
|
70
75
|
// A layer's manifest is validated when it arrives and its catalog entry follows: what was read the first time serves the second.
|
|
71
76
|
const recent = new Map();
|
|
77
|
+
// Serialize only competing writes to the same key. Other uploads retain their concurrency.
|
|
78
|
+
const writing = new Map();
|
|
79
|
+
async function exclusive(key, action) {
|
|
80
|
+
const previous = writing.get(key) ?? Promise.resolve();
|
|
81
|
+
const next = previous.catch(() => undefined).then(action);
|
|
82
|
+
writing.set(key, next);
|
|
83
|
+
try {
|
|
84
|
+
return await next;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
if (writing.get(key) === next)
|
|
88
|
+
writing.delete(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
72
91
|
const refuse = (c, status, message) => c.json({ error: message }, status);
|
|
73
92
|
app.get('/', async (c) => {
|
|
74
93
|
const verdict = auth.check(c.req.raw, 'read');
|
|
@@ -123,19 +142,21 @@ export function storeApi(options) {
|
|
|
123
142
|
return refuse(c, 422, 'Not a key Atlas holds');
|
|
124
143
|
try {
|
|
125
144
|
const bytes = await readLimited(c.req.raw, options.maxObjectBytes);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
return await exclusive(name, async () => {
|
|
146
|
+
const kind = await validateUpload(name, bytes, store, layer => { recent.set(layer.id, layer); if (recent.size > 8)
|
|
147
|
+
recent.delete(recent.keys().next().value); });
|
|
148
|
+
// A blob's name is the digest of what is in it, which was just checked: one that is there is this one, however it was compressed.
|
|
149
|
+
const present = kind === 'blob' && (await store.head(name)) ? 'same' : await sameBytes(store, name, bytes);
|
|
150
|
+
// Scratch objects may be overwritten (an object store's contract says writing a key twice is fine); layers may not.
|
|
151
|
+
if (present === 'different' && kind !== 'scratch')
|
|
152
|
+
throw new Refusal(409, `${name} already exists with other content. Layers are immutable: publish a new one.`);
|
|
153
|
+
if (present !== 'same')
|
|
154
|
+
await store.put(name, bytes, c.req.header('content-type') ?? 'application/octet-stream');
|
|
155
|
+
// Catalog entries make a layer, spec set or run visible, so everyone is told only once they are in.
|
|
156
|
+
if (present === 'absent' || isCatalog(kind))
|
|
157
|
+
await options.onStored(name, kind, kind === 'layer-catalog' ? recent.get(classify(name)?.id ?? '') : undefined);
|
|
158
|
+
return c.json({ ok: true, key: name, kind, created: present === 'absent' }, present === 'absent' ? 201 : 200);
|
|
159
|
+
});
|
|
139
160
|
}
|
|
140
161
|
catch (error) {
|
|
141
162
|
if (error instanceof Refusal)
|
package/docs/deploying.md
CHANGED
|
@@ -68,7 +68,7 @@ so run `atlas report doctor` against yours before relying on it.
|
|
|
68
68
|
```sh
|
|
69
69
|
atlas report policy --store s3://my-bucket/atlas/my-project --role publisher # CI and developers
|
|
70
70
|
atlas report policy --store s3://my-bucket/atlas/my-project --role reader # a server that only reads it, and the gate
|
|
71
|
-
# the server that uploads into it needs --role both
|
|
71
|
+
# the server that uploads into it needs --role both (also grants server/live/* for live leases)
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
A publisher may write and read `layers/*` and list the folder. A reader may read `layers/*` and list.
|
|
@@ -177,7 +177,7 @@ Everything is immutable and addressed by its content, so each publish is safe to
|
|
|
177
177
|
in parallel. A set of specs is named by the files and the commit, so publishing the same commit from ten
|
|
178
178
|
nodes writes it once.
|
|
179
179
|
|
|
180
|
-
For statecharts, publish each run as a layer and upload it for the
|
|
180
|
+
For statecharts, publish each run as a layer and upload it for the client:
|
|
181
181
|
|
|
182
182
|
```sh
|
|
183
183
|
bunx atlas run --config atlas.config.ts --shard 1/2
|
|
@@ -185,7 +185,7 @@ bunx atlas report publish --producer statecharts --environment desktop-web \
|
|
|
185
185
|
--manifest atlas-runs/<run>/manifest.json --specs ./specs --upload-run
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
A run is uploaded only when its privacy scan passed: the same rule the static
|
|
188
|
+
A run is uploaded only when its privacy scan passed: the same rule the static client applies.
|
|
189
189
|
|
|
190
190
|
## 4. The server
|
|
191
191
|
|
|
@@ -193,7 +193,7 @@ A run is uploaded only when its privacy scan passed: the same rule the static vi
|
|
|
193
193
|
atlas serve # ATLAS_STORE says where; listens on :8080
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
It serves the
|
|
196
|
+
It serves the client, the report and the project's specs and runs from all configured backends. It also
|
|
197
197
|
accepts uploads and announces new results on its event stream. Uploads go to `--data` by default;
|
|
198
198
|
use a named `--store project=s3://… --write project` to put uploads in a shared bucket, with publisher permissions.
|
|
199
199
|
|
|
@@ -204,7 +204,7 @@ use a named `--store project=s3://… --write project` to put uploads in a share
|
|
|
204
204
|
| `--write <id>` | `ATLAS_WRITE` | Backend that accepts uploads. Default `data`. |
|
|
205
205
|
| `--port <n>` | `ATLAS_PORT` or `PORT` | Default 8080. `0` picks a free one. |
|
|
206
206
|
| `--host <h>` | `ATLAS_HOST` or `HOST` | Default `127.0.0.1`. Listening beyond this machine requires tokens or `--insecure`. |
|
|
207
|
-
| `--site <dir>` | `ATLAS_SITE` | The built
|
|
207
|
+
| `--site <dir>` | `ATLAS_SITE` | The built client. Default: `./atlas-site`, else built once into `~/.cache/atlas/site`. |
|
|
208
208
|
| `--outline-branch <b>` | `ATLAS_OUTLINE_BRANCH` | Which branch's specs the report is laid over. Default `main`, then `master`, then the newest. |
|
|
209
209
|
| `--refresh <seconds>` | `ATLAS_REFRESH_SECONDS` | How often the bucket is looked at for new layers. Default 10. |
|
|
210
210
|
| `--branch <name>` (repeatable) | `ATLAS_ONLY_BRANCH` | Only layers recorded on these branches take part. |
|
|
@@ -214,7 +214,7 @@ use a named `--store project=s3://… --write project` to put uploads in a share
|
|
|
214
214
|
### Run it
|
|
215
215
|
|
|
216
216
|
**Docker.** [`examples/deploy/Dockerfile`](https://github.com/crvouga/atlas/blob/main/examples/deploy/Dockerfile)
|
|
217
|
-
installs Atlas, builds the
|
|
217
|
+
installs Atlas, builds the client once (it is the same for every project: the server tells the page where its
|
|
218
218
|
data is) and starts the server, keeping results on the volume at `/data`:
|
|
219
219
|
|
|
220
220
|
```sh
|
|
@@ -223,7 +223,7 @@ docker run -p 8080:8080 -v atlas-data:/data -e ATLAS_TOKEN=$(openssl rand -hex 2
|
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
For a bucket as the system of record add `-e ATLAS_STORE=project=s3://my-bucket/atlas/my-project -e ATLAS_WRITE=project -e AWS_REGION=us-east-1`.
|
|
226
|
-
On ECS, EKS or EC2, attach
|
|
226
|
+
On ECS, EKS or EC2, attach the combined **both** policy (including `server/live/*` for producer leases) to the task, service account or
|
|
227
227
|
instance and set no keys.
|
|
228
228
|
|
|
229
229
|
**Try it on one machine.** [`examples/deploy/compose.yaml`](https://github.com/crvouga/atlas/blob/main/examples/deploy/compose.yaml)
|
|
@@ -234,7 +234,7 @@ Publish to `http://localhost:8080` from your project and open it.
|
|
|
234
234
|
one copy (the server is a singleton for the project), results in the bucket, readiness on `/readyz`, an IRSA
|
|
235
235
|
service account.
|
|
236
236
|
|
|
237
|
-
**Without a container.** `bun add -d @crvouga/atlas`, then `bunx atlas
|
|
237
|
+
**Without a container.** `bun add -d @crvouga/atlas`, then `bunx atlas client build --no-data --out
|
|
238
238
|
atlas-site` and `bunx atlas serve`. It runs on Bun and on Node 22.18 or later. Everything it can be told is in
|
|
239
239
|
[the Atlas server](server.md#configuration).
|
|
240
240
|
|
|
@@ -249,12 +249,12 @@ layer too. Pass `--insecure` only when that proxy handles sign-in.
|
|
|
249
249
|
|
|
250
250
|
| Path | |
|
|
251
251
|
| --- | --- |
|
|
252
|
-
| `/` | The
|
|
252
|
+
| `/` | The client. Its page carries the server's address book, so one build serves every project. |
|
|
253
253
|
| `/api/reports/evidence/effective` | The whole report in one request: only the layers that win a check, each with only the rows that win. ETag, gzip. |
|
|
254
254
|
| `/api/reports/evidence`, `/evidence/rows/<backend>/<id>`, `/outline`, `/outline/spec?path=` | History, one layer's checks, the specs. |
|
|
255
255
|
| `/api/reports/evidence/detail/<backend>/<id>?check=<id>&kind=<kind>` | Everything one result carries: its timeline, annotations, retries, attachments and extensions, and the run's own. Without `check`, only the run's. |
|
|
256
256
|
| `/api/reports/layers/<id>/blobs/<key>` | One file a layer carries (screenshot, video, trace…), served as untrusted bytes with range support, cached for good. |
|
|
257
|
-
| `/api/data/specs/…`, `/api/data/runs/…`, `/api/data/sources.json` | The specs and runs, laid out the way the
|
|
257
|
+
| `/api/data/specs/…`, `/api/data/runs/…`, `/api/data/sources.json` | The specs and runs, laid out the way the client reads a static host. |
|
|
258
258
|
| `/api/checks` | Upload a layer of checks with `POST`. |
|
|
259
259
|
| `/api/events` | Live report updates over server-sent events. |
|
|
260
260
|
| `/healthz` | The process is up. |
|
|
@@ -277,7 +277,7 @@ Narrow it by choice, per consumer, never by moving data:
|
|
|
277
277
|
| A pull request's gate that inherits what it did not re-run from `main` | `--branch "$PR_BRANCH" --branch main --ignore-modified` |
|
|
278
278
|
| A business dashboard of what is committed | `atlas serve --ignore-modified` |
|
|
279
279
|
| Everything, as the team lives it | nothing (the default) |
|
|
280
|
-
| One person's view of what leaving a source out changes | **Sources** in the
|
|
280
|
+
| One person's view of what leaving a source out changes | **Sources** in the client: leave out a branch, environment, version or layer |
|
|
281
281
|
|
|
282
282
|
## 6. Operating it
|
|
283
283
|
|
|
@@ -287,12 +287,12 @@ Narrow it by choice, per consumer, never by moving data:
|
|
|
287
287
|
they never need to move together.
|
|
288
288
|
- **Logs.** One JSON line per request to stdout. `/healthz` and `/readyz` are not logged. At start it
|
|
289
289
|
prints what it reads and any store it cannot reach.
|
|
290
|
-
- **A store goes down.** The server keeps serving what it has, names the store in the
|
|
290
|
+
- **A store goes down.** The server keeps serving what it has, names the store in the client's notices and
|
|
291
291
|
in `/readyz`, and recovers by itself. The gate fails closed: unknown is not a pass.
|
|
292
292
|
- **A layer cannot be read** (corrupt, or written by a newer Atlas): a notice names it. It is tried again
|
|
293
293
|
every five minutes.
|
|
294
|
-
- **Scaling.** One
|
|
295
|
-
report of 100,000 checks costs a
|
|
294
|
+
- **Scaling.** One Atlas node supports multiple project namespaces with independently loaded report indexes. It takes in an upload in milliseconds and a
|
|
295
|
+
report of 100,000 checks costs a client kilobytes per change ([performance](server.md#performance)). Memory
|
|
296
296
|
grows with the number of distinct checks, plus about a hundred bytes for each layer ever seen (to know it has
|
|
297
297
|
read it). More than one server on a backend works and shows the same report, but each hears of uploads to
|
|
298
298
|
another only at its next refresh.
|
|
@@ -318,13 +318,13 @@ latency per request, which the server hides by reading 64 at a time):
|
|
|
318
318
|
| Publish a layer of plain checks | One `HEAD` and two small `PUT`s. Two thousand layers published in under two seconds from one process, in parallel. |
|
|
319
319
|
| Server start | About 0.2 ms of CPU per layer, plus S3 latency ÷ 64 per layer: 2,000 layers in well under a second on a fast store, a few seconds on S3. Each layer is read once, ever. |
|
|
320
320
|
| Refresh with nothing new | One `LIST` request for the newest page of the catalog, whatever the history. |
|
|
321
|
-
| The report, to a
|
|
322
|
-
| The
|
|
323
|
-
| The
|
|
321
|
+
| The report, to a client | One request. 12,000 checks from 2,000 layers is 108 KB gzipped, built once per change. |
|
|
322
|
+
| The client asking again | `304 Not Modified` in under a millisecond. While the live stream is open it asks only when told, and then for only what changed. |
|
|
323
|
+
| The client in a browser | One request for the report, plus the spec index, outline and run index and the specs it opens. None per layer, whatever the history (measured in Chromium against a server reading S3). |
|
|
324
324
|
|
|
325
325
|
How: each layer is folded into the winning result of every check once and dropped unless it still holds a
|
|
326
326
|
winner, so work and memory follow the number of checks. Because the winner rule is a total order, folding in
|
|
327
|
-
any order is exact; a test proves the incremental report equals recomputing from every layer. The
|
|
327
|
+
any order is exact; a test proves the incremental report equals recomputing from every layer. The client
|
|
328
328
|
reads that compact report in one request and only falls back to reading every layer when you ask it to
|
|
329
329
|
leave a source out (which needs the history).
|
|
330
330
|
|
|
@@ -341,5 +341,5 @@ leave a source out (which needs the history).
|
|
|
341
341
|
| The server says `listening … no backend can be read yet` | The bucket is unreachable from the server. It retries; `/readyz` stays 503 until it can read. |
|
|
342
342
|
| A new layer does not show | Uploaded through the server it is there at once: check the token (`401`/`403` say so). Written straight to a bucket, wait one refresh interval (10 s). Check `atlas report status --store <url>` sees it. |
|
|
343
343
|
| Everything is red because of one laptop | By design the newest result wins. Gate on `--ignore-modified` and a branch ([above](#5-choosing-what-takes-part)). |
|
|
344
|
-
| Specs are empty in the
|
|
344
|
+
| Specs are empty in the client | Nothing published with `--specs` yet, or only from a branch other than `main`: set `--outline-branch`. |
|
|
345
345
|
| `Run … is not shared: its privacy scan did not pass` | `--upload-run` refuses runs whose scan found real data. Fix what it found and run again. |
|
package/docs/dogfooding.md
CHANGED
|
@@ -51,7 +51,7 @@ and nothing runs just because the package is installed.
|
|
|
51
51
|
| `@crvouga/atlas/reports/<module>` | Publishing, storing, combining, gating and serving test evidence for specs of any kind. |
|
|
52
52
|
|
|
53
53
|
It also installs two commands: `atlas` (report, lint, plan, run, merge, export) and
|
|
54
|
-
`atlas
|
|
54
|
+
`atlas client` (browse specs, runs and reports).
|
|
55
55
|
|
|
56
56
|
`playwright` and `detox` are optional peers: install the one your driver uses, as above. The
|
|
57
57
|
documentation ships in the package, matching the version you installed: the README of each part at
|
|
@@ -151,7 +151,7 @@ Do this once per project. Check first that the package is on npm:
|
|
|
151
151
|
| `@crvouga/atlas-schema` | `@crvouga/atlas/schema` |
|
|
152
152
|
| `@crvouga/atlas-schema/schemas/<file>` | `@crvouga/atlas/schemas/<file>` |
|
|
153
153
|
| `@crvouga/atlas-reports/<module>` | `@crvouga/atlas/reports/<module>` |
|
|
154
|
-
| `@crvouga/atlas
|
|
154
|
+
| `@crvouga/atlas client` (the `atlas client` command) | installed with `@crvouga/atlas` |
|
|
155
155
|
|
|
156
156
|
`@crvouga/atlas`, `@crvouga/atlas/spec` and `@crvouga/atlas/signals` are unchanged. One pass
|
|
157
157
|
over a project's sources does the rest:
|
package/docs/extending.md
CHANGED
|
@@ -31,7 +31,7 @@ carry any of these. Every one is optional.
|
|
|
31
31
|
| `attempts[]` | Every try, when the check was retried. |
|
|
32
32
|
| `tags[]` | Free labels to filter by: `smoke`, `payments`. |
|
|
33
33
|
| `labels{}` | Facts about the result that are not its identity: `owner`, `severity`, `browser`, `build`. |
|
|
34
|
-
| `links[]` | Where to read more: the CI run, the issue, the trace
|
|
34
|
+
| `links[]` | Where to read more: the CI run, the issue, the trace client. |
|
|
35
35
|
| `extensions{}` | Structured data of your own or a tool's, under a name you own. |
|
|
36
36
|
| `measurements[]` | Numbers, with units and an allowance (already part of the contract). |
|
|
37
37
|
|
|
@@ -224,7 +224,7 @@ Annotation `type`: `note`, `warning`, `deprecation`, `issue`, `accessibility`, `
|
|
|
224
224
|
| Something that should be shown well | Ask for a renderer (below). |
|
|
225
225
|
| A field every tool should have | Open an issue. It is added as an optional field, and the rules above guarantee nothing breaks. |
|
|
226
226
|
|
|
227
|
-
**A renderer** is one entry in the
|
|
227
|
+
**A renderer** is one entry in the client's list (`artifactRenderers` in
|
|
228
228
|
`packages/visualizer/src/evidence/artifacts.tsx`): how to show an attachment of a media type. The first one that
|
|
229
229
|
matches wins:
|
|
230
230
|
|
|
@@ -236,7 +236,7 @@ registerArtifactRenderer({
|
|
|
236
236
|
});
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
-
The
|
|
239
|
+
The client is part of Atlas, so a renderer for a new media type is a contribution to Atlas: file an issue
|
|
240
240
|
([reporting issues](reporting-issues.md)) with a sample file. Until it lands, the attachment is still stored,
|
|
241
241
|
listed with its name, role, type and size, and offered for download, and everything else about the result is
|
|
242
242
|
unaffected.
|
package/docs/integrating.md
CHANGED
|
@@ -28,7 +28,7 @@ integrate. To run it for a whole team, with a bucket and one node everyone looks
|
|
|
28
28
|
```
|
|
29
29
|
node 1: suite A, ios ──▶ layer ─┐
|
|
30
30
|
node 2: suite A, android ──▶ layer ─┤ ┌──▶ atlas report status (gate: exit 1 on failure)
|
|
31
|
-
node 3: suite B, shard 1 ──▶ layer ─┼──▶ backends ──▶ combine ──┼──▶ the
|
|
31
|
+
node 3: suite B, shard 1 ──▶ layer ─┼──▶ backends ──▶ combine ──┼──▶ the client (browse it)
|
|
32
32
|
node 4: suite B, shard 2 ──▶ layer ─┤ └──▶ your own code (combineBackends)
|
|
33
33
|
a laptop, one scenario ──▶ layer ─┘
|
|
34
34
|
```
|
|
@@ -394,7 +394,7 @@ unknown is not a pass.
|
|
|
394
394
|
|
|
395
395
|
## 6. Browse it
|
|
396
396
|
|
|
397
|
-
The
|
|
397
|
+
The Atlas client lays your stores over your specs directory. Add a module to your project:
|
|
398
398
|
|
|
399
399
|
```ts
|
|
400
400
|
// atlas.reports.ts
|
|
@@ -404,14 +404,14 @@ export const reportsDevFetch = reportsHost({ specs: './specs', stores: ['./atlas
|
|
|
404
404
|
```
|
|
405
405
|
|
|
406
406
|
```sh
|
|
407
|
-
ATLAS_REPORTS_MODULE="$(pwd)/atlas.reports.ts" bunx atlas
|
|
407
|
+
ATLAS_REPORTS_MODULE="$(pwd)/atlas.reports.ts" bunx atlas client dev --specs ./specs
|
|
408
408
|
```
|
|
409
409
|
|
|
410
410
|
`reportsHost(options)` returns `(request: Request, repoRoot: string) => Promise<Response>`, the
|
|
411
|
-
function the
|
|
412
|
-
against `repoRoot`, which the
|
|
411
|
+
function the client calls for every `/api/reports/…` request. `specs` and directory `stores` resolve
|
|
412
|
+
against `repoRoot`, which the client sets to where you ran the command (`INIT_CWD`), not against the
|
|
413
413
|
module. A wrong path comes back as a JSON error that names it. A `stores` entry that is not a string is
|
|
414
|
-
a backend of your own: `{ id, label?, store }`. You can call it yourself to look at what the
|
|
414
|
+
a backend of your own: `{ id, label?, store }`. You can call it yourself to look at what the client
|
|
415
415
|
sees:
|
|
416
416
|
|
|
417
417
|
```ts
|
|
@@ -424,11 +424,11 @@ const page = await (await reportsDevFetch(new Request('http://x/api/reports/evid
|
|
|
424
424
|
|
|
425
425
|
| Route (after `/api/reports`) | Returns |
|
|
426
426
|
| --- | --- |
|
|
427
|
-
| `/evidence?cursor=` | A page of layer headers, the backends and any notices. The
|
|
427
|
+
| `/evidence?cursor=` | A page of layer headers, the backends and any notices. The client combines them with the specs. |
|
|
428
428
|
| `/evidence/rows/:backend/:id` | One layer's checks. |
|
|
429
429
|
| `/outline`, `/outline/spec?path=` | The specs directory as an outline, and one spec's text. |
|
|
430
430
|
|
|
431
|
-
The combined result of each spec is computed in the
|
|
431
|
+
The combined result of each spec is computed in the client. To get it without a browser, use
|
|
432
432
|
`atlas report status --format json`: its `specs` array has each spec's counts per environment.
|
|
433
433
|
|
|
434
434
|
Specs are the files of the specs directory (`.feature` as Gherkin, `.qnt` as Quint, `.dmn`, `.als`,
|
|
@@ -469,7 +469,7 @@ An integration is complete when each of these holds. Run them and keep the outpu
|
|
|
469
469
|
| `X is unavailable` | A backend is down. The rest were combined; fix it or leave it out. |
|
|
470
470
|
| `check id "…" appears twice` | Your suite emits two checks with one name. Make the names unique. Importers number repeats as `#2`. |
|
|
471
471
|
| `Potentially outdated: spec changed` | The spec changed since the layer was recorded. Advisory: the recorded outcome still counts. Re-run to refresh. |
|
|
472
|
-
|
|
|
472
|
+
| Client shows `No report store is configured` | `ATLAS_REPORTS_MODULE` is unset, or `stores` is empty. |
|
|
473
473
|
|
|
474
474
|
## Rules
|
|
475
475
|
|
package/docs/reference/cli.md
CHANGED
|
@@ -16,8 +16,9 @@ atlas — executable statecharts
|
|
|
16
16
|
[--no-retry] [--output <dir>]
|
|
17
17
|
atlas merge [--config atlas.config.ts] [--output <dir>] <run-dir> <run-dir>...
|
|
18
18
|
atlas export --specs <dir> --format scxml|mermaid|markdown|json [--out <file>]
|
|
19
|
-
atlas
|
|
20
|
-
atlas
|
|
19
|
+
atlas server [--data <dir>] [--port 8080] [--store <url>]... one Atlas node: API, projects and client
|
|
20
|
+
atlas client build [--no-data] [--out <dir>] build the project-agnostic Atlas client
|
|
21
|
+
atlas dev [--port 5180] develop the Atlas server and client on one host
|
|
21
22
|
atlas init [--server <url>] [--write] integrate this project: detect its suites, print the plan (atlas init --help)
|
|
22
23
|
atlas report publish|stream|status|merge|doctor|policy … layered evidence for any kind of spec (atlas report --help)
|
|
23
24
|
|
|
@@ -57,7 +58,7 @@ atlas serve: the Atlas server
|
|
|
57
58
|
[--outline-branch <b>] [--branch <name>]... [--ignore-modified] [--refresh <seconds>]
|
|
58
59
|
[--anonymous-read] [--insecure] [--no-log]
|
|
59
60
|
|
|
60
|
-
One process serves the
|
|
61
|
+
One process serves the client and the API. Test results are uploaded to it from anywhere (laptops, CI) and it
|
|
61
62
|
shows everyone the combined report, live. It keeps its data in --data (default ./atlas-data) and can read more
|
|
62
63
|
backends (--store): S3, other servers, folders. Uploads go to the data folder unless --write names another.
|
|
63
64
|
|
|
@@ -68,12 +69,12 @@ atlas serve: the Atlas server
|
|
|
68
69
|
## atlas dev
|
|
69
70
|
|
|
70
71
|
```text
|
|
71
|
-
atlas dev: the Atlas server and the
|
|
72
|
+
atlas dev: the Atlas server and the client, for working on either
|
|
72
73
|
|
|
73
74
|
atlas dev [--port 5180] [--host 127.0.0.1] [--data <dir>] [--store <url|id=url>]... [--config atlas.server.json]
|
|
74
75
|
|
|
75
|
-
Starts the server (API only, on a port of its own) and the
|
|
76
|
-
browser talks to one host and a change to the
|
|
76
|
+
Starts the server (API only, on a port of its own) and the client's dev server, which proxies /api to it, so the
|
|
77
|
+
browser talks to one host and a change to the client shows at once. In production `atlas serve` does both from
|
|
77
78
|
one process.
|
|
78
79
|
```
|
|
79
80
|
|
|
@@ -101,7 +102,8 @@ atlas report — layered evidence for any kind of spec
|
|
|
101
102
|
atlas report policy --store s3://bucket/prefix [--role publisher|reader|both]
|
|
102
103
|
|
|
103
104
|
A store is a directory or s3://bucket/prefix (add ?region=…&endpoint=… for R2, MinIO and the
|
|
104
|
-
rest).
|
|
105
|
+
rest). Select a project on an Atlas node with --project <slug> or ATLAS_PROJECT. This scopes specs, reports and recordings together.
|
|
106
|
+
When no --store is given, ATLAS_STORE is used. Keys never go in the URL: set
|
|
105
107
|
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, or sign in with `aws sso login`, or use a role.
|
|
106
108
|
|
|
107
109
|
publish Write one immutable layer: the checks of one producer, on one environment. Safe to repeat.
|
package/docs/reference/core.md
CHANGED
|
@@ -49,6 +49,8 @@ See docs/statecharts.md
|
|
|
49
49
|
export type AtlasConfig<C = unknown> = {
|
|
50
50
|
/** The directory holding the charts (`*.scxml`, `*.machine.{json,yaml}`) and `journeys.yaml`. */
|
|
51
51
|
specs: string;
|
|
52
|
+
/** Live reporting endpoint. Defaults to ATLAS_SERVER_URL when set; false disables streaming. */
|
|
53
|
+
reporting?: LiveReporterOptions | false;
|
|
52
54
|
/** The chart to run; the root chart when omitted. */
|
|
53
55
|
chart?: string;
|
|
54
56
|
/** Where generated paths start (an XState state value); the initial state when omitted. */
|
|
@@ -149,6 +151,8 @@ not thrown.
|
|
|
149
151
|
export type RunCommandOptions = PrepareOptions & {
|
|
150
152
|
/** `fast` when omitted. */
|
|
151
153
|
mode?: RunMode;
|
|
154
|
+
/** Override live reporting. Defaults to config.reporting or ATLAS_SERVER_URL. */
|
|
155
|
+
reporting?: LiveReporterOptions | false;
|
|
152
156
|
/** Only these paths, by id. */
|
|
153
157
|
paths?: string[];
|
|
154
158
|
/** Only paths that start from these seeds ("start" for paths that `setup` starts). */
|
|
@@ -1071,6 +1075,8 @@ export type RunOptions<C> = RunContext<C> & {
|
|
|
1071
1075
|
reuse?: { runDir: string; outcomes: Record<string, PathOutcome> };
|
|
1072
1076
|
/** Receives progress lines; writes to stdout when omitted. */
|
|
1073
1077
|
log?: (line: string) => void;
|
|
1078
|
+
/** Receives privacy-checked manifest snapshots as paths and steps change. */
|
|
1079
|
+
onProgress?: (manifest: Manifest) => void;
|
|
1074
1080
|
};
|
|
1075
1081
|
```
|
|
1076
1082
|
|