@crvouga/atlas 0.5.0 → 0.6.1
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 +12 -0
- package/README.md +51 -16
- 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 +24 -16
- package/dist/reports/src/live-reporter.d.ts +34 -0
- package/dist/reports/src/live-reporter.js +122 -0
- package/dist/reports/src/package-managers.d.ts +35 -0
- package/dist/reports/src/package-managers.js +23 -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/cli-results.d.ts +233 -2
- package/dist/schema/src/cli-results.js +14 -0
- 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/llms.js +17 -8
- 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/agents.md +166 -15
- package/docs/deploying.md +24 -20
- package/docs/dogfooding.md +7 -3
- package/docs/extending.md +3 -3
- package/docs/integrating.md +19 -11
- package/docs/quickstart.md +81 -0
- 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 +84 -1
- package/docs/reference/runners.md +169 -0
- package/docs/reference/schema.md +120 -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 +155 -25
- package/docs/statecharts.md +22 -20
- package/llms-full.txt +1079 -126
- package/llms.txt +7 -4
- package/package.json +33 -9
- package/packages/core/README.md +3 -1
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog: @crvouga/atlas
|
|
2
2
|
|
|
3
|
+
## 0.6.1 (2026-10-15)
|
|
4
|
+
|
|
5
|
+
### Fixes and improvements
|
|
6
|
+
|
|
7
|
+
- make consumer integration reproducible (#9) ([bfd4e0c](https://github.com/crvouga/atlas/commit/bfd4e0c1c804d1f11fce3299e609af86c50e4732))
|
|
8
|
+
|
|
9
|
+
## 0.6.0 (2026-10-14)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- add project-scoped live test reporting (#8) ([70936ab](https://github.com/crvouga/atlas/commit/70936ab5b2c6bec5e50b458fc1c3030a3f20bc55))
|
|
14
|
+
|
|
3
15
|
## 0.5.0 (2026-10-14)
|
|
4
16
|
|
|
5
17
|
### Features
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ backends, and tells you in one command whether the product still does what it wa
|
|
|
7
7
|
|
|
8
8
|
- **Any spec, any runner.** Anything that ends in "this check passed or failed" publishes a *layer*:
|
|
9
9
|
from a JUnit or CTRF file, a ten-line script, or code. No runner and no notation is privileged.
|
|
10
|
-
- **Layered
|
|
10
|
+
- **Layered within each project.** A report is computed from layers, not stored as "the run". Partial
|
|
11
11
|
runs on separate nodes, branches and devices are how a report is built, and they compose without
|
|
12
12
|
coordinating: the same layers give the same report in any order, split any way.
|
|
13
13
|
- **Open evidence.** A result carries whatever your framework produces: screenshots, videos, traces, logs, DOM
|
|
@@ -16,9 +16,9 @@ backends, and tells you in one command whether the product still does what it wa
|
|
|
16
16
|
Gherkin, Quint, DMN, Alloy, OpenSLO); evidence stays open.
|
|
17
17
|
- **Open standards, stable schemas.** JUnit XML and CTRF in, JSON Schema 2020-12 contracts, a
|
|
18
18
|
versioned layer format with a golden fixture, and an `ObjectStore` interface any bucket fits.
|
|
19
|
-
- **One
|
|
19
|
+
- **One node, many projects.** `atlas server` runs a Bun API and serves the Atlas client from the same host. Reports, specs, recordings and live progress are partitioned by project slug.
|
|
20
20
|
Every laptop and CI job sends results to it as they finish (`atlas report publish`, or `atlas report stream` while
|
|
21
|
-
a suite runs), it keeps them on its own disk (or in S3, R2, MinIO), and every
|
|
21
|
+
a suite runs), it keeps them on its own disk (or in S3, R2, MinIO), and every client sees the combined report
|
|
22
22
|
update live, receiving only what changed ([the server](docs/server.md), [deploying](docs/deploying.md)).
|
|
23
23
|
- **Fast and fail-closed.** Publishing is two small writes. A thousand layers of a hundred checks
|
|
24
24
|
combine in about a second. An unreadable layer, a downed backend or a check still running is never
|
|
@@ -28,7 +28,7 @@ backends, and tells you in one command whether the product still does what it wa
|
|
|
28
28
|
|
|
29
29
|
```
|
|
30
30
|
any suite ──▶ layer ─┐ atlas report status exit 1 unless it passes
|
|
31
|
-
any node ──▶ layer ─┼─▶ backends ─▶ combine ─▶ the
|
|
31
|
+
any node ──▶ layer ─┼─▶ backends ─▶ combine ─▶ the Atlas client browse it beside the specs
|
|
32
32
|
any branch ──▶ layer ─┘ (directories, buckets, …) combineBackends() your own code
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -40,6 +40,34 @@ backends, and tells you in one command whether the product still does what it wa
|
|
|
40
40
|
> [using Atlas from another project](docs/dogfooding.md) and [reporting issues](docs/reporting-issues.md).
|
|
41
41
|
> Releases are automatic: see [releasing](https://github.com/crvouga/atlas/blob/main/docs/releasing.md).
|
|
42
42
|
|
|
43
|
+
## Given this repo link to integrate Atlas?
|
|
44
|
+
|
|
45
|
+
Start with **[the agent integration playbook](docs/agents.md#given-only-this-repository-link)** in your own
|
|
46
|
+
project. It covers choosing an integration, installing with your package manager, finding docs for your installed
|
|
47
|
+
version, running without a server, connecting real suites, and proving completion. **[Verify Atlas locally](docs/quickstart.md)**
|
|
48
|
+
is a copyable walkthrough with expected JSON and exit codes; it needs no credentials or existing test suite.
|
|
49
|
+
The machine-readable index is [llms.txt](llms.txt), and [llms-full.txt](llms-full.txt) contains the guides in one file.
|
|
50
|
+
|
|
51
|
+
## Run this repository's example
|
|
52
|
+
|
|
53
|
+
For contributors working in an Atlas checkout with dependencies installed (`bun install --frozen-lockfile`).
|
|
54
|
+
Consumer projects install the npm package instead; they do not need to clone or build this repository.
|
|
55
|
+
|
|
56
|
+
Use two terminals:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
bun atlas # starts the project-agnostic Atlas node at http://localhost:5180
|
|
60
|
+
bun example:run # runs tests and streams results, current steps and progress as they execute
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The server starts independently with data in `./atlas-data`; it never loads example specs or reports. `bun example:run` publishes the Todo example under `todo-app`. Select a project using the top-right picker, then use the spec dropdown breadcrumbs to navigate. Other projects set `ATLAS_SERVER_URL`, `ATLAS_PROJECT` (for example `checkout-service`) and their publish token. Every uploader talks to the node; storage credentials stay on the server.
|
|
64
|
+
|
|
65
|
+
Every Gherkin scenario runs, including sharing and renaming. Test reports show running spinners,
|
|
66
|
+
live step logs and a Running status filter. Producers send heartbeats every
|
|
67
|
+
5 seconds. A run becomes interrupted after 30 seconds without a heartbeat, or 15 minutes without progress;
|
|
68
|
+
configure `ATLAS_RUN_TIMEOUT_SECONDS` and `ATLAS_RUN_STALL_SECONDS` (0 disables the progress timeout),
|
|
69
|
+
or edit these settings in the client. An interruption never turns missing outcomes into passes.
|
|
70
|
+
|
|
43
71
|
## For AI agents
|
|
44
72
|
|
|
45
73
|
Atlas is built to be integrated and operated by agents, with nothing to click and nothing to guess.
|
|
@@ -47,20 +75,21 @@ Atlas is built to be integrated and operated by agents, with nothing to click an
|
|
|
47
75
|
- **`atlas init`** detects a project's test runners, specs and CI and prints a plan (`--format json`) of the exact commands to
|
|
48
76
|
run each suite and send its results; `--write` creates the CI workflow and an `AGENTS.md` section. Running it twice
|
|
49
77
|
changes nothing.
|
|
50
|
-
- **
|
|
51
|
-
|
|
78
|
+
- **Init and report commands print JSON on request** (`--format json`) with published JSON Schemas and consistent
|
|
79
|
+
exit codes (`0` worked, `1` the answer is no, `2` the command was wrong). Reapplying init or publishing identical evidence is safe.
|
|
52
80
|
- **The server describes itself:** `GET /api/openapi.json` (OpenAPI 3.1) and `GET /llms.txt` (a guide for an agent given only
|
|
53
81
|
the address) need no token; errors are RFC 9457 problem documents that say what to do.
|
|
54
82
|
- **Documentation is generated from the code beside it:** `llms.txt` / `llms-full.txt` (also in the package), the TypeScript
|
|
55
83
|
reference in [`docs/reference/`](docs/reference/server.md), the CLI and HTTP references, and the settings tables; tests fail when
|
|
56
84
|
any of it drifts.
|
|
57
85
|
|
|
58
|
-
Start with **[Atlas for AI agents](docs/agents.md)**.
|
|
86
|
+
Start with **[Atlas for AI agents](docs/agents.md)**. A [copyable integration brief](docs/agents.md#an-integration-brief-you-can-give-an-agent)
|
|
87
|
+
lets a user hand an agent this repository URL and a concrete definition of done.
|
|
59
88
|
|
|
60
89
|
## Integrate in five minutes
|
|
61
90
|
|
|
62
91
|
```sh
|
|
63
|
-
bun add -d @crvouga/atlas # or npm install -D / pnpm add -D
|
|
92
|
+
bun add -d @crvouga/atlas # or npm install -D / pnpm add -D
|
|
64
93
|
|
|
65
94
|
# 1. Each suite publishes a layer of what it ran: a JUnit or CTRF file, an Atlas run, or a checks file.
|
|
66
95
|
bunx atlas report publish --store atlas-reports --producer web-e2e --junit out.xml --specs ./specs --environment desktop-web
|
|
@@ -88,7 +117,11 @@ bunx atlas report status --store all --specs ./specs --require-environments desk
|
|
|
88
117
|
## One package
|
|
89
118
|
|
|
90
119
|
Atlas is a single npm package. Each part is its own import path, so a bundler keeps only what you
|
|
91
|
-
import.
|
|
120
|
+
import. Use your project's package manager:
|
|
121
|
+
|
|
122
|
+
<!-- generated:runtime-requirements -->
|
|
123
|
+
Supported runtimes (from `package.json`): **node `>=22.18.0`** or **bun `>=1.2.0`**. Node consumers do not need Bun unless they use the Bun WebView driver.
|
|
124
|
+
<!-- /generated:runtime-requirements -->
|
|
92
125
|
|
|
93
126
|
```sh
|
|
94
127
|
bun add -d @crvouga/atlas
|
|
@@ -105,10 +138,10 @@ bun add -d @crvouga/atlas
|
|
|
105
138
|
| [`@crvouga/atlas/webdriver`](packages/webdriver) | W3C WebDriver driver with no dependencies: Safari, Chrome, Firefox and Edge, and native iOS and Android apps through Appium. |
|
|
106
139
|
| [`@crvouga/atlas/detox`](packages/detox) | Detox driver for iOS and Android apps, inside a Detox test run. |
|
|
107
140
|
| [`@crvouga/atlas/vitest`](packages/vitest) | Run a config as a test suite in Vitest, `bun test` or any Jest-style runner, one test per planned path. |
|
|
108
|
-
| [`atlas
|
|
141
|
+
| [`atlas client`](packages/visualizer) (command) | Browse specs, charts and layered reports: every spec with its result per environment, every state with its screenshots, every transition with its clip. |
|
|
109
142
|
|
|
110
143
|
Two commands install with it: `atlas` (`report`, `lint`, `plan`, `run`, `merge`, `export`) and
|
|
111
|
-
`atlas
|
|
144
|
+
`atlas client`.
|
|
112
145
|
|
|
113
146
|
## Reports for any spec
|
|
114
147
|
|
|
@@ -147,10 +180,10 @@ has results. `--format json` is a [stable schema](packages/schema/schemas/report
|
|
|
147
180
|
means the report does not pass; 2 means the command itself was wrong (a missing store directory, a bad
|
|
148
181
|
flag).
|
|
149
182
|
|
|
150
|
-
**The server** is `atlas serve`: one process that serves the
|
|
151
|
-
every
|
|
183
|
+
**The server** is `atlas serve`: one process that serves the client, takes in results and streams the report to
|
|
184
|
+
every client. Point `ATLAS_STORE` at it and every developer's laptop and every CI job sends results with the same
|
|
152
185
|
commands that write to a folder; everyone then looks at one report, live. Results are kept in its data folder
|
|
153
|
-
unless you choose a bucket. `atlas dev` runs the same server with the
|
|
186
|
+
unless you choose a bucket. `atlas dev` runs the same server with the client's dev server in front, so
|
|
154
187
|
development is one host too. See [the server](docs/server.md) and
|
|
155
188
|
[running Atlas for a project](docs/deploying.md).
|
|
156
189
|
|
|
@@ -163,7 +196,7 @@ my-suite --json | atlas report stream --producer e2e --environment ios # or as
|
|
|
163
196
|
atlas report status --branch main --ignore-modified # the gate, answered by the server
|
|
164
197
|
```
|
|
165
198
|
|
|
166
|
-
**The
|
|
199
|
+
**The client** shows each spec (Gherkin, Quint, DMN, Alloy, OpenSLO, and the rest by file type) with
|
|
167
200
|
its combined result per environment and the layer behind it. See
|
|
168
201
|
[integrating Atlas](docs/integrating.md#6-browse-it).
|
|
169
202
|
|
|
@@ -190,7 +223,7 @@ understand, or a JSON Schema it publishes.
|
|
|
190
223
|
| [JUnit XML](https://github.com/testmoapp/junitxml) | Read: any test runner's results become a layer. Written: `junit.xml`, one test case per statechart path. |
|
|
191
224
|
| [CTRF](https://ctrf.io) | Read: any CTRF report becomes a layer. Written: `ctrf.json` for each statechart run. |
|
|
192
225
|
| [JSON Schema 2020-12](https://json-schema.org/draft/2020-12) | The contract: layers, check files, report status, run manifests, journeys, state nodes, timelines and the runs index (`@crvouga/atlas/schemas/*.schema.json`) |
|
|
193
|
-
| [Gherkin](https://cucumber.io/docs/gherkin/), [Quint](https://quint-lang.org), [DMN](https://www.omg.org/spec/DMN), [Alloy](https://alloytools.org), [OpenSLO](https://openslo.com) | Spec kinds the
|
|
226
|
+
| [Gherkin](https://cucumber.io/docs/gherkin/), [Quint](https://quint-lang.org), [DMN](https://www.omg.org/spec/DMN), [Alloy](https://alloytools.org), [OpenSLO](https://openslo.com) | Spec kinds the client lays results over, each in its own tab. Any other file is a spec of its own type. |
|
|
194
227
|
| [W3C SCXML](https://www.w3.org/TR/scxml/) | Statecharts (`*.scxml`), with business metadata in an `atlas` XML namespace |
|
|
195
228
|
| [XState v5](https://stately.ai/docs/machines) machine config | Statecharts (`*.machine.json`, `*.machine.yaml`): the JSON shape Stately Studio imports and exports |
|
|
196
229
|
| [WebVTT](https://www.w3.org/TR/webvtt1/) | Captions for every clip: each tap, keystroke, system call and screen change |
|
|
@@ -228,3 +261,5 @@ in **[Executable statecharts](docs/statecharts.md)**, which starts with a runnab
|
|
|
228
261
|
## License
|
|
229
262
|
|
|
230
263
|
[MIT](LICENSE)
|
|
264
|
+
|
|
265
|
+
Native [CodeceptJS, Mocha, Jest and Node test reporters](docs/runners.md) upload directly to the project server. Open **Settings** in the client to configure storage and report settings for the next restart.
|
package/dist/core/src/cli.d.ts
CHANGED
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
* @see docs/reference/cli.md
|
|
10
10
|
*/
|
|
11
11
|
/** The text `atlas --help` prints: every command, and the flags of the statechart commands. */
|
|
12
|
-
export declare const USAGE = "atlas \u2014 executable statecharts\n\n atlas lint [--config atlas.config.ts | --specs <dir>] [--require-implementations]\n atlas plan [--config atlas.config.ts] [--state <name>]\n atlas run [--config atlas.config.ts] [--mode fast|showcase] [--workers n] [--state <name>]\n [--seed <name>]... [--paths id,id] [--shard i/n] [--rerun-failed | --reuse <run-dir>]\n [--no-retry] [--output <dir>]\n atlas merge [--config atlas.config.ts] [--output <dir>] <run-dir> <run-dir>...\n atlas export --specs <dir> --format scxml|mermaid|markdown|json [--out <file>]\n atlas
|
|
12
|
+
export declare const USAGE = "atlas \u2014 executable statecharts\n\n atlas lint [--config atlas.config.ts | --specs <dir>] [--require-implementations]\n atlas plan [--config atlas.config.ts] [--state <name>]\n atlas run [--config atlas.config.ts] [--mode fast|showcase] [--workers n] [--state <name>]\n [--seed <name>]... [--paths id,id] [--shard i/n] [--rerun-failed | --reuse <run-dir>]\n [--no-retry] [--output <dir>]\n atlas merge [--config atlas.config.ts] [--output <dir>] <run-dir> <run-dir>...\n atlas export --specs <dir> --format scxml|mermaid|markdown|json [--out <file>]\n atlas server [--data <dir>] [--port 8080] [--store <url>]... one Atlas node: API, projects and client\n atlas client build [--no-data] [--out <dir>] build the project-agnostic Atlas client\n atlas dev [--port 5180] develop the Atlas server and client on one host\n atlas init [--server <url>] [--write] integrate this project: detect its suites, print the plan (atlas init --help)\n atlas report publish|stream|status|merge|doctor|policy \u2026 layered evidence for any kind of spec (atlas report --help)\n\n --workers path attempts that run at once (default: one per CPU, capped by the driver)\n --seed only paths that start from this seed (\"start\" for paths setup starts); repeatable\n --shard 2/4 run the second quarter of the plan; merge the shards' runs afterwards\n --rerun-failed keep the newest run's passed paths and run only the rest\n";
|
|
13
13
|
/** Runs `atlas` with `argv` (default: the process arguments) and returns the exit code. Throws for a command that is wrong, which the `atlas` binary reports as a message and exit code 2. */
|
|
14
14
|
export declare function main(argv?: string[]): Promise<number>;
|
package/dist/core/src/cli.js
CHANGED
|
@@ -28,8 +28,9 @@ export const USAGE = `atlas — executable statecharts
|
|
|
28
28
|
[--no-retry] [--output <dir>]
|
|
29
29
|
atlas merge [--config atlas.config.ts] [--output <dir>] <run-dir> <run-dir>...
|
|
30
30
|
atlas export --specs <dir> --format scxml|mermaid|markdown|json [--out <file>]
|
|
31
|
-
atlas
|
|
32
|
-
atlas
|
|
31
|
+
atlas server [--data <dir>] [--port 8080] [--store <url>]... one Atlas node: API, projects and client
|
|
32
|
+
atlas client build [--no-data] [--out <dir>] build the project-agnostic Atlas client
|
|
33
|
+
atlas dev [--port 5180] develop the Atlas server and client on one host
|
|
33
34
|
atlas init [--server <url>] [--write] integrate this project: detect its suites, print the plan (atlas init --help)
|
|
34
35
|
atlas report publish|stream|status|merge|doctor|policy … layered evidence for any kind of spec (atlas report --help)
|
|
35
36
|
|
|
@@ -88,10 +89,14 @@ async function loadConfig(file) {
|
|
|
88
89
|
export async function main(argv = process.argv.slice(2)) {
|
|
89
90
|
const [command] = argv;
|
|
90
91
|
// Loaded on demand: lint, plan and run do not pay for the reports.
|
|
91
|
-
if (command === 'serve') {
|
|
92
|
+
if (command === 'serve' || command === 'server') {
|
|
92
93
|
const { serveMain } = await import('../../server/src/main.js');
|
|
93
94
|
return serveMain(argv.slice(1), { out: text => process.stdout.write(text) });
|
|
94
95
|
}
|
|
96
|
+
if (command === 'client') {
|
|
97
|
+
const { clientMain } = await import('../../server/src/site.js');
|
|
98
|
+
return clientMain(argv.slice(1));
|
|
99
|
+
}
|
|
95
100
|
if (command === 'dev') {
|
|
96
101
|
const { devMain } = await import('../../server/src/dev.js');
|
|
97
102
|
return devMain(argv.slice(1), { out: text => process.stdout.write(text) });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LiveReporterOptions } from '../../reports/src/live-reporter.js';
|
|
1
2
|
import type { StateValue } from './graph.js';
|
|
2
3
|
import type { Shard } from './run/execute.js';
|
|
3
4
|
import type { Driver, EventMatcher, EventSource, Implementation, PrivacyRules, RunMode } from './run/types.js';
|
|
@@ -8,6 +9,8 @@ import type { Driver, EventMatcher, EventSource, Implementation, PrivacyRules, R
|
|
|
8
9
|
export type AtlasConfig<C = unknown> = {
|
|
9
10
|
/** The directory holding the charts (`*.scxml`, `*.machine.{json,yaml}`) and `journeys.yaml`. */
|
|
10
11
|
specs: string;
|
|
12
|
+
/** Live reporting endpoint. Defaults to ATLAS_SERVER_URL when set; false disables streaming. */
|
|
13
|
+
reporting?: LiveReporterOptions | false;
|
|
11
14
|
/** The chart to run; the root chart when omitted. */
|
|
12
15
|
chart?: string;
|
|
13
16
|
/** Where generated paths start (an XState state value); the initial state when omitted. */
|
|
@@ -125,6 +128,8 @@ export declare function prepare<C>(config: AtlasConfig<C>, baseDirectory?: strin
|
|
|
125
128
|
export type RunCommandOptions = PrepareOptions & {
|
|
126
129
|
/** `fast` when omitted. */
|
|
127
130
|
mode?: RunMode;
|
|
131
|
+
/** Override live reporting. Defaults to config.reporting or ATLAS_SERVER_URL. */
|
|
132
|
+
reporting?: LiveReporterOptions | false;
|
|
128
133
|
/** Only these paths, by id. */
|
|
129
134
|
paths?: string[];
|
|
130
135
|
/** Only paths that start from these seeds ("start" for paths that `setup` starts). */
|
|
@@ -260,6 +265,7 @@ export declare function runConfig<C>(config: AtlasConfig<C>, options?: RunComman
|
|
|
260
265
|
key: string;
|
|
261
266
|
name: string;
|
|
262
267
|
kind: import('./plan.js').PlannedPath["kind"];
|
|
268
|
+
progress?: "queued" | "running" | "complete";
|
|
263
269
|
status: import('./index.js').Status;
|
|
264
270
|
attempts: {
|
|
265
271
|
status: "passed" | "failed";
|
|
@@ -458,6 +464,7 @@ export declare function mergeConfigRuns<C>(config: AtlasConfig<C>, runDirs: stri
|
|
|
458
464
|
key: string;
|
|
459
465
|
name: string;
|
|
460
466
|
kind: import('./plan.js').PlannedPath["kind"];
|
|
467
|
+
progress?: "queued" | "running" | "complete";
|
|
461
468
|
status: import('./index.js').Status;
|
|
462
469
|
attempts: {
|
|
463
470
|
status: "passed" | "failed";
|
package/dist/core/src/config.js
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { availableParallelism } from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
|
+
import { createLiveReporter } from '../../reports/src/live-reporter.js';
|
|
15
|
+
import { httpStore } from '../../reports/src/http-store.js';
|
|
16
|
+
import { publishChecks } from '../../reports/src/publish-checks.js';
|
|
17
|
+
import { publishRunBundle } from '../../reports/src/run-bundle.js';
|
|
18
|
+
import { checksFromManifest } from '../../reports/src/ingest.js';
|
|
14
19
|
import { lintBundle } from './lint.js';
|
|
15
20
|
import { chartScope, planChart } from './plan.js';
|
|
16
21
|
import { assertAllowedTarget, mergePrivacy } from './privacy.js';
|
|
@@ -129,20 +134,49 @@ export async function runConfig(config, options = {}, baseDirectory = process.cw
|
|
|
129
134
|
const reused = reuseDir ? readOutcomes(reuseDir) : null;
|
|
130
135
|
if (options.reuse && !reused)
|
|
131
136
|
throw new Error(`There is no earlier run to reuse${reuseDir ? ` at ${reuseDir}` : ''}`);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
137
|
+
const requestedReporting = options.reporting ?? config.reporting ?? (process.env.ATLAS_SERVER_URL ? { url: process.env.ATLAS_SERVER_URL, token: process.env.ATLAS_PUBLISH_TOKEN ?? process.env.ATLAS_TOKEN } : undefined);
|
|
138
|
+
const reporting = requestedReporting ? { ...requestedReporting, project: requestedReporting.project ?? process.env.ATLAS_PROJECT, token: requestedReporting.token ?? process.env.ATLAS_PUBLISH_TOKEN ?? process.env.ATLAS_TOKEN } : undefined;
|
|
139
|
+
let live;
|
|
140
|
+
const onProgress = reporting ? (manifest) => {
|
|
141
|
+
if (!manifest.privacy.passed)
|
|
142
|
+
return;
|
|
143
|
+
live ??= createLiveReporter({ producer: process.env.ATLAS_PRODUCER ?? 'atlas-statecharts', title: `${prepared.scope.chart} · ${options.mode ?? 'fast'}`,
|
|
144
|
+
plannedChecks: paths.length, environment: process.env.ATLAS_ENVIRONMENT ?? 'desktop-web', branch: manifest.run.branch ?? undefined, sha: manifest.run.commit ?? undefined }, reporting);
|
|
145
|
+
const ingested = checksFromManifest(manifest);
|
|
146
|
+
ingested.checks.forEach((check, index) => {
|
|
147
|
+
if (manifest.paths[index]?.progress === 'queued' && !manifest.run.finishedAt)
|
|
148
|
+
return;
|
|
149
|
+
live.check({ ...check, steps: check.steps?.slice(-20).map(step => ({ name: step.name, status: step.status })) });
|
|
150
|
+
});
|
|
151
|
+
} : undefined;
|
|
152
|
+
try {
|
|
153
|
+
const outcome = await runPaths({
|
|
154
|
+
...contextOf(config, prepared),
|
|
155
|
+
paths,
|
|
156
|
+
driver,
|
|
157
|
+
mode: options.mode ?? 'fast',
|
|
158
|
+
outputRoot,
|
|
159
|
+
...(options.retry === undefined ? {} : { retry: options.retry }),
|
|
160
|
+
...(config.stepTimeoutMs === undefined ? {} : { stepTimeoutMs: config.stepTimeoutMs }),
|
|
161
|
+
eventSources: config.eventSources ? await config.eventSources() : [],
|
|
162
|
+
workers: options.workers ?? config.workers ?? availableParallelism(),
|
|
163
|
+
...(options.shard ? { shard: options.shard } : {}),
|
|
164
|
+
...(reused && reuseDir ? { reuse: { runDir: reuseDir, outcomes: reused.outcomes } } : {}),
|
|
165
|
+
...(options.log ? { log: options.log } : {}), ...(onProgress ? { onProgress } : {})
|
|
166
|
+
});
|
|
167
|
+
await live?.finish(outcome.manifest.privacy.passed ? 'completed' : 'interrupted', outcome.manifest.privacy.passed ? undefined : 'Run failed its privacy scan; unsafe progress was withheld.');
|
|
168
|
+
if (reporting && outcome.manifest.privacy.passed) {
|
|
169
|
+
const store = httpStore({ url: reporting.url, project: reporting.project, token: reporting.token });
|
|
170
|
+
await publishChecks(store, { producer: process.env.ATLAS_PRODUCER ?? 'atlas-statecharts', environment: process.env.ATLAS_ENVIRONMENT ?? 'desktop-web',
|
|
171
|
+
branch: outcome.manifest.run.branch, sha: outcome.manifest.run.commit, recordedAtMs: Math.max(Date.now(), (live?.finishedAtMs ?? 0) + 1), checks: checksFromManifest(outcome.manifest).checks.map(check => ({ ...check, measurements: [...check.measurements] })) });
|
|
172
|
+
await publishRunBundle(store, outcome.runDir);
|
|
173
|
+
}
|
|
174
|
+
return outcome;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
await live?.finish('interrupted', 'The producer exited before completing the run.').catch(() => { });
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
146
180
|
}
|
|
147
181
|
/**
|
|
148
182
|
* Composes runs of a config (its shards, or a rerun and the run before it) into one new run
|
|
@@ -58,6 +58,8 @@ export type RunOptions<C> = RunContext<C> & {
|
|
|
58
58
|
};
|
|
59
59
|
/** Receives progress lines; writes to stdout when omitted. */
|
|
60
60
|
log?: (line: string) => void;
|
|
61
|
+
/** Receives privacy-checked manifest snapshots as paths and steps change. */
|
|
62
|
+
onProgress?: (manifest: Manifest) => void;
|
|
61
63
|
};
|
|
62
64
|
type StepResult = {
|
|
63
65
|
transition: string;
|
|
@@ -268,6 +270,7 @@ export declare function buildManifest<C>(context: RunContext<C>, paths: PlannedP
|
|
|
268
270
|
key: string;
|
|
269
271
|
name: string;
|
|
270
272
|
kind: PlannedPath["kind"];
|
|
273
|
+
progress?: "queued" | "running" | "complete";
|
|
271
274
|
status: Status;
|
|
272
275
|
attempts: Attempt[];
|
|
273
276
|
steps: StepResult[];
|
|
@@ -447,6 +450,7 @@ export declare function writeRun<C>(runDir: string, context: RunContext<C>, path
|
|
|
447
450
|
key: string;
|
|
448
451
|
name: string;
|
|
449
452
|
kind: PlannedPath["kind"];
|
|
453
|
+
progress?: "queued" | "running" | "complete";
|
|
450
454
|
status: Status;
|
|
451
455
|
attempts: Attempt[];
|
|
452
456
|
steps: StepResult[];
|
|
@@ -638,6 +642,7 @@ export declare function mergeRuns<C>(context: RunContext<C>, paths: PlannedPath[
|
|
|
638
642
|
key: string;
|
|
639
643
|
name: string;
|
|
640
644
|
kind: PlannedPath["kind"];
|
|
645
|
+
progress?: "queued" | "running" | "complete";
|
|
641
646
|
status: Status;
|
|
642
647
|
attempts: Attempt[];
|
|
643
648
|
steps: StepResult[];
|
|
@@ -832,6 +837,7 @@ export declare function runPaths<C>(options: RunOptions<C>): Promise<{
|
|
|
832
837
|
key: string;
|
|
833
838
|
name: string;
|
|
834
839
|
kind: PlannedPath["kind"];
|
|
840
|
+
progress?: "queued" | "running" | "complete";
|
|
835
841
|
status: Status;
|
|
836
842
|
attempts: Attempt[];
|
|
837
843
|
steps: StepResult[];
|
|
@@ -891,7 +891,8 @@ export async function runPaths(options) {
|
|
|
891
891
|
}
|
|
892
892
|
});
|
|
893
893
|
function publish() {
|
|
894
|
-
writeRun(runDir, options, options.paths, new Map([...outcomes, ...activeOutcomes]), runInfo(false));
|
|
894
|
+
const snapshot = writeRun(runDir, options, options.paths, new Map([...outcomes, ...activeOutcomes]), runInfo(false));
|
|
895
|
+
options.onProgress?.(snapshot);
|
|
895
896
|
}
|
|
896
897
|
const history = durationHistory(options.outputRoot);
|
|
897
898
|
const cost = (p) => history[p.key] ?? estimateMs(p);
|
|
@@ -899,7 +900,7 @@ export async function runPaths(options) {
|
|
|
899
900
|
if (reused.size)
|
|
900
901
|
log(`Reusing ${reused.size} passed path(s) from ${path.basename(options.reuse.runDir)}`);
|
|
901
902
|
log(`Running ${queue.length} path(s) on ${workers} worker(s)`);
|
|
902
|
-
writeRun(runDir, options, options.paths, outcomes, runInfo(false));
|
|
903
|
+
options.onProgress?.(writeRun(runDir, options, options.paths, outcomes, runInfo(false)));
|
|
903
904
|
// A driver can throw outside any awaited call (a message for a context that just closed); it
|
|
904
905
|
// belongs to one attempt, so it is logged and the run goes on instead of the process dying.
|
|
905
906
|
const stray = (error) => log(` (ignored an error thrown outside any step: ${(error instanceof Error ? error.message : String(error)).split('\n')[0]})`);
|
|
@@ -924,5 +925,6 @@ export async function runPaths(options) {
|
|
|
924
925
|
await driver.dispose?.();
|
|
925
926
|
await Promise.all(sources.map((s) => s.close?.()));
|
|
926
927
|
const manifest = writeRun(runDir, options, options.paths, outcomes, runInfo(true));
|
|
928
|
+
options.onProgress?.(manifest);
|
|
927
929
|
return { runDir, manifest, ok: manifest.privacy.passed && manifest.paths.every((r) => r.status !== 'failed') };
|
|
928
930
|
}
|
|
@@ -16,7 +16,7 @@ export type Check = {
|
|
|
16
16
|
readonly detail: string;
|
|
17
17
|
readonly fix?: string;
|
|
18
18
|
};
|
|
19
|
-
/** The least an account needs, as an AWS IAM policy. R2, MinIO and the rest have their own policy languages: grant the same actions. */
|
|
19
|
+
/** The least an account needs, as an AWS IAM policy. The combined server role also stores operational leases under server/live/. R2, MinIO and the rest have their own policy languages: grant the same actions. */
|
|
20
20
|
export declare function iamPolicy(location: {
|
|
21
21
|
bucket: string;
|
|
22
22
|
prefix: string;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { LAYER_INDEX } from './layer-store.js';
|
|
12
12
|
import { verifyObjectStore } from './object-store-conformance.js';
|
|
13
|
-
/** The least an account needs, as an AWS IAM policy. R2, MinIO and the rest have their own policy languages: grant the same actions. */
|
|
13
|
+
/** The least an account needs, as an AWS IAM policy. The combined server role also stores operational leases under server/live/. R2, MinIO and the rest have their own policy languages: grant the same actions. */
|
|
14
14
|
export function iamPolicy(location, role) {
|
|
15
15
|
const base = location.prefix ? `${location.prefix}/` : '';
|
|
16
16
|
const bucketArn = `arn:aws:s3:::${location.bucket}`;
|
|
@@ -21,6 +21,8 @@ export function iamPolicy(location, role) {
|
|
|
21
21
|
if (role !== 'publisher') {
|
|
22
22
|
statements.push({ Sid: 'AtlasRead', Effect: 'Allow', Action: 's3:GetObject', Resource: `${bucketArn}/${base}layers/*` });
|
|
23
23
|
}
|
|
24
|
+
if (role === 'both')
|
|
25
|
+
statements.push({ Sid: 'AtlasLiveRuns', Effect: 'Allow', Action: ['s3:GetObject', 's3:PutObject'], Resource: `${bucketArn}/${base}server/live/*` }, { Sid: 'AtlasProjects', Effect: 'Allow', Action: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject'], Resource: [`${bucketArn}/${base}projects/*`, `${bucketArn}/${base}server/projects/*`] });
|
|
24
26
|
// Publishers need this too: without it S3 answers 403, not 404, when asked whether a layer exists yet.
|
|
25
27
|
statements.push({ Sid: 'AtlasList', Effect: 'Allow', Action: 's3:ListBucket', Resource: bucketArn, // IfExists: a listing always carries a prefix and is held to it; a HEAD or GET of one object carries none, and
|
|
26
28
|
// must still be answered "not found" rather than "forbidden".
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import type { ObjectStore, PagedListing } from './object-store.js';
|
|
12
12
|
/** How to reach a server: its `url`, the `token` to send (never in the URL), and, for tests and special runtimes, `fetch`, the retry count, the per-attempt timeout and `sleep`. */
|
|
13
13
|
export type HttpStoreOptions = {
|
|
14
|
+
/** Project namespace on a shared Atlas node. Omit for the legacy default project. */
|
|
15
|
+
readonly project?: string;
|
|
14
16
|
/** The Atlas server, such as `https://atlas.example.com`. A path on it (`/atlas`) is kept. */
|
|
15
17
|
readonly url: string;
|
|
16
18
|
/** A publish or admin token to write with, a read token to read with. Never put it in the URL. */
|
|
@@ -30,6 +32,8 @@ export declare class AtlasHttpError extends Error {
|
|
|
30
32
|
}
|
|
31
33
|
/** An `ObjectStore` (with paged listing) over an Atlas server, plus what only a server can answer: `ping` (is it there, and what may this token do) and `status` (the gate, worked out by the server). */
|
|
32
34
|
export type HttpStore = ObjectStore & PagedListing & {
|
|
35
|
+
/** The same authenticated node connection, scoped to another project. */
|
|
36
|
+
forProject(project: string): HttpStore;
|
|
33
37
|
/** `https://atlas.example.com`, with no secrets. */
|
|
34
38
|
readonly label: string;
|
|
35
39
|
readonly url: string;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* @see docs/server.md#the-store-protocol
|
|
10
10
|
*/
|
|
11
11
|
import { ObjectStoreWithPrefix } from './store-prefix.js';
|
|
12
|
+
import { ProjectSlugSchema } from '../../schema/src/project.js';
|
|
12
13
|
/** What an Atlas server said no to, with what to do about it. */
|
|
13
14
|
export class AtlasHttpError extends Error {
|
|
14
15
|
status;
|
|
@@ -40,7 +41,8 @@ function explain(status, base, message) {
|
|
|
40
41
|
*/
|
|
41
42
|
export function httpStore(options) {
|
|
42
43
|
const base = options.url.replace(/\/+$/, '');
|
|
43
|
-
const
|
|
44
|
+
const api = options.project ? `${base}/api/projects/${ProjectSlugSchema.parse(options.project)}` : `${base}/api`;
|
|
45
|
+
const root = `${api}/store`;
|
|
44
46
|
const doFetch = options.fetch ?? fetch;
|
|
45
47
|
const retries = options.retries ?? 4;
|
|
46
48
|
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
@@ -66,7 +68,7 @@ export function httpStore(options) {
|
|
|
66
68
|
await response.body?.cancel();
|
|
67
69
|
return null;
|
|
68
70
|
}
|
|
69
|
-
const said = await response.json().then((body) => body.error
|
|
71
|
+
const said = await response.json().then((body) => body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null, () => null);
|
|
70
72
|
failure = new AtlasHttpError(explain(response.status, base, said), response.status, response.status >= 500 || response.status === 429 || response.status === 408);
|
|
71
73
|
}
|
|
72
74
|
catch (error) {
|
|
@@ -78,6 +80,7 @@ export function httpStore(options) {
|
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
const store = {
|
|
83
|
+
forProject: project => httpStore({ ...options, project }),
|
|
81
84
|
label: base,
|
|
82
85
|
url: base,
|
|
83
86
|
async get(key) {
|
|
@@ -127,7 +130,7 @@ export function httpStore(options) {
|
|
|
127
130
|
},
|
|
128
131
|
async status(query = new URLSearchParams()) {
|
|
129
132
|
const suffix = query.size ? `?${query}` : '';
|
|
130
|
-
return (await request('GET', `${
|
|
133
|
+
return (await request('GET', `${api}/status${suffix}`)).json();
|
|
131
134
|
},
|
|
132
135
|
async ping() {
|
|
133
136
|
const started = performance.now();
|