@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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/dist/build.d.ts +21 -0
  4. package/dist/build.js +132 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +43 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/crossModel.d.ts +14 -0
  10. package/dist/crossModel.js +49 -0
  11. package/dist/crossModel.js.map +1 -0
  12. package/dist/em/docBody.d.ts +5 -0
  13. package/dist/em/docBody.js +43 -0
  14. package/dist/em/docBody.js.map +1 -0
  15. package/dist/em/exportDoc.d.ts +135 -0
  16. package/dist/em/exportDoc.js +22 -0
  17. package/dist/em/exportDoc.js.map +1 -0
  18. package/dist/em/run.d.ts +41 -0
  19. package/dist/em/run.js +99 -0
  20. package/dist/em/run.js.map +1 -0
  21. package/dist/em/statusDoc.d.ts +56 -0
  22. package/dist/em/statusDoc.js +11 -0
  23. package/dist/em/statusDoc.js.map +1 -0
  24. package/dist/pages/html.d.ts +11 -0
  25. package/dist/pages/html.js +84 -0
  26. package/dist/pages/html.js.map +1 -0
  27. package/dist/pages/index.d.ts +3 -0
  28. package/dist/pages/index.js +128 -0
  29. package/dist/pages/index.js.map +1 -0
  30. package/dist/pages/model.d.ts +17 -0
  31. package/dist/pages/model.js +54 -0
  32. package/dist/pages/model.js.map +1 -0
  33. package/dist/pages/slice.d.ts +16 -0
  34. package/dist/pages/slice.js +144 -0
  35. package/dist/pages/slice.js.map +1 -0
  36. package/dist/pages/types.d.ts +22 -0
  37. package/dist/pages/types.js +4 -0
  38. package/dist/pages/types.js.map +1 -0
  39. package/dist/refs.d.ts +36 -0
  40. package/dist/refs.js +69 -0
  41. package/dist/refs.js.map +1 -0
  42. package/dist/slug.d.ts +5 -0
  43. package/dist/slug.js +30 -0
  44. package/dist/slug.js.map +1 -0
  45. 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
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
@@ -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"}