@milehimikey/em 1.21.0 → 1.22.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/.claude/agents/em-implementer.md +10 -0
- package/.claude/agents/em-validator.md +1 -0
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +7 -4
- package/.claude/skills/event-modeling-conform/reference/conform.md +82 -4
- package/.claude/skills/event-modeling-design/SKILL.md +5 -2
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/reference/engagement.md +1 -0
- package/.claude/skills/event-modeling-implement/SKILL.md +5 -3
- package/.claude/skills/event-modeling-implement/reference/implement.md +14 -0
- package/.claude/skills/event-modeling-review/SKILL.md +1 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +55 -6
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +26 -15
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/cli/api.d.ts +136 -9
- package/dist/cli/api.js +278 -27
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/bindings.d.ts +152 -0
- package/dist/cli/bindings.js +462 -0
- package/dist/cli/bindings.js.map +1 -0
- package/dist/cli/conformScope.d.ts +32 -0
- package/dist/cli/conformScope.js +68 -5
- package/dist/cli/conformScope.js.map +1 -1
- package/dist/cli/engagement.d.ts +24 -1
- package/dist/cli/engagement.js +50 -6
- package/dist/cli/engagement.js.map +1 -1
- package/dist/cli/engagementBrief.d.ts +127 -0
- package/dist/cli/engagementBrief.js +313 -0
- package/dist/cli/engagementBrief.js.map +1 -0
- package/dist/cli/engagementFile.d.ts +14 -0
- package/dist/cli/engagementFile.js +54 -0
- package/dist/cli/engagementFile.js.map +1 -1
- package/dist/cli/scopeInputs.js +2 -2
- package/dist/cli/scopeInputs.js.map +1 -1
- package/dist/cli/status.d.ts +5 -0
- package/dist/cli/status.js +1 -0
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/upgrade.js +9 -10
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli.js +143 -11
- package/dist/cli.js.map +1 -1
- package/dist/emit/api.d.ts +26 -1
- package/dist/emit/api.js +183 -25
- package/dist/emit/api.js.map +1 -1
- package/dist/emit/apiSurfaceJson.d.ts +3 -0
- package/dist/emit/apiSurfaceJson.js +43 -0
- package/dist/emit/apiSurfaceJson.js.map +1 -0
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +20 -1
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/engagementJson.d.ts +8 -3
- package/dist/emit/engagementJson.js +31 -4
- package/dist/emit/engagementJson.js.map +1 -1
- package/dist/emit/json.d.ts +42 -1
- package/dist/emit/json.js +65 -6
- package/dist/emit/json.js.map +1 -1
- package/dist/mcp/server.js +92 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/diff.d.ts +19 -1
- package/dist/model/diff.js +99 -0
- package/dist/model/diff.js.map +1 -1
- package/dist/model/model.d.ts +105 -9
- package/dist/model/model.js +116 -20
- package/dist/model/model.js.map +1 -1
- package/dist/model/refs.d.ts +4 -0
- package/dist/model/refs.js +35 -1
- package/dist/model/refs.js.map +1 -1
- package/dist/model/rules.d.ts +61 -0
- package/dist/model/rules.js +72 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/validate.js +137 -4
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +30 -0
- package/dist/parser/parser.js +67 -1
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/sliceDiagram.js +6 -0
- package/dist/render/sliceDiagram.js.map +1 -1
- package/dist/render/storylineDiagram.js +6 -0
- package/dist/render/storylineDiagram.js.map +1 -1
- package/dist/system/adaptation.d.ts +5 -2
- package/dist/system/adaptation.js +5 -3
- package/dist/system/adaptation.js.map +1 -1
- package/dist/templates.d.ts +4 -0
- package/dist/templates.js +29 -0
- package/dist/templates.js.map +1 -1
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/agents/em-implementer.md +10 -0
- package/plugin/agents/em-validator.md +1 -0
- package/plugin/shared/reference/em-dsl.md +55 -6
- package/plugin/shared/reference/operating-principles.md +26 -15
- package/plugin/skills/conform/SKILL.md +7 -4
- package/plugin/skills/conform/reference/conform.md +82 -4
- package/plugin/skills/design/SKILL.md +5 -2
- package/plugin/skills/discover/SKILL.md +1 -1
- package/plugin/skills/engagement/SKILL.md +1 -1
- package/plugin/skills/engagement/reference/engagement.md +1 -0
- package/plugin/skills/event-modeling/SKILL.md +1 -1
- package/plugin/skills/implement/SKILL.md +5 -3
- package/plugin/skills/implement/reference/implement.md +14 -0
- package/plugin/skills/review/SKILL.md +1 -1
package/dist/cli/api.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { NormalizedModel } from "../model/model.js";
|
|
1
2
|
import { Diagnostic } from "../model/validate.js";
|
|
2
3
|
import { compile } from "../pipeline.js";
|
|
3
|
-
import { ElementExport, FieldExport, TypeExport } from "../emit/json.js";
|
|
4
|
+
import { ElementExport, EnumExport, FieldExport, TypeExport } from "../emit/json.js";
|
|
5
|
+
import { RefsResult } from "../model/refs.js";
|
|
4
6
|
import { GitRunner } from "./diff-inputs.js";
|
|
5
7
|
/** `<modelDir>/contracts/<modelKey>.tsp` (R6), built from the model path as given. */
|
|
6
8
|
export declare function contractPathFor(modelFile: string, modelKey: string): string;
|
|
@@ -28,7 +30,7 @@ export interface ApiCheckChange {
|
|
|
28
30
|
}
|
|
29
31
|
type Role = "input" | "output";
|
|
30
32
|
interface SurfaceEntry {
|
|
31
|
-
/** command | event | view | type */
|
|
33
|
+
/** command | event | view | type | enum */
|
|
32
34
|
kind: string;
|
|
33
35
|
name: string;
|
|
34
36
|
/** Normalized identity key: kind + normalizeName(name). */
|
|
@@ -36,11 +38,13 @@ interface SurfaceEntry {
|
|
|
36
38
|
fields: FieldExport[];
|
|
37
39
|
renamedFrom: string[];
|
|
38
40
|
roles: Set<Role>;
|
|
39
|
-
/** Model-declared invariants (MIL-265), declaration order; `[]` for a type. */
|
|
41
|
+
/** Model-declared invariants (MIL-265), declaration order; `[]` for a type/enum. */
|
|
40
42
|
invariants: {
|
|
41
43
|
id: string;
|
|
42
44
|
rule: string | null;
|
|
43
45
|
}[];
|
|
46
|
+
/** GH #478: an enum's own members, in declaration order — `[]` for every non-enum kind. */
|
|
47
|
+
members: string[];
|
|
44
48
|
}
|
|
45
49
|
interface Surface {
|
|
46
50
|
entries: SurfaceEntry[];
|
|
@@ -49,14 +53,29 @@ interface Surface {
|
|
|
49
53
|
allElements: Set<string>;
|
|
50
54
|
}
|
|
51
55
|
/** Build the public surface of one compiled model: public commands (input), events and views
|
|
52
|
-
* (output), and every declared type reachable from them (carrying the roles that reach
|
|
56
|
+
* (output), and every declared type/enum reachable from them (carrying the roles that reach
|
|
57
|
+
* it — GH #478 extends the existing type-reach walk to also track an `enumRef` field,
|
|
58
|
+
* directly or through a reachable type's own fields, the same way `reachableEnumRefs`
|
|
59
|
+
* (emit/api.ts) tracks it for `em api generate`). `enums` defaults to `[]` so every existing
|
|
60
|
+
* caller (and the one test helper that built a `Surface` before enums existed) keeps compiling
|
|
61
|
+
* without a required third argument at a call site that never has one. */
|
|
53
62
|
export declare function surfaceOf(slices: {
|
|
54
63
|
elements: ElementExport[];
|
|
55
|
-
}[], types: TypeExport[]): Surface;
|
|
56
|
-
/** A field's type as the contract sees it: a declared type's
|
|
57
|
-
* normalized, with its array arity. */
|
|
58
|
-
export declare function typeLabel(f: Pick<FieldExport, "type" | "typeRef">): string;
|
|
59
|
-
|
|
64
|
+
}[], types: TypeExport[], enums?: EnumExport[]): Surface;
|
|
65
|
+
/** A field's type as the contract sees it: a declared type's, identity scalar's or enum's
|
|
66
|
+
* name, or the raw string, normalized, with its array arity. */
|
|
67
|
+
export declare function typeLabel(f: Pick<FieldExport, "type" | "typeRef" | "scalarRef" | "enumRef">): string;
|
|
68
|
+
/**
|
|
69
|
+
* GH #477: a `scalarRef` folds its `base` into the identity string too, not just `ref`/`array` —
|
|
70
|
+
* so a surviving scalar DECLARATION's base changing (`OrderId: uuid` -> `OrderId: string`)
|
|
71
|
+
* registers as a type change on every field that uses it, even though the scalar's own ref and
|
|
72
|
+
* the field's array arity are unchanged. This reuses `diffFields`' existing per-field
|
|
73
|
+
* `compare()` rather than adding a second comparison path — see its `scalarRef`-aware branch
|
|
74
|
+
* below for the breaking-change message this produces. GH #478: `enumRef` needs no equivalent
|
|
75
|
+
* fold — an enum's MEMBER changes are diffed on the enum's own `SurfaceEntry` (`diffEnumMembers`
|
|
76
|
+
* below), not per using field, so `ref`/`array` alone identify a field's enum reference here.
|
|
77
|
+
*/
|
|
78
|
+
export declare function typeIdentity(f: Pick<FieldExport, "type" | "typeRef" | "scalarRef" | "enumRef">): string;
|
|
60
79
|
/** Diff two public surfaces. `base === null` means the model did not exist at the base
|
|
61
80
|
* revision: every HEAD element is an additive addition. */
|
|
62
81
|
export declare function diffSurfaces(base: Surface | null, head: Surface): ApiCheckChange[];
|
|
@@ -83,8 +102,116 @@ export type ApiCheckOutcome = {
|
|
|
83
102
|
* model and compare to the committed contract file's text; (b) with `base`, compile the model
|
|
84
103
|
* at that revision (`resolveRevision`) and diff the public surfaces structurally. A model
|
|
85
104
|
* absent at `base` (new model) reports every public element as additive.
|
|
105
|
+
*
|
|
106
|
+
* GH #473: the HEAD side compiles through `compileModelFile` — the same storyline-aware loader
|
|
107
|
+
* `em api generate` uses (root plus every storyline model file it declares, GH #421) — so a
|
|
108
|
+
* public element declared in a storyline file is part of the surface instead of making the
|
|
109
|
+
* contract stale forever. `--base` has no multi-file-at-revision loader (`resolveRevision` reads
|
|
110
|
+
* one file at one rev); rather than silently diff a partial surface, a base revision that itself
|
|
111
|
+
* declares storyline files is refused.
|
|
86
112
|
*/
|
|
87
113
|
export declare function runApiCheck(file: string, base: string | undefined, runGit?: GitRunner): ApiCheckOutcome;
|
|
88
114
|
/** The text report: one status line, then one `additive:`/`breaking:` line per change. */
|
|
89
115
|
export declare function formatApiCheckText(r: ApiCheckReport): string[];
|
|
116
|
+
/** A public field's resolved type (R7's fixed table, a declared identity scalar, a declared
|
|
117
|
+
* enum, or a declared type), the same shape `resolvePublicType` (model/model.ts) computes —
|
|
118
|
+
* `scalar` carries the matched table entry's key and the TypeSpec core scalar `em api
|
|
119
|
+
* generate` emits for it; `declared` carries the reachable type's own name (see
|
|
120
|
+
* `ApiSurfaceType` below for its full field shape); `scalar-alias` (GH #477, schema 1.1)
|
|
121
|
+
* carries the declared identity scalar's own name in `tsType` (what actually appears in the
|
|
122
|
+
* generated contract, same convention `declared` already uses) and the table key it extends
|
|
123
|
+
* in `base` — `scalar`/`declared` leave `base` `null`; `enum` (GH #478, schema 1.2) carries
|
|
124
|
+
* the declared enum's own name in `tsType`, same convention — find its members under the
|
|
125
|
+
* report's own `enums` list. */
|
|
126
|
+
export interface ApiSurfaceFieldType {
|
|
127
|
+
kind: "scalar" | "declared" | "scalar-alias" | "enum";
|
|
128
|
+
scalar: string | null;
|
|
129
|
+
/** The `PUBLIC_SCALAR_TYPES` key a `scalar-alias` extends; `null` for every other kind. */
|
|
130
|
+
base: string | null;
|
|
131
|
+
tsType: string;
|
|
132
|
+
array: boolean;
|
|
133
|
+
}
|
|
134
|
+
export interface ApiSurfaceField {
|
|
135
|
+
name: string;
|
|
136
|
+
type: ApiSurfaceFieldType;
|
|
137
|
+
optional: boolean;
|
|
138
|
+
tag: boolean;
|
|
139
|
+
assigned: boolean;
|
|
140
|
+
renamedFrom: string[] | null;
|
|
141
|
+
}
|
|
142
|
+
/** One public command/event/view on the surface. `slug` is the kebab-slug of `name` — a stable,
|
|
143
|
+
* human-legible identity a companion tool can use without re-deriving it (same slugging `em api
|
|
144
|
+
* check`'s element ids use, minus the `<kind>.` prefix since commands/events/views are already
|
|
145
|
+
* separated into their own arrays here). */
|
|
146
|
+
export interface ApiSurfaceElement {
|
|
147
|
+
name: string;
|
|
148
|
+
slug: string;
|
|
149
|
+
fields: ApiSurfaceField[];
|
|
150
|
+
invariants: {
|
|
151
|
+
id: string;
|
|
152
|
+
rule: string | null;
|
|
153
|
+
}[] | null;
|
|
154
|
+
}
|
|
155
|
+
/** One declared `type` reachable from a public command/event/view's fields. */
|
|
156
|
+
export interface ApiSurfaceType {
|
|
157
|
+
name: string;
|
|
158
|
+
slug: string;
|
|
159
|
+
fields: ApiSurfaceField[];
|
|
160
|
+
}
|
|
161
|
+
/** One declared `scalar Name: base` identity alias reachable from a public command/event/view's
|
|
162
|
+
* fields (GH #477, schema 1.1) — `base` is the `PUBLIC_SCALAR_TYPES` key it extends. */
|
|
163
|
+
export interface ApiSurfaceScalar {
|
|
164
|
+
name: string;
|
|
165
|
+
slug: string;
|
|
166
|
+
base: string;
|
|
167
|
+
}
|
|
168
|
+
/** One declared `enum Name { member, … }` reachable from a public command/event/view's fields
|
|
169
|
+
* (GH #478, schema 1.2) — `members` in declaration order, raw (unquoted). */
|
|
170
|
+
export interface ApiSurfaceEnum {
|
|
171
|
+
name: string;
|
|
172
|
+
slug: string;
|
|
173
|
+
members: string[];
|
|
174
|
+
}
|
|
175
|
+
export interface ApiSurfaceReport {
|
|
176
|
+
file: string;
|
|
177
|
+
modelKey: string;
|
|
178
|
+
commands: ApiSurfaceElement[];
|
|
179
|
+
events: ApiSurfaceElement[];
|
|
180
|
+
views: ApiSurfaceElement[];
|
|
181
|
+
types: ApiSurfaceType[];
|
|
182
|
+
scalars: ApiSurfaceScalar[];
|
|
183
|
+
enums: ApiSurfaceEnum[];
|
|
184
|
+
}
|
|
185
|
+
export type ApiSurfaceOutcome = {
|
|
186
|
+
ok: true;
|
|
187
|
+
report: ApiSurfaceReport;
|
|
188
|
+
diagnostics: Diagnostic[];
|
|
189
|
+
} | {
|
|
190
|
+
ok: false;
|
|
191
|
+
message: string;
|
|
192
|
+
diagnostics: Diagnostic[];
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Build the `em api surface` report for an already-validated (error-free) model: the `public`
|
|
196
|
+
* commands/events/views (`publicSurfaceOf`, same as `em api generate`/`em api check`) plus every
|
|
197
|
+
* declared type reachable from their fields (`reachableTypeRefs`, same walk `em api generate`'s
|
|
198
|
+
* contract body uses), every declared identity scalar reachable the same way
|
|
199
|
+
* (`reachableScalarRefs`, GH #477), and every declared enum reachable the same way
|
|
200
|
+
* (`reachableEnumRefs`, GH #478), each field's type resolved through `resolvePublicType` — the
|
|
201
|
+
* one notion of the public surface every em surface shares, so a companion tool never re-derives
|
|
202
|
+
* it from `em export --json` and risks drifting from it.
|
|
203
|
+
*/
|
|
204
|
+
export declare function buildApiSurfaceReport(model: NormalizedModel, refs: RefsResult, diagnostics: Diagnostic[], source: string, file: string): ApiSurfaceReport;
|
|
205
|
+
/**
|
|
206
|
+
* Run `em api surface <file>`: compile, refuse on errors (same posture as `em api generate` —
|
|
207
|
+
* `public-field-type-unresolved` included), build the report. Self-contained (reads + compiles
|
|
208
|
+
* `file` itself) so the MCP `api_surface` tool can call it with just a path, same shape as
|
|
209
|
+
* `runApiCheck`.
|
|
210
|
+
*
|
|
211
|
+
* GH #473: compiles through `compileModelFile` (storyline-aware, GH #421) — a single-file
|
|
212
|
+
* `compile()` here silently omitted every public element declared in a storyline file.
|
|
213
|
+
*/
|
|
214
|
+
export declare function runApiSurface(file: string): ApiSurfaceOutcome;
|
|
215
|
+
/** The text listing: one line per command/event/view/type, with its field count. */
|
|
216
|
+
export declare function formatApiSurfaceText(r: ApiSurfaceReport): string[];
|
|
90
217
|
export {};
|
package/dist/cli/api.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// `em api generate | check` (MIL-237): the contract path convention
|
|
3
|
-
// "is the committed contract current" text comparison,
|
|
4
|
-
// that annotates each change additive or breaking (briefing R8/R12)
|
|
2
|
+
// `em api generate | check | surface` (MIL-237, GH #448): the contract path convention
|
|
3
|
+
// (briefing R6), the "is the committed contract current" text comparison, the structural
|
|
4
|
+
// public-surface diff that annotates each change additive or breaking (briefing R8/R12), and the
|
|
5
|
+
// public surface as a structured document for companion tools (GH #448). Pure where it can be —
|
|
5
6
|
// fs/git reads go through injectable readers so the diff and the classification are unit-
|
|
6
|
-
// testable without a repository; cli.ts and the MCP `api_check`
|
|
7
|
+
// testable without a repository; cli.ts and the MCP `api_check`/`api_surface` tools share
|
|
8
|
+
// `runApiCheck`/`runApiSurface`.
|
|
7
9
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
10
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
9
|
-
import { normalizeName } from "../model/model.js";
|
|
11
|
+
import { normalizeName, resolvePublicType } from "../model/model.js";
|
|
10
12
|
import { computeModelKey } from "../model/qualifiedRef.js";
|
|
11
13
|
import { hasErrors } from "../model/validate.js";
|
|
12
|
-
import { compile } from "../pipeline.js";
|
|
14
|
+
import { compile, compileModelFile } from "../pipeline.js";
|
|
15
|
+
import { ModelLoadError } from "../model/loadModel.js";
|
|
13
16
|
import { ParseError } from "../parser/parser.js";
|
|
14
|
-
import { buildApiContract, publicSurfaceOf } from "../emit/api.js";
|
|
17
|
+
import { buildApiContract, publicSurfaceOf, reachableEnumRefs, reachableScalarRefs, reachableTypeRefs } from "../emit/api.js";
|
|
15
18
|
import { buildExportDoc } from "../emit/json.js";
|
|
16
19
|
import { kebabSlug } from "../util/slug.js";
|
|
17
20
|
import { realGit, resolveRevision } from "./diff-inputs.js";
|
|
@@ -33,14 +36,25 @@ export function generateContract(file, source, compiled, outPath) {
|
|
|
33
36
|
}
|
|
34
37
|
const keyOf = (kind, name) => `${kind}:${normalizeName(name)}`;
|
|
35
38
|
/** Build the public surface of one compiled model: public commands (input), events and views
|
|
36
|
-
* (output), and every declared type reachable from them (carrying the roles that reach
|
|
37
|
-
|
|
39
|
+
* (output), and every declared type/enum reachable from them (carrying the roles that reach
|
|
40
|
+
* it — GH #478 extends the existing type-reach walk to also track an `enumRef` field,
|
|
41
|
+
* directly or through a reachable type's own fields, the same way `reachableEnumRefs`
|
|
42
|
+
* (emit/api.ts) tracks it for `em api generate`). `enums` defaults to `[]` so every existing
|
|
43
|
+
* caller (and the one test helper that built a `Surface` before enums existed) keeps compiling
|
|
44
|
+
* without a required third argument at a call site that never has one. */
|
|
45
|
+
export function surfaceOf(slices, types, enums = []) {
|
|
38
46
|
const s = publicSurfaceOf(slices);
|
|
39
47
|
const entries = [];
|
|
40
48
|
const typesByRef = new Map(types.map((t) => [t.ref, t]));
|
|
41
49
|
const typeRoles = new Map();
|
|
50
|
+
const enumRoles = new Map();
|
|
42
51
|
const reach = (fields, role) => {
|
|
43
52
|
for (const f of fields ?? []) {
|
|
53
|
+
if (f.enumRef) {
|
|
54
|
+
const roles = enumRoles.get(f.enumRef.ref) ?? new Set();
|
|
55
|
+
roles.add(role);
|
|
56
|
+
enumRoles.set(f.enumRef.ref, roles);
|
|
57
|
+
}
|
|
44
58
|
if (!f.typeRef)
|
|
45
59
|
continue;
|
|
46
60
|
const roles = typeRoles.get(f.typeRef.ref) ?? new Set();
|
|
@@ -60,6 +74,7 @@ export function surfaceOf(slices, types) {
|
|
|
60
74
|
renamedFrom: el.renamedFrom ?? [],
|
|
61
75
|
roles: new Set([role]),
|
|
62
76
|
invariants: el.invariants ?? [],
|
|
77
|
+
members: [],
|
|
63
78
|
});
|
|
64
79
|
reach(el.fields, role);
|
|
65
80
|
};
|
|
@@ -73,7 +88,13 @@ export function surfaceOf(slices, types) {
|
|
|
73
88
|
const roles = typeRoles.get(t.ref);
|
|
74
89
|
if (!roles)
|
|
75
90
|
continue;
|
|
76
|
-
entries.push({ kind: "type", name: t.name, key: keyOf("type", t.name), fields: t.fields, renamedFrom: [], roles, invariants: [] });
|
|
91
|
+
entries.push({ kind: "type", name: t.name, key: keyOf("type", t.name), fields: t.fields, renamedFrom: [], roles, invariants: [], members: [] });
|
|
92
|
+
}
|
|
93
|
+
for (const e of enums) {
|
|
94
|
+
const roles = enumRoles.get(e.ref);
|
|
95
|
+
if (!roles)
|
|
96
|
+
continue;
|
|
97
|
+
entries.push({ kind: "enum", name: e.name, key: keyOf("enum", e.name), fields: [], renamedFrom: [], roles, invariants: [], members: e.members });
|
|
77
98
|
}
|
|
78
99
|
const allElements = new Set();
|
|
79
100
|
for (const slice of slices)
|
|
@@ -81,18 +102,38 @@ export function surfaceOf(slices, types) {
|
|
|
81
102
|
allElements.add(keyOf(el.kind, el.name));
|
|
82
103
|
for (const t of types)
|
|
83
104
|
allElements.add(keyOf("type", t.name));
|
|
105
|
+
for (const e of enums)
|
|
106
|
+
allElements.add(keyOf("enum", e.name));
|
|
84
107
|
return { entries, allElements };
|
|
85
108
|
}
|
|
86
|
-
/** A field's type as the contract sees it: a declared type's
|
|
87
|
-
* normalized, with its array arity. */
|
|
109
|
+
/** A field's type as the contract sees it: a declared type's, identity scalar's or enum's
|
|
110
|
+
* name, or the raw string, normalized, with its array arity. */
|
|
88
111
|
export function typeLabel(f) {
|
|
89
112
|
if (f.typeRef)
|
|
90
113
|
return `${f.typeRef.name}${f.typeRef.array ? "[]" : ""}`;
|
|
114
|
+
if (f.scalarRef)
|
|
115
|
+
return `${f.scalarRef.name}${f.scalarRef.array ? "[]" : ""}`;
|
|
116
|
+
if (f.enumRef)
|
|
117
|
+
return `${f.enumRef.name}${f.enumRef.array ? "[]" : ""}`;
|
|
91
118
|
return f.type ?? "(none)";
|
|
92
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* GH #477: a `scalarRef` folds its `base` into the identity string too, not just `ref`/`array` —
|
|
122
|
+
* so a surviving scalar DECLARATION's base changing (`OrderId: uuid` -> `OrderId: string`)
|
|
123
|
+
* registers as a type change on every field that uses it, even though the scalar's own ref and
|
|
124
|
+
* the field's array arity are unchanged. This reuses `diffFields`' existing per-field
|
|
125
|
+
* `compare()` rather than adding a second comparison path — see its `scalarRef`-aware branch
|
|
126
|
+
* below for the breaking-change message this produces. GH #478: `enumRef` needs no equivalent
|
|
127
|
+
* fold — an enum's MEMBER changes are diffed on the enum's own `SurfaceEntry` (`diffEnumMembers`
|
|
128
|
+
* below), not per using field, so `ref`/`array` alone identify a field's enum reference here.
|
|
129
|
+
*/
|
|
93
130
|
export function typeIdentity(f) {
|
|
94
131
|
if (f.typeRef)
|
|
95
132
|
return `ref:${f.typeRef.ref}:${f.typeRef.array}`;
|
|
133
|
+
if (f.scalarRef)
|
|
134
|
+
return `ref:${f.scalarRef.ref}:${f.scalarRef.array}:${f.scalarRef.base}`;
|
|
135
|
+
if (f.enumRef)
|
|
136
|
+
return `enum-ref:${f.enumRef.ref}:${f.enumRef.array}`;
|
|
96
137
|
return `raw:${(f.type ?? "").replace(/\s+/g, "").toLowerCase()}`;
|
|
97
138
|
}
|
|
98
139
|
function diffFields(base, head, element, out) {
|
|
@@ -117,7 +158,25 @@ function diffFields(base, head, element, out) {
|
|
|
117
158
|
}
|
|
118
159
|
const compare = (b, h) => {
|
|
119
160
|
if (typeIdentity(b) !== typeIdentity(h)) {
|
|
120
|
-
|
|
161
|
+
// GH #477: same scalar declaration, same array arity, different `base` — the field's own
|
|
162
|
+
// reference didn't change, but what it resolves to on the wire did (every consumer
|
|
163
|
+
// deserializing this field as the old base now gets the new one). Phrased distinctly from
|
|
164
|
+
// an ordinary type change so the report doesn't read as "OrderId -> OrderId" (no visible
|
|
165
|
+
// difference) when the real story is the scalar's declaration.
|
|
166
|
+
if (b.scalarRef && h.scalarRef &&
|
|
167
|
+
b.scalarRef.ref === h.scalarRef.ref &&
|
|
168
|
+
b.scalarRef.array === h.scalarRef.array &&
|
|
169
|
+
b.scalarRef.base !== h.scalarRef.base) {
|
|
170
|
+
out.push({
|
|
171
|
+
kind: "breaking",
|
|
172
|
+
element,
|
|
173
|
+
field: h.name,
|
|
174
|
+
what: `scalar "${h.scalarRef.name}" base changed ${b.scalarRef.base} → ${h.scalarRef.base}`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
out.push({ kind: "breaking", element, field: h.name, what: `type changed ${typeLabel(b)} → ${typeLabel(h)}` });
|
|
179
|
+
}
|
|
121
180
|
}
|
|
122
181
|
if (b.optional !== h.optional) {
|
|
123
182
|
if (h.optional) {
|
|
@@ -157,6 +216,37 @@ function diffFields(base, head, element, out) {
|
|
|
157
216
|
out.push({ kind, element, field: h.name, what: `added (${required ? "required" : "optional"})` });
|
|
158
217
|
}
|
|
159
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* GH #478: an enum's own member set, base vs HEAD, diffed by role (the same `isInput`/
|
|
221
|
+
* `isOutput` union `diffFields` computes above) — the issue's pinned severity matrix:
|
|
222
|
+
* - member REMOVED (or, since there's no rename clause, the removal half of what reads as a
|
|
223
|
+
* rename) is breaking wherever the enum reaches an OUTPUT (an existing receiver may still
|
|
224
|
+
* expect that known value) — not conditioned on input reach at all.
|
|
225
|
+
* - member ADDED is breaking wherever the enum reaches an INPUT (existing senders are
|
|
226
|
+
* unaffected; a strict receiver built against the old member set rejects the new value) —
|
|
227
|
+
* additive on a PURE output (`isInput` false): a receiver tolerates an unknown value.
|
|
228
|
+
* A rename is never detected as such (same "no rename detection" posture `em diff` already
|
|
229
|
+
* takes for a `type`/element rename, model/diff.ts) — it reads as remove+add, each half scored
|
|
230
|
+
* independently by the rules above. Base order first (removals), then HEAD-only (additions),
|
|
231
|
+
* same convention `diffFields` uses.
|
|
232
|
+
*/
|
|
233
|
+
function diffEnumMembers(base, head, element, out) {
|
|
234
|
+
const roles = new Set([...base.roles, ...head.roles]);
|
|
235
|
+
const isInput = roles.has("input");
|
|
236
|
+
const isOutput = roles.has("output");
|
|
237
|
+
const baseKeys = new Set(base.members.map(normalizeName));
|
|
238
|
+
const headKeys = new Set(head.members.map(normalizeName));
|
|
239
|
+
for (const b of base.members) {
|
|
240
|
+
if (headKeys.has(normalizeName(b)))
|
|
241
|
+
continue;
|
|
242
|
+
out.push({ kind: isOutput ? "breaking" : "additive", element, field: null, what: `member "${b}" removed` });
|
|
243
|
+
}
|
|
244
|
+
for (const h of head.members) {
|
|
245
|
+
if (baseKeys.has(normalizeName(h)))
|
|
246
|
+
continue;
|
|
247
|
+
out.push({ kind: isInput ? "breaking" : "additive", element, field: null, what: `member "${h}" added` });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
160
250
|
/** MIL-265: a public element's invariants, base vs HEAD, matched by ID. Added or rule text
|
|
161
251
|
* changed = breaking (the contract got tighter); removed = additive (looser). HEAD order for
|
|
162
252
|
* additions/changes, then base order for removals. */
|
|
@@ -192,7 +282,10 @@ export function diffSurfaces(base, head) {
|
|
|
192
282
|
const same = baseByKey.get(h.key);
|
|
193
283
|
if (same) {
|
|
194
284
|
matchedBase.add(same.key);
|
|
195
|
-
|
|
285
|
+
if (h.kind === "enum")
|
|
286
|
+
diffEnumMembers(same, h, element, out);
|
|
287
|
+
else
|
|
288
|
+
diffFields(same, h, element, out);
|
|
196
289
|
diffInvariants(same, h, element, out);
|
|
197
290
|
continue;
|
|
198
291
|
}
|
|
@@ -202,11 +295,14 @@ export function diffSurfaces(base, head) {
|
|
|
202
295
|
if (renamedFrom) {
|
|
203
296
|
matchedBase.add(renamedFrom.key);
|
|
204
297
|
out.push({ kind: "breaking", element, field: null, what: `renamed from "${renamedFrom.name}"` });
|
|
205
|
-
|
|
298
|
+
if (h.kind === "enum")
|
|
299
|
+
diffEnumMembers(renamedFrom, h, element, out);
|
|
300
|
+
else
|
|
301
|
+
diffFields(renamedFrom, h, element, out);
|
|
206
302
|
diffInvariants(renamedFrom, h, element, out);
|
|
207
303
|
continue;
|
|
208
304
|
}
|
|
209
|
-
if (h.kind !== "type" && base.allElements.has(h.key)) {
|
|
305
|
+
if (h.kind !== "type" && h.kind !== "enum" && base.allElements.has(h.key)) {
|
|
210
306
|
out.push({ kind: "additive", element, field: null, what: "marked public" });
|
|
211
307
|
}
|
|
212
308
|
else {
|
|
@@ -217,7 +313,7 @@ export function diffSurfaces(base, head) {
|
|
|
217
313
|
if (matchedBase.has(b.key))
|
|
218
314
|
continue;
|
|
219
315
|
const element = elementId(b);
|
|
220
|
-
if (b.kind !== "type" && head.allElements.has(b.key)) {
|
|
316
|
+
if (b.kind !== "type" && b.kind !== "enum" && head.allElements.has(b.key)) {
|
|
221
317
|
out.push({ kind: "breaking", element, field: null, what: "`public` dropped" });
|
|
222
318
|
}
|
|
223
319
|
else {
|
|
@@ -231,28 +327,33 @@ export function diffSurfaces(base, head) {
|
|
|
231
327
|
* model and compare to the committed contract file's text; (b) with `base`, compile the model
|
|
232
328
|
* at that revision (`resolveRevision`) and diff the public surfaces structurally. A model
|
|
233
329
|
* absent at `base` (new model) reports every public element as additive.
|
|
330
|
+
*
|
|
331
|
+
* GH #473: the HEAD side compiles through `compileModelFile` — the same storyline-aware loader
|
|
332
|
+
* `em api generate` uses (root plus every storyline model file it declares, GH #421) — so a
|
|
333
|
+
* public element declared in a storyline file is part of the surface instead of making the
|
|
334
|
+
* contract stale forever. `--base` has no multi-file-at-revision loader (`resolveRevision` reads
|
|
335
|
+
* one file at one rev); rather than silently diff a partial surface, a base revision that itself
|
|
336
|
+
* declares storyline files is refused.
|
|
234
337
|
*/
|
|
235
338
|
export function runApiCheck(file, base, runGit = realGit) {
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
source = readFileSync(file, "utf8");
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
339
|
+
if (!existsSync(file)) {
|
|
241
340
|
return { ok: false, message: `em api check: cannot read ${file}`, diagnostics: [] };
|
|
242
341
|
}
|
|
243
342
|
let head;
|
|
244
343
|
try {
|
|
245
|
-
head =
|
|
344
|
+
head = compileModelFile(file);
|
|
246
345
|
}
|
|
247
346
|
catch (e) {
|
|
248
347
|
if (e instanceof ParseError)
|
|
249
348
|
return { ok: false, message: `em api check: parse error in ${file} ${e.message}`, diagnostics: [] };
|
|
349
|
+
if (e instanceof ModelLoadError)
|
|
350
|
+
return { ok: false, message: `em api check: ${e.message}`, diagnostics: [] };
|
|
250
351
|
throw e;
|
|
251
352
|
}
|
|
252
353
|
if (hasErrors(head.diagnostics)) {
|
|
253
354
|
return { ok: false, message: "em api check: not checking — fix the errors above", diagnostics: head.diagnostics };
|
|
254
355
|
}
|
|
255
|
-
const generated = generateContract(file, source, head);
|
|
356
|
+
const generated = generateContract(file, head.source, head);
|
|
256
357
|
const missing = !existsSync(generated.contractPath);
|
|
257
358
|
const current = !missing && readFileSync(generated.contractPath, "utf8") === generated.text;
|
|
258
359
|
let changes = [];
|
|
@@ -265,8 +366,8 @@ export function runApiCheck(file, base, runGit = realGit) {
|
|
|
265
366
|
if (verify.status !== 0) {
|
|
266
367
|
return { ok: false, message: `em api check: unknown revision "${base}" (needed for --base)`, diagnostics: head.diagnostics };
|
|
267
368
|
}
|
|
268
|
-
const headDoc = buildExportDoc(head.model, head.refs, head.diagnostics, source, file).doc;
|
|
269
|
-
const headSurface = surfaceOf(headDoc.model.slices, headDoc.model.types);
|
|
369
|
+
const headDoc = buildExportDoc(head.model, head.refs, head.diagnostics, head.source, file).doc;
|
|
370
|
+
const headSurface = surfaceOf(headDoc.model.slices, headDoc.model.types, headDoc.model.enums);
|
|
270
371
|
const atBase = resolveRevision(file, base, runGit);
|
|
271
372
|
let baseSurface = null;
|
|
272
373
|
if (atBase.ok) {
|
|
@@ -280,10 +381,20 @@ export function runApiCheck(file, base, runGit = realGit) {
|
|
|
280
381
|
}
|
|
281
382
|
throw e;
|
|
282
383
|
}
|
|
384
|
+
// GH #473: resolveRevision reads one file at one revision — there is no multi-file-at-
|
|
385
|
+
// revision loader yet. A base root that itself declares storyline files would silently
|
|
386
|
+
// diff a surface missing every element those files carry, so refuse loudly instead.
|
|
387
|
+
if ((baseCompiled.model.storylines ?? []).some((st) => st.file !== undefined)) {
|
|
388
|
+
return {
|
|
389
|
+
ok: false,
|
|
390
|
+
message: "em api check: --base does not yet support storyline models (the base revision's storyline files cannot be loaded) — see GH #475",
|
|
391
|
+
diagnostics: head.diagnostics,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
283
394
|
// Base-side validate errors (e.g. a pre-1.14 free-text public type) do not block the
|
|
284
395
|
// structural diff — the diff reads shapes, not strictness.
|
|
285
396
|
const baseDoc = buildExportDoc(baseCompiled.model, baseCompiled.refs, baseCompiled.diagnostics, atBase.content, file).doc;
|
|
286
|
-
baseSurface = surfaceOf(baseDoc.model.slices, baseDoc.model.types);
|
|
397
|
+
baseSurface = surfaceOf(baseDoc.model.slices, baseDoc.model.types, baseDoc.model.enums);
|
|
287
398
|
}
|
|
288
399
|
else if (!/is not tracked by git/.test(atBase.message)) {
|
|
289
400
|
return { ok: false, message: atBase.message.replace(/^em diff:/, "em api check:"), diagnostics: head.diagnostics };
|
|
@@ -315,4 +426,144 @@ export function formatApiCheckText(r) {
|
|
|
315
426
|
}
|
|
316
427
|
return lines;
|
|
317
428
|
}
|
|
429
|
+
/** Resolve one field's public type exactly as `em api generate`'s own `fieldType()` does
|
|
430
|
+
* (emit/api.ts) — declared types win (same precedence `resolvePublicType` already gives), then
|
|
431
|
+
* a declared identity scalar (GH #477), then a declared enum (GH #478), else the fixed scalar
|
|
432
|
+
* table. A model with errors never reaches here (both CLI callers refuse first), so an
|
|
433
|
+
* unresolved type is a programming error, not a user-facing case — same posture as
|
|
434
|
+
* `buildApiContract`'s own throw. */
|
|
435
|
+
function surfaceFieldType(f, typesByName, scalarsByName, enumsByName, owner) {
|
|
436
|
+
const r = resolvePublicType(f.type ?? undefined, typesByName, scalarsByName, enumsByName);
|
|
437
|
+
if (!r) {
|
|
438
|
+
throw new Error(`em api surface: ${owner} field "${f.name}" has no public type — em validate's ` +
|
|
439
|
+
"public-field-type-unresolved should have refused this model");
|
|
440
|
+
}
|
|
441
|
+
if (r.kind === "scalar")
|
|
442
|
+
return { kind: "scalar", scalar: r.scalar, base: null, tsType: r.tsType, array: r.array };
|
|
443
|
+
if (r.kind === "identity") {
|
|
444
|
+
return { kind: "scalar-alias", scalar: null, base: r.base, tsType: r.scalarDecl.name, array: r.array };
|
|
445
|
+
}
|
|
446
|
+
if (r.kind === "enum")
|
|
447
|
+
return { kind: "enum", scalar: null, base: null, tsType: r.enumDecl.name, array: r.array };
|
|
448
|
+
return { kind: "declared", scalar: null, base: null, tsType: r.typeDecl.name, array: r.array };
|
|
449
|
+
}
|
|
450
|
+
function surfaceField(f, typesByName, scalarsByName, enumsByName, owner) {
|
|
451
|
+
return {
|
|
452
|
+
name: f.name,
|
|
453
|
+
type: surfaceFieldType(f, typesByName, scalarsByName, enumsByName, owner),
|
|
454
|
+
optional: f.optional,
|
|
455
|
+
tag: f.tag,
|
|
456
|
+
assigned: f.assigned,
|
|
457
|
+
renamedFrom: f.renamedFrom,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function surfaceElement(el, typesByName, scalarsByName, enumsByName) {
|
|
461
|
+
return {
|
|
462
|
+
name: el.name,
|
|
463
|
+
slug: kebabSlug(el.name),
|
|
464
|
+
fields: (el.fields ?? []).map((f) => surfaceField(f, typesByName, scalarsByName, enumsByName, `${el.kind} "${el.name}"`)),
|
|
465
|
+
invariants: el.invariants ?? null,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Build the `em api surface` report for an already-validated (error-free) model: the `public`
|
|
470
|
+
* commands/events/views (`publicSurfaceOf`, same as `em api generate`/`em api check`) plus every
|
|
471
|
+
* declared type reachable from their fields (`reachableTypeRefs`, same walk `em api generate`'s
|
|
472
|
+
* contract body uses), every declared identity scalar reachable the same way
|
|
473
|
+
* (`reachableScalarRefs`, GH #477), and every declared enum reachable the same way
|
|
474
|
+
* (`reachableEnumRefs`, GH #478), each field's type resolved through `resolvePublicType` — the
|
|
475
|
+
* one notion of the public surface every em surface shares, so a companion tool never re-derives
|
|
476
|
+
* it from `em export --json` and risks drifting from it.
|
|
477
|
+
*/
|
|
478
|
+
export function buildApiSurfaceReport(model, refs, diagnostics, source, file) {
|
|
479
|
+
const { doc } = buildExportDoc(model, refs, diagnostics, source, file);
|
|
480
|
+
const surface = publicSurfaceOf(doc.model.slices);
|
|
481
|
+
const typesByRef = new Map(doc.model.types.map((t) => [t.ref, t]));
|
|
482
|
+
const reachable = reachableTypeRefs([...surface.commands, ...surface.events, ...surface.views], typesByRef);
|
|
483
|
+
const reachableScalars = reachableScalarRefs([...surface.commands, ...surface.events, ...surface.views], typesByRef);
|
|
484
|
+
const reachableEnums = reachableEnumRefs([...surface.commands, ...surface.events, ...surface.views], typesByRef);
|
|
485
|
+
return {
|
|
486
|
+
file,
|
|
487
|
+
modelKey: doc.model.key,
|
|
488
|
+
commands: surface.commands.map((el) => surfaceElement(el, model.typesByName, model.scalarsByName, model.enumsByName)),
|
|
489
|
+
events: surface.events.map((el) => surfaceElement(el, model.typesByName, model.scalarsByName, model.enumsByName)),
|
|
490
|
+
views: surface.views.map((el) => surfaceElement(el, model.typesByName, model.scalarsByName, model.enumsByName)),
|
|
491
|
+
types: doc.model.types
|
|
492
|
+
.filter((t) => reachable.has(t.ref))
|
|
493
|
+
.map((t) => ({
|
|
494
|
+
name: t.name,
|
|
495
|
+
slug: kebabSlug(t.name),
|
|
496
|
+
fields: t.fields.map((f) => surfaceField(f, model.typesByName, model.scalarsByName, model.enumsByName, `type "${t.name}"`)),
|
|
497
|
+
})),
|
|
498
|
+
scalars: doc.model.scalars
|
|
499
|
+
.filter((s) => reachableScalars.has(s.ref))
|
|
500
|
+
.map((s) => ({ name: s.name, slug: kebabSlug(s.name), base: s.base })),
|
|
501
|
+
enums: doc.model.enums
|
|
502
|
+
.filter((e) => reachableEnums.has(e.ref))
|
|
503
|
+
.map((e) => ({ name: e.name, slug: kebabSlug(e.name), members: e.members })),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Run `em api surface <file>`: compile, refuse on errors (same posture as `em api generate` —
|
|
508
|
+
* `public-field-type-unresolved` included), build the report. Self-contained (reads + compiles
|
|
509
|
+
* `file` itself) so the MCP `api_surface` tool can call it with just a path, same shape as
|
|
510
|
+
* `runApiCheck`.
|
|
511
|
+
*
|
|
512
|
+
* GH #473: compiles through `compileModelFile` (storyline-aware, GH #421) — a single-file
|
|
513
|
+
* `compile()` here silently omitted every public element declared in a storyline file.
|
|
514
|
+
*/
|
|
515
|
+
export function runApiSurface(file) {
|
|
516
|
+
if (!existsSync(file)) {
|
|
517
|
+
return { ok: false, message: `em api surface: cannot read ${file}`, diagnostics: [] };
|
|
518
|
+
}
|
|
519
|
+
let compiled;
|
|
520
|
+
try {
|
|
521
|
+
compiled = compileModelFile(file);
|
|
522
|
+
}
|
|
523
|
+
catch (e) {
|
|
524
|
+
if (e instanceof ParseError) {
|
|
525
|
+
return { ok: false, message: `em api surface: parse error in ${file} ${e.message}`, diagnostics: [] };
|
|
526
|
+
}
|
|
527
|
+
if (e instanceof ModelLoadError) {
|
|
528
|
+
return { ok: false, message: `em api surface: ${e.message}`, diagnostics: [] };
|
|
529
|
+
}
|
|
530
|
+
throw e;
|
|
531
|
+
}
|
|
532
|
+
if (hasErrors(compiled.diagnostics)) {
|
|
533
|
+
return { ok: false, message: "em api surface: not generating — fix the errors above", diagnostics: compiled.diagnostics };
|
|
534
|
+
}
|
|
535
|
+
const report = buildApiSurfaceReport(compiled.model, compiled.refs, compiled.diagnostics, compiled.source, file);
|
|
536
|
+
return { ok: true, report, diagnostics: compiled.diagnostics };
|
|
537
|
+
}
|
|
538
|
+
/** The text listing: one line per command/event/view/type, with its field count. */
|
|
539
|
+
export function formatApiSurfaceText(r) {
|
|
540
|
+
const lines = [`model ${r.modelKey} (${r.file})`];
|
|
541
|
+
const fieldCount = (n) => `${n} field${n === 1 ? "" : "s"}`;
|
|
542
|
+
const section = (label, elements) => {
|
|
543
|
+
if (elements.length === 0)
|
|
544
|
+
return;
|
|
545
|
+
lines.push(`${label}:`);
|
|
546
|
+
for (const el of elements)
|
|
547
|
+
lines.push(` ${el.slug} (${fieldCount(el.fields.length)})`);
|
|
548
|
+
};
|
|
549
|
+
section("commands", r.commands);
|
|
550
|
+
section("events", r.events);
|
|
551
|
+
section("views", r.views);
|
|
552
|
+
section("types", r.types);
|
|
553
|
+
if (r.scalars.length > 0) {
|
|
554
|
+
lines.push("scalars:");
|
|
555
|
+
for (const s of r.scalars)
|
|
556
|
+
lines.push(` ${s.slug} extends ${s.base}`);
|
|
557
|
+
}
|
|
558
|
+
if (r.enums.length > 0) {
|
|
559
|
+
lines.push("enums:");
|
|
560
|
+
for (const e of r.enums)
|
|
561
|
+
lines.push(` ${e.slug} { ${e.members.join(", ")} }`);
|
|
562
|
+
}
|
|
563
|
+
if (r.commands.length === 0 && r.events.length === 0 && r.views.length === 0 &&
|
|
564
|
+
r.types.length === 0 && r.scalars.length === 0 && r.enums.length === 0) {
|
|
565
|
+
lines.push("(no public surface)");
|
|
566
|
+
}
|
|
567
|
+
return lines;
|
|
568
|
+
}
|
|
318
569
|
//# sourceMappingURL=api.js.map
|