@milehimikey/em-portal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/dist/build.d.ts +21 -0
- package/dist/build.js +132 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +43 -0
- package/dist/cli.js.map +1 -0
- package/dist/crossModel.d.ts +14 -0
- package/dist/crossModel.js +49 -0
- package/dist/crossModel.js.map +1 -0
- package/dist/em/docBody.d.ts +5 -0
- package/dist/em/docBody.js +43 -0
- package/dist/em/docBody.js.map +1 -0
- package/dist/em/exportDoc.d.ts +135 -0
- package/dist/em/exportDoc.js +22 -0
- package/dist/em/exportDoc.js.map +1 -0
- package/dist/em/run.d.ts +41 -0
- package/dist/em/run.js +99 -0
- package/dist/em/run.js.map +1 -0
- package/dist/em/statusDoc.d.ts +56 -0
- package/dist/em/statusDoc.js +11 -0
- package/dist/em/statusDoc.js.map +1 -0
- package/dist/pages/html.d.ts +11 -0
- package/dist/pages/html.js +84 -0
- package/dist/pages/html.js.map +1 -0
- package/dist/pages/index.d.ts +3 -0
- package/dist/pages/index.js +128 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/model.d.ts +17 -0
- package/dist/pages/model.js +54 -0
- package/dist/pages/model.js.map +1 -0
- package/dist/pages/slice.d.ts +16 -0
- package/dist/pages/slice.js +144 -0
- package/dist/pages/slice.js.map +1 -0
- package/dist/pages/types.d.ts +22 -0
- package/dist/pages/types.js +4 -0
- package/dist/pages/types.js.map +1 -0
- package/dist/refs.d.ts +36 -0
- package/dist/refs.js +69 -0
- package/dist/refs.js.map +1 -0
- package/dist/slug.d.ts +5 -0
- package/dist/slug.js +30 -0
- package/dist/slug.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 milehimikey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# em-portal
|
|
2
|
+
|
|
3
|
+
A stakeholder-facing, **fully static** portal for [`em`](https://github.com/milehimikey/em)
|
|
4
|
+
event models — a read-only, multi-model browser built entirely from `em export`/`em status`
|
|
5
|
+
JSON. No server, no database, no storage of its own: git stays the only history store, and
|
|
6
|
+
`em-portal build` is a pure function of your `.em` files that a CI job runs and publishes to
|
|
7
|
+
static hosting (GitHub Pages, an S3/GCS bucket, ...).
|
|
8
|
+
|
|
9
|
+
This executes the decision recorded in the `em` repo at
|
|
10
|
+
[`docs/decisions/mil-162-teachable-navigator.md`](https://github.com/milehimikey/em/blob/main/docs/decisions/mil-162-teachable-navigator.md):
|
|
11
|
+
a **separate add-on tool**, not a rework of `em catalog`, built for the audience `em catalog`
|
|
12
|
+
names but doesn't fully serve — a non-technical stakeholder who wants "is this healthy" up
|
|
13
|
+
front and a way to navigate a system of several models, not a flat per-model site.
|
|
14
|
+
|
|
15
|
+
## Status
|
|
16
|
+
|
|
17
|
+
**0.1.0 — read-only multi-model browser.** See [What 0.1.0 includes](#what-01-includes) below.
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install --save-dev @milehimikey/em @milehimikey/em-portal
|
|
23
|
+
npx em-portal build models/*/*.em -o site
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
site/
|
|
28
|
+
index.html # landing page: em status rollup (state up front)
|
|
29
|
+
<model-key>/
|
|
30
|
+
index.html # model page: diagram + slice table
|
|
31
|
+
diagram.svg
|
|
32
|
+
slices/
|
|
33
|
+
<slice-key>.html # slice page: diagram, doc, driftSignal, PR link
|
|
34
|
+
<slice-key>.svg
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Open `site/index.html` directly (`file://` works, no server needed) or serve the directory with
|
|
38
|
+
anything that serves static files.
|
|
39
|
+
|
|
40
|
+
### CLI
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
em-portal build <models...> [options]
|
|
44
|
+
|
|
45
|
+
Arguments:
|
|
46
|
+
models Paths to .em model files (same argument form as `em status`)
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
-o, --out <dir> Output directory (default: "site")
|
|
50
|
+
--title <title> Site title shown on the landing page (default: "em portal")
|
|
51
|
+
--tests <dir> Directory to scan for INV-* test citations (enables invariant
|
|
52
|
+
coverage on the landing page — forwarded to `em status --tests`)
|
|
53
|
+
--repo <path> Git repo to compute commits-behind-HEAD in (forwarded to
|
|
54
|
+
`em status --repo`; default: each model's own directory)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Multiple models in one invocation is how a multi-model system's cross-model index and
|
|
58
|
+
`em status` rollup get built — same "one directory per model" convention
|
|
59
|
+
[`examples/multi-model`](https://github.com/milehimikey/em/tree/main/examples/multi-model) in
|
|
60
|
+
the em repo documents:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
em-portal build models/checkout/checkout.em models/fulfillment/fulfillment.em -o site
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## How it works
|
|
67
|
+
|
|
68
|
+
em-portal is a **separate package from `em`** — it never imports em's internal modules. Every
|
|
69
|
+
fact it shows comes from shelling out to the installed `em` CLI (a real dependency of your
|
|
70
|
+
project, resolved the normal npm way):
|
|
71
|
+
|
|
72
|
+
- `em export <file>` — the normalized model as JSON (schema 1.8+): slices, elements, stable
|
|
73
|
+
`ref`s, the slice-doc frontmatter join (`status`, `driftSignal`, `implementedIn`, ...).
|
|
74
|
+
- `em status <files...> --json` — the state-of-the-system rollup across every model given:
|
|
75
|
+
slices by lifecycle status, drift-signal breakdown, invariant coverage (with `--tests`),
|
|
76
|
+
open issues, and freshness (last-conformed revision, commits/slice-PRs behind HEAD).
|
|
77
|
+
- `em render <file>` / `em render <file> --slice <name>` — the model's full diagram and each
|
|
78
|
+
slice's own canonical-pattern diagram (SVG).
|
|
79
|
+
|
|
80
|
+
`em export`'s doc join is deliberately frontmatter-only ("never the markdown body" — see the em
|
|
81
|
+
repo's `docs/cli.md`); em-portal reads the bound `slices/<key>.md` file itself (the same
|
|
82
|
+
`doc.path` the join already names) and renders its body with `marked`, the same markdown engine
|
|
83
|
+
`em catalog` uses.
|
|
84
|
+
|
|
85
|
+
**Consumers must tolerate unknown fields.** em's own versioning policy is additive-minor-bump,
|
|
86
|
+
breaking-major-bump; `src/em/exportDoc.ts`/`statusDoc.ts` declare only the fields em-portal
|
|
87
|
+
reads (nothing is `.strict()`-validated), and `checkExportSchemaCompatible`/
|
|
88
|
+
`checkStatusSchemaCompatible` only warn on a **major** version mismatch. MIL-171 (landing in the
|
|
89
|
+
em repo in parallel with this ticket) adds `owner:`/`tracking:` fields to the doc join — this is
|
|
90
|
+
exactly the kind of change em-portal's ingestion layer is built to shrug off.
|
|
91
|
+
|
|
92
|
+
## Deep links (MIL-173)
|
|
93
|
+
|
|
94
|
+
Portal URLs are built on em's own stable element `ref`s (`<sliceKey>/<kind>.<slug>`, assigned by
|
|
95
|
+
`em export` and edit-stable — inserting or reordering slices never changes an existing ref). One
|
|
96
|
+
addressing scheme for both audiences: an agent citing `checkout/event.order-placed` (via
|
|
97
|
+
`em query`/MCP) and a stakeholder clicking the matching row on a slice page mean the same
|
|
98
|
+
element.
|
|
99
|
+
|
|
100
|
+
A slice page's URL already **is** its slice's own key (`<model-key>/slices/<sliceKey>.html`);
|
|
101
|
+
every element row on that page carries its full `ref` as both its DOM `id` and a visible,
|
|
102
|
+
copyable `#`-fragment permalink next to it (`src/refs.ts`). The full deep link to one element is:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
<model-key>/slices/<sliceKey>.html#<sliceKey>/<kind>.<slug>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For example, in `examples/order-fulfillment`'s `Checkout` slice, the `Submit Payment` command's
|
|
109
|
+
deep link is:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
order-fulfillment/slices/checkout.html#checkout/command.submit-payment
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`src/refs.ts` exports `elementDeepLink(modelKey, ref)` / `sliceUrl(modelKey, sliceKey)` to build
|
|
116
|
+
these, and `parseDeepLink(link)` to resolve one back into `{ modelKey, sliceKey, elementRef }`
|
|
117
|
+
(returns `null` for anything not shaped like a page this portal generates — an external URL, the
|
|
118
|
+
site's own landing page, ...). The landing page's cross-model links table (below) uses the same
|
|
119
|
+
builder to link straight at the specific publishing/referencing element on each side, not just
|
|
120
|
+
the two models' index pages. Refs never depend on layout/coordinates or on the portal's own
|
|
121
|
+
directory-naming choices for the `<model-key>` segment (see `src/slug.ts`) — only on `em
|
|
122
|
+
export`'s own ref-stability guarantee.
|
|
123
|
+
|
|
124
|
+
## Multi-model navigation
|
|
125
|
+
|
|
126
|
+
The landing page indexes every model given to `em-portal build` and rolls up `em status` across
|
|
127
|
+
all of them. Where one model's `public` event feeds another model's slice, em-portal resolves
|
|
128
|
+
that as a **cross-model link** (`src/crossModel.ts`) — matching a public event's exact name
|
|
129
|
+
against every other model's element names. This is a naming-convention join, **not** a
|
|
130
|
+
compiler-verified reference: em has no DSL-level construct for "this element's trigger is
|
|
131
|
+
another model file's public event" (confirmed against `em validate`'s `view-from-unresolved`
|
|
132
|
+
error — see the decision doc). If a link looks wrong, the fix is in the `.em` naming, not in
|
|
133
|
+
em-portal.
|
|
134
|
+
|
|
135
|
+
## What 0.1.0 includes
|
|
136
|
+
|
|
137
|
+
- Package scaffold, CLI (`em-portal build`), export/status-ingestion tolerant of schema minor
|
|
138
|
+
bumps.
|
|
139
|
+
- Landing page: the `em status` rollup up front (slice counts, drift signal, invariant coverage,
|
|
140
|
+
open issues, freshness/commits-behind-HEAD per model) — a reader's first question ("is this
|
|
141
|
+
healthy") answered before any slice list.
|
|
142
|
+
- Multi-model index + the cross-model link heuristic.
|
|
143
|
+
- Per-model pages (diagram + slice table) and per-slice pages (diagram, rendered doc,
|
|
144
|
+
driftSignal, implemented-in link).
|
|
145
|
+
- Deep links on em's stable element refs (MIL-173) — see [Deep links](#deep-links-mil-173) above.
|
|
146
|
+
- Fully static, self-contained output; deterministic builds (same inputs -> byte-identical
|
|
147
|
+
site, verified in `test/build.test.ts`); no LLM anywhere.
|
|
148
|
+
|
|
149
|
+
**Deferred to 0.2.0** (MIL-174, "guided first read"): the onboarding/teaching layer — real
|
|
150
|
+
click-driven interaction that annotates a first-time reader's own model as they encounter each
|
|
151
|
+
element/pattern kind, rather than a glossary page. 0.1.0's pages are informative but not
|
|
152
|
+
teaching-aware.
|
|
153
|
+
|
|
154
|
+
**Deferred to 0.3.0** (MIL-175, "async review intake"): a "raise a question" affordance on a
|
|
155
|
+
slice page, triaged into an `issue "..."` marker through the normal ratified path. 0.1.0 is
|
|
156
|
+
read-only.
|
|
157
|
+
|
|
158
|
+
**Also deferred / open, not yet built**:
|
|
159
|
+
- A real GitHub Pages/bucket deploy workflow wired to a specific target repo — see
|
|
160
|
+
[CI publish recipe](#ci-publish-recipe) below for the pattern; this repo's own CI only builds
|
|
161
|
+
and tests, since it has no real event models of its own to publish.
|
|
162
|
+
- Parallelizing the per-model `em export`/`em render` calls (they run sequentially today — see
|
|
163
|
+
[Open questions](#open-questions)).
|
|
164
|
+
- Any client-side JS (search, filtering, sort) — pages are plain static HTML, same posture as
|
|
165
|
+
`em catalog`.
|
|
166
|
+
|
|
167
|
+
## CI publish recipe
|
|
168
|
+
|
|
169
|
+
A downstream project (one that actually has `.em` models) publishes the portal from CI like
|
|
170
|
+
this — build, then hand the output directory to whichever static-hosting deploy action you use:
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
# .github/workflows/portal.yml
|
|
174
|
+
name: em-portal
|
|
175
|
+
on:
|
|
176
|
+
push:
|
|
177
|
+
branches: [main]
|
|
178
|
+
permissions:
|
|
179
|
+
contents: read
|
|
180
|
+
pages: write
|
|
181
|
+
id-token: write
|
|
182
|
+
jobs:
|
|
183
|
+
build-and-deploy:
|
|
184
|
+
runs-on: ubuntu-latest
|
|
185
|
+
steps:
|
|
186
|
+
- uses: actions/checkout@v4
|
|
187
|
+
- uses: actions/setup-node@v4
|
|
188
|
+
with: { node-version: 20 }
|
|
189
|
+
- run: npm ci
|
|
190
|
+
- run: npx em-portal build models/*/*.em -o site
|
|
191
|
+
- uses: actions/upload-pages-artifact@v3
|
|
192
|
+
with: { path: site }
|
|
193
|
+
- uses: actions/deploy-pages@v4
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
This repo's own [`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs `npm test` (which
|
|
197
|
+
builds the portal against the fixture models under `test/fixtures/`) on every push — the build
|
|
198
|
+
half of the recipe above, exercised continuously; the deploy half needs a real target repo with
|
|
199
|
+
real models, which this package intentionally isn't.
|
|
200
|
+
|
|
201
|
+
## Test fixtures
|
|
202
|
+
|
|
203
|
+
`test/fixtures/` holds `.em` files (plus their `slices/*.md` docs) copied verbatim from the `em`
|
|
204
|
+
repo's own `examples/order-fulfillment` and `examples/multi-model`, generated by running that
|
|
205
|
+
`em` checkout's CLI (`npx tsx src/cli.ts` from the em repo) against those examples and confirming
|
|
206
|
+
they still compile clean. `test/fixtures/invalid/broken.em` is hand-written to exercise the
|
|
207
|
+
refuse-on-error path (an unresolvable `view ... from`). `test/fixtures/cross-model/` is a small
|
|
208
|
+
hand-written pair (`producer`/`consumer`) exercising the cross-model link heuristic end to end: a
|
|
209
|
+
`public` event in one model whose exact name appears inside two of the other model's element
|
|
210
|
+
names.
|
|
211
|
+
|
|
212
|
+
## Development
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npm install
|
|
216
|
+
npm run build # tsc -> dist/
|
|
217
|
+
npm test # vitest — packs and shells out to the real @milehimikey/em devDependency
|
|
218
|
+
npm run typecheck
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`@milehimikey/em` is installed from a packed tarball checked into `vendor/` (`npm pack` output
|
|
222
|
+
from the em repo) rather than a registry version, since em-portal is developed alongside
|
|
223
|
+
unreleased em changes; see `package.json`'s devDependency and re-pack with a newer em checkout
|
|
224
|
+
as needed (`cd ../em && npm run build && npm pack && cp *.tgz ../em-portal/vendor/`, then bump
|
|
225
|
+
the `vendor/*.tgz` filename referenced in `package.json` and `npm install`). A production
|
|
226
|
+
install of em-portal in a downstream project instead takes `@milehimikey/em` as a normal
|
|
227
|
+
(published) dependency — see [Quickstart](#quickstart).
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT
|
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface BuildOptions {
|
|
2
|
+
outDir: string;
|
|
3
|
+
title?: string;
|
|
4
|
+
testsDir?: string;
|
|
5
|
+
repo?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface BuildTiming {
|
|
8
|
+
file: string;
|
|
9
|
+
exportMs: number;
|
|
10
|
+
renderMs: number;
|
|
11
|
+
}
|
|
12
|
+
export interface BuildResult {
|
|
13
|
+
models: number;
|
|
14
|
+
slices: number;
|
|
15
|
+
crossModelLinks: number;
|
|
16
|
+
warnings: string[];
|
|
17
|
+
timing: BuildTiming[];
|
|
18
|
+
statusTimingMs: number;
|
|
19
|
+
totalMs: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function buildPortal(files: string[], opts: BuildOptions): Promise<BuildResult>;
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Orchestrates the portal's static-site build: one `em export --json` +
|
|
3
|
+
// `em render` pass per input model, one `em status --json` pass across all of
|
|
4
|
+
// them, the cross-model link heuristic, then every page written to disk.
|
|
5
|
+
//
|
|
6
|
+
// <outDir>/
|
|
7
|
+
// index.html landing page: em status rollup + model index
|
|
8
|
+
// <model-key>/
|
|
9
|
+
// index.html model page: diagram + slice table
|
|
10
|
+
// diagram.svg
|
|
11
|
+
// slices/
|
|
12
|
+
// <slice-key>.html slice page: diagram, doc, driftSignal, PR link
|
|
13
|
+
// <slice-key>.svg
|
|
14
|
+
//
|
|
15
|
+
// Every model gets its own output directory (mirroring `em catalog`'s
|
|
16
|
+
// convention) so slice keys from different models can never collide, even
|
|
17
|
+
// without a cross-file dedupe pass.
|
|
18
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
19
|
+
import { basename, extname, join } from "node:path";
|
|
20
|
+
import { emExportJson, emRenderModel, emRenderSlice, emStatusJson } from "./em/run.js";
|
|
21
|
+
import { checkExportSchemaCompatible } from "./em/exportDoc.js";
|
|
22
|
+
import { renderDocBody } from "./em/docBody.js";
|
|
23
|
+
import { checkStatusSchemaCompatible } from "./em/statusDoc.js";
|
|
24
|
+
import { buildCrossModelLinks } from "./crossModel.js";
|
|
25
|
+
import { dedupe, kebabSlug } from "./slug.js";
|
|
26
|
+
import { renderIndexPage } from "./pages/index.js";
|
|
27
|
+
import { renderModelPage } from "./pages/model.js";
|
|
28
|
+
import { renderSlicePage } from "./pages/slice.js";
|
|
29
|
+
export async function buildPortal(files, opts) {
|
|
30
|
+
const t0 = performance.now();
|
|
31
|
+
if (files.length === 0)
|
|
32
|
+
throw new Error("em-portal build: at least one model file is required");
|
|
33
|
+
const outDir = opts.outDir;
|
|
34
|
+
await mkdir(outDir, { recursive: true });
|
|
35
|
+
const warnings = [];
|
|
36
|
+
const usedModelKeys = new Set();
|
|
37
|
+
const compiled = [];
|
|
38
|
+
const modelSummaries = [];
|
|
39
|
+
const timing = [];
|
|
40
|
+
let sliceCount = 0;
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
const tExportStart = performance.now();
|
|
43
|
+
const doc = (await emExportJson(file));
|
|
44
|
+
const exportWarning = checkExportSchemaCompatible(doc);
|
|
45
|
+
if (exportWarning)
|
|
46
|
+
warnings.push(`${file}: ${exportWarning}`);
|
|
47
|
+
for (const d of doc.diagnostics) {
|
|
48
|
+
if (d.severity === "warning")
|
|
49
|
+
warnings.push(`${file}:${d.line ?? "?"}: ${d.message}`);
|
|
50
|
+
}
|
|
51
|
+
const tExportEnd = performance.now();
|
|
52
|
+
const modelKey = dedupe(kebabSlug(doc.model.name ?? basename(file, extname(file))), usedModelKeys);
|
|
53
|
+
const modelDir = join(outDir, modelKey);
|
|
54
|
+
const slicesDir = join(modelDir, "slices");
|
|
55
|
+
await mkdir(slicesDir, { recursive: true });
|
|
56
|
+
const tRenderStart = performance.now();
|
|
57
|
+
const diagramFile = "diagram.svg";
|
|
58
|
+
await emRenderModel(file, join(modelDir, diagramFile));
|
|
59
|
+
const sliceSummaries = [];
|
|
60
|
+
for (const slice of doc.model.slices) {
|
|
61
|
+
const sliceDiagramFile = `${slice.key}.svg`;
|
|
62
|
+
await emRenderSlice(file, slice.name, join(slicesDir, sliceDiagramFile));
|
|
63
|
+
const page = renderSlicePage({
|
|
64
|
+
modelName: doc.model.name ?? modelKey,
|
|
65
|
+
modelKey,
|
|
66
|
+
diagramFile: `../${diagramFile}`,
|
|
67
|
+
sliceDiagramFile,
|
|
68
|
+
slice,
|
|
69
|
+
pattern: slice.pattern,
|
|
70
|
+
doc: slice.doc,
|
|
71
|
+
docBodyHtml: renderDocBody(file, slice.doc.path),
|
|
72
|
+
});
|
|
73
|
+
await writeFile(join(slicesDir, `${slice.key}.html`), page, "utf8");
|
|
74
|
+
sliceSummaries.push({
|
|
75
|
+
key: slice.key,
|
|
76
|
+
name: slice.name,
|
|
77
|
+
pattern: slice.pattern,
|
|
78
|
+
hasDoc: slice.doc.found,
|
|
79
|
+
status: slice.doc.status,
|
|
80
|
+
driftSignal: slice.doc.driftSignal,
|
|
81
|
+
});
|
|
82
|
+
sliceCount++;
|
|
83
|
+
}
|
|
84
|
+
const tRenderEnd = performance.now();
|
|
85
|
+
const modelPage = renderModelPage({
|
|
86
|
+
modelKey,
|
|
87
|
+
modelName: doc.model.name ?? modelKey,
|
|
88
|
+
file,
|
|
89
|
+
diagramFile,
|
|
90
|
+
slices: sliceSummaries,
|
|
91
|
+
});
|
|
92
|
+
await writeFile(join(modelDir, "index.html"), modelPage, "utf8");
|
|
93
|
+
modelSummaries.push({
|
|
94
|
+
key: modelKey,
|
|
95
|
+
name: doc.model.name ?? modelKey,
|
|
96
|
+
file,
|
|
97
|
+
sliceCount: doc.model.slices.length,
|
|
98
|
+
slices: sliceSummaries,
|
|
99
|
+
});
|
|
100
|
+
compiled.push({ modelKey, file, doc });
|
|
101
|
+
timing.push({
|
|
102
|
+
file,
|
|
103
|
+
exportMs: tExportEnd - tExportStart,
|
|
104
|
+
renderMs: tRenderEnd - tRenderStart,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const tStatusStart = performance.now();
|
|
108
|
+
const statusDoc = (await emStatusJson(files, { testsDir: opts.testsDir, repo: opts.repo }));
|
|
109
|
+
const statusWarning = checkStatusSchemaCompatible(statusDoc);
|
|
110
|
+
if (statusWarning)
|
|
111
|
+
warnings.push(statusWarning);
|
|
112
|
+
const tStatusEnd = performance.now();
|
|
113
|
+
const crossModelLinks = buildCrossModelLinks(compiled);
|
|
114
|
+
const summary = {
|
|
115
|
+
title: opts.title ?? "em portal",
|
|
116
|
+
models: modelSummaries,
|
|
117
|
+
crossModelLinks,
|
|
118
|
+
};
|
|
119
|
+
const indexHtml = renderIndexPage(statusDoc, summary);
|
|
120
|
+
await writeFile(join(outDir, "index.html"), indexHtml, "utf8");
|
|
121
|
+
const t1 = performance.now();
|
|
122
|
+
return {
|
|
123
|
+
models: modelSummaries.length,
|
|
124
|
+
slices: sliceCount,
|
|
125
|
+
crossModelLinks: crossModelLinks.length,
|
|
126
|
+
warnings,
|
|
127
|
+
timing,
|
|
128
|
+
statusTimingMs: tStatusEnd - tStatusStart,
|
|
129
|
+
totalMs: t1 - t0,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { buildPortal } from "./build.js";
|
|
5
|
+
import { EmCliError } from "./em/run.js";
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name("em-portal")
|
|
9
|
+
.description("Stakeholder-facing static portal for em event models")
|
|
10
|
+
.version("0.1.0");
|
|
11
|
+
program
|
|
12
|
+
.command("build")
|
|
13
|
+
.description("Build a static, read-only multi-model portal site from one or more .em model files")
|
|
14
|
+
.argument("<models...>", "Paths to .em model files (same argument form as `em status`)")
|
|
15
|
+
.option("-o, --out <dir>", "Output directory", "site")
|
|
16
|
+
.option("--title <title>", "Site title shown on the landing page", "em portal")
|
|
17
|
+
.option("--tests <dir>", "Directory to scan for INV-* test citations (enables invariant coverage on the landing page)")
|
|
18
|
+
.option("--repo <path>", "Git repo to compute commits-behind-HEAD in (default: each model's own directory)")
|
|
19
|
+
.action(async (models, options) => {
|
|
20
|
+
try {
|
|
21
|
+
const result = await buildPortal(models, {
|
|
22
|
+
outDir: options.out,
|
|
23
|
+
title: options.title,
|
|
24
|
+
testsDir: options.tests,
|
|
25
|
+
repo: options.repo,
|
|
26
|
+
});
|
|
27
|
+
for (const w of result.warnings)
|
|
28
|
+
console.warn(`warning: ${w}`);
|
|
29
|
+
console.log(`${result.models} model(s), ${result.slices} slice(s), ${result.crossModelLinks} cross-model link(s) — wrote ${options.out}/ in ${result.totalMs.toFixed(0)}ms`);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (err instanceof EmCliError) {
|
|
33
|
+
process.stderr.write(`${err.message}\n`);
|
|
34
|
+
if (err.stdout.trim())
|
|
35
|
+
process.stderr.write(`${err.stdout}\n`);
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
program.parseAsync(process.argv);
|
|
43
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,+BAA+B;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,sDAAsD,CAAC;KACnE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,oFAAoF,CAAC;KACjG,QAAQ,CAAC,aAAa,EAAE,8DAA8D,CAAC;KACvF,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,CAAC;KACrD,MAAM,CAAC,iBAAiB,EAAE,sCAAsC,EAAE,WAAW,CAAC;KAC9E,MAAM,CAAC,eAAe,EAAE,6FAA6F,CAAC;KACtH,MAAM,CAAC,eAAe,EAAE,kFAAkF,CAAC;KAC3G,MAAM,CAAC,KAAK,EAAE,MAAgB,EAAE,OAAsE,EAAE,EAAE;IACzG,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE;YACvC,MAAM,EAAE,OAAO,CAAC,GAAG;YACnB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,QAAQ,EAAE,OAAO,CAAC,KAAK;YACvB,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB,CAAC,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,GAAG,CACT,GAAG,MAAM,CAAC,MAAM,cAAc,MAAM,CAAC,MAAM,cAAc,MAAM,CAAC,eAAe,gCAAgC,OAAO,CAAC,GAAG,QAAQ,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAChK,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,UAAU,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACzC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;gBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;YAC/D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ExportDoc } from "./em/exportDoc.js";
|
|
2
|
+
export interface CompiledModel {
|
|
3
|
+
modelKey: string;
|
|
4
|
+
file: string;
|
|
5
|
+
doc: ExportDoc;
|
|
6
|
+
}
|
|
7
|
+
export interface CrossModelLink {
|
|
8
|
+
eventName: string;
|
|
9
|
+
fromModelKey: string;
|
|
10
|
+
fromRef: string;
|
|
11
|
+
toModelKey: string;
|
|
12
|
+
toElementRef: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function buildCrossModelLinks(compiled: CompiledModel[]): CrossModelLink[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// The cross-model navigation join (MIL-162's "multi-model navigation"
|
|
3
|
+
// property #3). em has no DSL-level construct for "this element's trigger is
|
|
4
|
+
// another model file's public event" — each model compiles independently,
|
|
5
|
+
// and `from` only resolves within the model being compiled. So the only
|
|
6
|
+
// thing carried across independently-built `em export` documents today is a
|
|
7
|
+
// public event's exact NAME (docs/dsl.md "Integration surface"). This is
|
|
8
|
+
// exactly that heuristic join, ported from prototypes/portal-spike/spike.ts's
|
|
9
|
+
// buildCrossModelLinks in the em repo (the MIL-162 prototype that tested this
|
|
10
|
+
// approach at scale) — pure string matching over the independently-computed
|
|
11
|
+
// export JSON for each model, no access to em's internal model objects, no
|
|
12
|
+
// LLM, no fuzzy matching. It's honest about what it is: a naming-convention
|
|
13
|
+
// join with no compiler guarantee behind it (see the decision doc's "What
|
|
14
|
+
// the prototype found" section).
|
|
15
|
+
export function buildCrossModelLinks(compiled) {
|
|
16
|
+
const publicEventsByName = new Map();
|
|
17
|
+
for (const c of compiled) {
|
|
18
|
+
for (const slice of c.doc.model.slices) {
|
|
19
|
+
for (const el of slice.elements) {
|
|
20
|
+
if (el.kind === "event" && el.public) {
|
|
21
|
+
publicEventsByName.set(el.name, { modelKey: c.modelKey, ref: el.ref });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const links = [];
|
|
27
|
+
for (const c of compiled) {
|
|
28
|
+
for (const slice of c.doc.model.slices) {
|
|
29
|
+
for (const el of slice.elements) {
|
|
30
|
+
for (const [eventName, source] of publicEventsByName) {
|
|
31
|
+
// An element referencing its OWN model's public event isn't a cross-model link.
|
|
32
|
+
if (source.modelKey === c.modelKey)
|
|
33
|
+
continue;
|
|
34
|
+
if (el.name.includes(eventName)) {
|
|
35
|
+
links.push({
|
|
36
|
+
eventName,
|
|
37
|
+
fromModelKey: source.modelKey,
|
|
38
|
+
fromRef: source.ref,
|
|
39
|
+
toModelKey: c.modelKey,
|
|
40
|
+
toElementRef: el.ref,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return links;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=crossModel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossModel.js","sourceRoot":"","sources":["../src/crossModel.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,sEAAsE;AACtE,6EAA6E;AAC7E,0EAA0E;AAC1E,wEAAwE;AACxE,4EAA4E;AAC5E,yEAAyE;AACzE,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,4EAA4E;AAC5E,0EAA0E;AAC1E,iCAAiC;AAkBjC,MAAM,UAAU,oBAAoB,CAAC,QAAyB;IAC5D,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAA6C,CAAC;IAChF,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACvC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAChC,IAAI,EAAE,CAAC,IAAI,KAAK,OAAO,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;oBACrC,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;gBACzE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAqB,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACvC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAChC,KAAK,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,kBAAkB,EAAE,CAAC;oBACrD,gFAAgF;oBAChF,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;wBAAE,SAAS;oBAC7C,IAAI,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;wBAChC,KAAK,CAAC,IAAI,CAAC;4BACT,SAAS;4BACT,YAAY,EAAE,MAAM,CAAC,QAAQ;4BAC7B,OAAO,EAAE,MAAM,CAAC,GAAG;4BACnB,UAAU,EAAE,CAAC,CAAC,QAAQ;4BACtB,YAAY,EAAE,EAAE,CAAC,GAAG;yBACrB,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Returns rendered HTML for the slice doc bound at `docRelPath` (relative to
|
|
2
|
+
* `modelFile`'s own directory), or `null` if the file can't be read — a
|
|
3
|
+
* routine, non-fatal state (no doc bound, or a stale `path` from a `found:
|
|
4
|
+
* false` join). */
|
|
5
|
+
export declare function renderDocBody(modelFile: string, docRelPath: string): string | null;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// `em export`'s slice-doc join is deliberately frontmatter-only — "never the
|
|
3
|
+
// markdown body" (docs/cli.md) — so a portal that wants to show the rendered
|
|
4
|
+
// doc has to read and render the body itself. This reads the same
|
|
5
|
+
// `slices/<key>.md` file the join's own `path` field names (resolved
|
|
6
|
+
// relative to the `.em` file's directory, the same sibling-file convention
|
|
7
|
+
// every doc-aware em command uses), strips the leading YAML frontmatter
|
|
8
|
+
// block, and renders the remainder with `marked` — the same markdown engine
|
|
9
|
+
// em's own catalog/sliceDoc.ts uses, so headings/lists/code fences render
|
|
10
|
+
// the same way a reader would see in `em catalog`.
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { marked } from "marked";
|
|
14
|
+
function stripFrontmatter(markdown) {
|
|
15
|
+
if (!markdown.startsWith("---"))
|
|
16
|
+
return markdown;
|
|
17
|
+
const lines = markdown.split("\n");
|
|
18
|
+
if (lines[0].trim() !== "---")
|
|
19
|
+
return markdown;
|
|
20
|
+
const closeIndex = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
|
|
21
|
+
if (closeIndex === -1)
|
|
22
|
+
return markdown;
|
|
23
|
+
return lines.slice(closeIndex + 1).join("\n");
|
|
24
|
+
}
|
|
25
|
+
/** Returns rendered HTML for the slice doc bound at `docRelPath` (relative to
|
|
26
|
+
* `modelFile`'s own directory), or `null` if the file can't be read — a
|
|
27
|
+
* routine, non-fatal state (no doc bound, or a stale `path` from a `found:
|
|
28
|
+
* false` join). */
|
|
29
|
+
export function renderDocBody(modelFile, docRelPath) {
|
|
30
|
+
const absPath = join(dirname(modelFile), docRelPath);
|
|
31
|
+
let raw;
|
|
32
|
+
try {
|
|
33
|
+
raw = readFileSync(absPath, "utf8");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const body = stripFrontmatter(raw).trim();
|
|
39
|
+
if (!body)
|
|
40
|
+
return null;
|
|
41
|
+
return marked.parse(body, { async: false });
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=docBody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docBody.js","sourceRoot":"","sources":["../../src/em/docBody.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,6EAA6E;AAC7E,6EAA6E;AAC7E,kEAAkE;AAClE,qEAAqE;AACrE,2EAA2E;AAC3E,wEAAwE;AACxE,4EAA4E;AAC5E,0EAA0E;AAC1E,mDAAmD;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,SAAS,gBAAgB,CAAC,QAAgB;IACxC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC/C,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,CAAC;IAChF,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvC,OAAO,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED;;;oBAGoB;AACpB,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,UAAkB;IACjE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,CAAC;IACrD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAW,CAAC;AACxD,CAAC"}
|