@glubean/sdk 0.2.0 → 0.2.2
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/dist/configure/http.d.ts +25 -0
- package/dist/configure/http.d.ts.map +1 -0
- package/dist/configure/http.js +88 -0
- package/dist/configure/http.js.map +1 -0
- package/dist/configure/index.d.ts +78 -0
- package/dist/configure/index.d.ts.map +1 -0
- package/dist/configure/index.js +78 -0
- package/dist/configure/index.js.map +1 -0
- package/dist/configure/plugin.d.ts +23 -0
- package/dist/configure/plugin.d.ts.map +1 -0
- package/dist/configure/plugin.js +81 -0
- package/dist/configure/plugin.js.map +1 -0
- package/dist/configure/runtime.d.ts +24 -0
- package/dist/configure/runtime.d.ts.map +1 -0
- package/dist/configure/runtime.js +45 -0
- package/dist/configure/runtime.js.map +1 -0
- package/dist/configure/template.d.ts +22 -0
- package/dist/configure/template.d.ts.map +1 -0
- package/dist/configure/template.js +34 -0
- package/dist/configure/template.js.map +1 -0
- package/dist/configure/vars.d.ts +20 -0
- package/dist/configure/vars.d.ts.map +1 -0
- package/dist/configure/vars.js +48 -0
- package/dist/configure/vars.js.map +1 -0
- package/dist/configure.d.ts +2 -150
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +2 -562
- package/dist/configure.js.map +1 -1
- package/dist/contract-artifacts.d.ts +268 -0
- package/dist/contract-artifacts.d.ts.map +1 -0
- package/dist/contract-artifacts.js +402 -0
- package/dist/contract-artifacts.js.map +1 -0
- package/dist/contract-core.d.ts +33 -1
- package/dist/contract-core.d.ts.map +1 -1
- package/dist/contract-core.js +51 -2
- package/dist/contract-core.js.map +1 -1
- package/dist/contract-http/adapter.d.ts.map +1 -1
- package/dist/contract-http/adapter.js +22 -7
- package/dist/contract-http/adapter.js.map +1 -1
- package/dist/contract-http/factory.d.ts.map +1 -1
- package/dist/contract-http/factory.js +13 -14
- package/dist/contract-http/factory.js.map +1 -1
- package/dist/contract-http/index.d.ts +4 -3
- package/dist/contract-http/index.d.ts.map +1 -1
- package/dist/contract-http/index.js +4 -3
- package/dist/contract-http/index.js.map +1 -1
- package/dist/contract-http/openapi.d.ts +56 -7
- package/dist/contract-http/openapi.d.ts.map +1 -1
- package/dist/contract-http/openapi.js +371 -21
- package/dist/contract-http/openapi.js.map +1 -1
- package/dist/contract-http/types.d.ts +2 -13
- package/dist/contract-http/types.d.ts.map +1 -1
- package/dist/contract-types.d.ts +59 -10
- package/dist/contract-types.d.ts.map +1 -1
- package/dist/expect.d.ts +13 -0
- package/dist/expect.d.ts.map +1 -1
- package/dist/expect.js +18 -0
- package/dist/expect.js.map +1 -1
- package/dist/index.d.ts +61 -518
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -835
- package/dist/index.js.map +1 -1
- package/dist/install-plugin.d.ts +94 -0
- package/dist/install-plugin.d.ts.map +1 -0
- package/dist/install-plugin.js +222 -0
- package/dist/install-plugin.js.map +1 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +6 -0
- package/dist/internal.js.map +1 -1
- package/dist/plugin.d.ts +45 -34
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +47 -34
- package/dist/plugin.js.map +1 -1
- package/dist/runtime-carrier.d.ts +142 -0
- package/dist/runtime-carrier.d.ts.map +1 -0
- package/dist/runtime-carrier.js +148 -0
- package/dist/runtime-carrier.js.map +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +2 -1
- package/dist/session.js.map +1 -1
- package/dist/test/builder.d.ts +249 -0
- package/dist/test/builder.d.ts.map +1 -0
- package/dist/test/builder.js +265 -0
- package/dist/test/builder.js.map +1 -0
- package/dist/test/each-builder.d.ts +244 -0
- package/dist/test/each-builder.d.ts.map +1 -0
- package/dist/test/each-builder.js +268 -0
- package/dist/test/each-builder.js.map +1 -0
- package/dist/test/extend.d.ts +59 -0
- package/dist/test/extend.d.ts.map +1 -0
- package/dist/test/extend.js +111 -0
- package/dist/test/extend.js.map +1 -0
- package/dist/test/utils.d.ts +39 -0
- package/dist/test/utils.d.ts.map +1 -0
- package/dist/test/utils.js +91 -0
- package/dist/test/utils.js.map +1 -0
- package/dist/types.d.ts +89 -111
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/contract-http/markdown.d.ts +0 -10
- package/dist/contract-http/markdown.d.ts.map +0 -1
- package/dist/contract-http/markdown.js +0 -21
- package/dist/contract-http/markdown.js.map +0 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module contract-artifacts
|
|
3
|
+
*
|
|
4
|
+
* Contract artifact registry — declarative extension point for
|
|
5
|
+
* "contracts → X" rendering (markdown / openapi / future proto / sdl /
|
|
6
|
+
* asyncapi / ...).
|
|
7
|
+
*
|
|
8
|
+
* Replaces the v0.1 per-adapter `toMarkdown?` / `toOpenApi?` hooks which
|
|
9
|
+
* (a) polluted the generic `ContractProtocolAdapter` interface with
|
|
10
|
+
* protocol-specific artifacts (OpenAPI is HTTP-only), (b) had dead
|
|
11
|
+
* declarations that nothing called, and (c) gave consumers no way to
|
|
12
|
+
* enumerate "which artifact kinds does this project support".
|
|
13
|
+
*
|
|
14
|
+
* See `internal/40-discovery/proposals/contract-artifact-registry.md` (v6).
|
|
15
|
+
*
|
|
16
|
+
* ## Concepts
|
|
17
|
+
*
|
|
18
|
+
* **ArtifactKind**: first-class description of an output format (name +
|
|
19
|
+
* merge strategy + empty skeleton + optional defaultRender). Per-contract
|
|
20
|
+
* Part and final merged Final are independently typed (markdown renders
|
|
21
|
+
* structured parts that the merge assembles into a document; openapi
|
|
22
|
+
* renders per-contract partial documents that the merge stitches together).
|
|
23
|
+
*
|
|
24
|
+
* **ContractProtocolAdapter.artifacts**: each adapter declares which
|
|
25
|
+
* kinds it produces. Mapping from kind name to per-contract renderer.
|
|
26
|
+
*
|
|
27
|
+
* **Consumers**: `renderArtifact(kind, contracts, options?, control?)`
|
|
28
|
+
* dispatches across all registered adapters; fallback to kind.defaultRender
|
|
29
|
+
* where the adapter doesn't implement the kind; ultimate fallback to
|
|
30
|
+
* kind.empty when no contract contributes a part.
|
|
31
|
+
*
|
|
32
|
+
* ## Types
|
|
33
|
+
*
|
|
34
|
+
* `KnownArtifacts` / `KnownArtifactParts` / `KnownArtifactOptions` are
|
|
35
|
+
* declared at the package root (`packages/sdk/src/index.ts`) so third-
|
|
36
|
+
* party plugins can augment via `declare module "@glubean/sdk"`. This file
|
|
37
|
+
* imports them as types from the root so declaration merging hits the
|
|
38
|
+
* same node.
|
|
39
|
+
*/
|
|
40
|
+
import type { ExtractedContractProjection } from "./contract-types.js";
|
|
41
|
+
import type { OpenApiDocument, OpenApiOptions } from "./contract-http/openapi.js";
|
|
42
|
+
/**
|
|
43
|
+
* Declarative description of an artifact format.
|
|
44
|
+
*
|
|
45
|
+
* @template Final Final merged value type (what consumers receive).
|
|
46
|
+
* @template Part Per-contract partial type (what producer / defaultRender
|
|
47
|
+
* emit). Defaults to Final; distinct when per-contract
|
|
48
|
+
* rendering is structured (e.g. markdown produces
|
|
49
|
+
* `{ body, feature, caseCount }` parts and the merge
|
|
50
|
+
* assembles a feature-grouped doc string).
|
|
51
|
+
* @template Options Per-render options type. Threaded through the entire
|
|
52
|
+
* pipeline (producer / defaultRender / merge) so kinds
|
|
53
|
+
* whose options affect per-contract rendering (e.g. proto
|
|
54
|
+
* `package` per message) are as well-served as kinds whose
|
|
55
|
+
* options only affect the final merge (e.g. openapi
|
|
56
|
+
* `title`).
|
|
57
|
+
*/
|
|
58
|
+
export interface ArtifactKind<Final, Part = Final, Options = void> {
|
|
59
|
+
/** Kind identifier (snake-case or kebab-case). Unique across the registry. */
|
|
60
|
+
readonly name: string;
|
|
61
|
+
/**
|
|
62
|
+
* Combine per-contract parts into the final artifact. Called once per
|
|
63
|
+
* render; receives the collected parts and the same options that were
|
|
64
|
+
* passed to producers / defaultRender.
|
|
65
|
+
*/
|
|
66
|
+
readonly merge: (parts: Part[], options?: Options) => Final;
|
|
67
|
+
/**
|
|
68
|
+
* Optional fallback: when an adapter has not declared a producer for this
|
|
69
|
+
* kind, use this function to render a per-contract Part from the generic
|
|
70
|
+
* `ExtractedContractProjection`. Omitted for kinds that only make sense
|
|
71
|
+
* for specific protocols (e.g. openapi has no defaultRender — non-HTTP
|
|
72
|
+
* contracts are skipped).
|
|
73
|
+
*/
|
|
74
|
+
readonly defaultRender?: (projection: ExtractedContractProjection<unknown, unknown>, options?: Options) => Part;
|
|
75
|
+
/**
|
|
76
|
+
* Required: the value returned by `renderArtifact` when no contract
|
|
77
|
+
* contributes a part. Must be a valid Final. Lets `renderArtifact` promise
|
|
78
|
+
* `Final` (not `Final | undefined`) and forces kind authors to think
|
|
79
|
+
* about the zero-contribution scenario explicitly.
|
|
80
|
+
*/
|
|
81
|
+
readonly empty: Final;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Register an artifact kind by name. Idempotent when re-registering the same
|
|
85
|
+
* kind object; throws when a different object tries to take the same name
|
|
86
|
+
* (protects against typo-level collisions across plugins).
|
|
87
|
+
*
|
|
88
|
+
* `defineArtifactKind` is the ergonomic entry; this is exported for advanced
|
|
89
|
+
* scenarios where a kind is built separately from its registration.
|
|
90
|
+
*/
|
|
91
|
+
export declare function registerArtifactKind<Final, Part, Options>(kind: ArtifactKind<Final, Part, Options>): void;
|
|
92
|
+
/**
|
|
93
|
+
* Look up a registered kind by name. Used by `renderArtifactByName` and any
|
|
94
|
+
* other string-driven consumer (CLI --format, MCP tool inputs, etc.).
|
|
95
|
+
*/
|
|
96
|
+
export declare function getArtifactKind(name: string): ArtifactKind<unknown, unknown, unknown> | undefined;
|
|
97
|
+
/** List all registered kind names (insertion order). */
|
|
98
|
+
export declare function listArtifactKinds(): string[];
|
|
99
|
+
/**
|
|
100
|
+
* Define and register an artifact kind in one call. Preferred entry point
|
|
101
|
+
* for kind authors (SDK built-ins + first-party plugins).
|
|
102
|
+
*/
|
|
103
|
+
export declare function defineArtifactKind<Final, Part = Final, Options = void>(spec: ArtifactKind<Final, Part, Options>): ArtifactKind<Final, Part, Options>;
|
|
104
|
+
/**
|
|
105
|
+
* Test-only: clear the kind registry. Not exposed publicly; imported by
|
|
106
|
+
* `contract-artifacts.test.ts` via relative path.
|
|
107
|
+
*
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
export declare function __resetArtifactKindsForTesting(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Control knobs for a single render invocation (orthogonal to kind-specific
|
|
113
|
+
* options). Kept separate from `options` so adding new flags here doesn't
|
|
114
|
+
* require each kind author to know about them.
|
|
115
|
+
*/
|
|
116
|
+
export interface RenderArtifactControl {
|
|
117
|
+
/**
|
|
118
|
+
* Skip each adapter's `artifacts[kind]` producer even when defined; force
|
|
119
|
+
* all contracts through `kind.defaultRender`. For kinds without a
|
|
120
|
+
* defaultRender, contracts are skipped entirely.
|
|
121
|
+
*
|
|
122
|
+
* Used by CLI `--generic` for reproducible output across installed
|
|
123
|
+
* plugin versions, and by tests that assert default-render behavior.
|
|
124
|
+
*/
|
|
125
|
+
preferDefaultRender?: boolean;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Summary of a render invocation — per-contract classification of how each
|
|
129
|
+
* contract contributed (or was skipped) + a `usedEmptyFallback` boolean.
|
|
130
|
+
*/
|
|
131
|
+
export interface ArtifactContribution {
|
|
132
|
+
contractId: string;
|
|
133
|
+
protocol: string;
|
|
134
|
+
/** Whether the part came from the adapter's producer or kind.defaultRender. */
|
|
135
|
+
source: "explicit-producer" | "default-render";
|
|
136
|
+
}
|
|
137
|
+
export interface ArtifactSkip {
|
|
138
|
+
contractId: string;
|
|
139
|
+
protocol: string;
|
|
140
|
+
reason: "no-producer-no-default-render" | "prefer-default-render-no-default";
|
|
141
|
+
}
|
|
142
|
+
export interface ArtifactRenderSummary<Final> {
|
|
143
|
+
/** Same value `renderArtifact` would have returned. */
|
|
144
|
+
value: Final;
|
|
145
|
+
/** Contracts that produced a part. */
|
|
146
|
+
contributions: ArtifactContribution[];
|
|
147
|
+
/** Contracts that did not produce a part (with reason). */
|
|
148
|
+
skipped: ArtifactSkip[];
|
|
149
|
+
/**
|
|
150
|
+
* True iff `value === kind.empty` was returned because zero parts were
|
|
151
|
+
* collected. False when `kind.merge(parts, options)` was called. Callers
|
|
152
|
+
* should use this instead of comparing `value === kind.empty`
|
|
153
|
+
* themselves — merge typically returns a fresh object for object-typed
|
|
154
|
+
* Final, so identity / structural equality are not reliable.
|
|
155
|
+
*/
|
|
156
|
+
usedEmptyFallback: boolean;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Render an artifact of the given kind from a list of contracts. Threads
|
|
160
|
+
* `options` through the producer / defaultRender / merge pipeline. When no
|
|
161
|
+
* contract contributes a part, returns `kind.empty` (guaranteed valid
|
|
162
|
+
* `Final`).
|
|
163
|
+
*
|
|
164
|
+
* Strong-typed entry point: kind argument is a concrete `ArtifactKind<...>`
|
|
165
|
+
* object, so `options` type and return type are inferred at compile time.
|
|
166
|
+
*/
|
|
167
|
+
export declare function renderArtifact<Final, Part, Options>(kind: ArtifactKind<Final, Part, Options>, contracts: ReadonlyArray<ExtractedContractProjection<unknown, unknown>>, options?: Options, control?: RenderArtifactControl): Final;
|
|
168
|
+
/**
|
|
169
|
+
* Render an artifact and return the full summary (value + contributions +
|
|
170
|
+
* skipped + usedEmptyFallback). Use this when you need to distinguish
|
|
171
|
+
* "zero contribution" from "merged to kind.empty-shaped value" — the
|
|
172
|
+
* `usedEmptyFallback` field is the authoritative signal (do not compare
|
|
173
|
+
* `value === kind.empty` yourself for object-typed Final).
|
|
174
|
+
*/
|
|
175
|
+
export declare function renderArtifactWithSummary<Final, Part, Options>(kind: ArtifactKind<Final, Part, Options>, contracts: ReadonlyArray<ExtractedContractProjection<unknown, unknown>>, options?: Options, control?: RenderArtifactControl): ArtifactRenderSummary<Final>;
|
|
176
|
+
/**
|
|
177
|
+
* Look up a kind by name and render. Used by string-driven consumers
|
|
178
|
+
* (CLI --format <name>, MCP tools, agent introspection). Throws when the
|
|
179
|
+
* name is not registered — the error message includes the list of
|
|
180
|
+
* registered kinds so callers can surface it without extra lookups.
|
|
181
|
+
*
|
|
182
|
+
* Returns `unknown` since we don't know the Final type statically;
|
|
183
|
+
* callers that need a typed return should use `renderArtifact` directly
|
|
184
|
+
* with the kind object they import.
|
|
185
|
+
*/
|
|
186
|
+
export declare function renderArtifactByName(kindName: string, contracts: ReadonlyArray<ExtractedContractProjection<unknown, unknown>>, options?: unknown, control?: RenderArtifactControl): unknown;
|
|
187
|
+
/**
|
|
188
|
+
* List the protocols whose adapter **explicitly declares a producer** for
|
|
189
|
+
* the given kind. Does NOT include protocols that would fall back to
|
|
190
|
+
* `kind.defaultRender`. Use `listArtifactCapability` for the three-way split.
|
|
191
|
+
*/
|
|
192
|
+
export declare function listArtifactProducers(kindName: string): string[];
|
|
193
|
+
/**
|
|
194
|
+
* Static capability view — **based on installed adapters**, not on any
|
|
195
|
+
* particular project's contracts. Partitions protocols into:
|
|
196
|
+
*
|
|
197
|
+
* - `explicit` : adapter declares a producer for this kind
|
|
198
|
+
* - `fallback` : adapter has no producer but kind.defaultRender exists
|
|
199
|
+
* - `unsupported` : adapter has no producer and kind has no defaultRender
|
|
200
|
+
*
|
|
201
|
+
* To answer "does **this project** produce the artifact?", use
|
|
202
|
+
* `renderArtifactWithSummary` and inspect `contributions` + `usedEmptyFallback`.
|
|
203
|
+
* The capability view cannot know how many contracts of each protocol exist
|
|
204
|
+
* in the caller's project.
|
|
205
|
+
*/
|
|
206
|
+
export declare function listArtifactCapability(kindName: string): {
|
|
207
|
+
explicit: string[];
|
|
208
|
+
fallback: string[];
|
|
209
|
+
unsupported: string[];
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* OpenAPI 3.1 artifact kind. Per-contract partials are produced by HTTP
|
|
213
|
+
* adapter's `artifacts.openapi` (see `contract-http/openapi.ts`); merge
|
|
214
|
+
* combines them into a full spec. No `defaultRender` — non-HTTP protocols
|
|
215
|
+
* don't map to OpenAPI and are skipped.
|
|
216
|
+
*
|
|
217
|
+
* Ported from MCP's former `contractsToOpenApi` — CAR-1 Phase 2.
|
|
218
|
+
*/
|
|
219
|
+
export declare const openapiArtifact: ArtifactKind<OpenApiDocument, OpenApiDocument, OpenApiOptions>;
|
|
220
|
+
/**
|
|
221
|
+
* Per-contract structured data consumed by `assembleMarkdownDocument`.
|
|
222
|
+
* Carries raw fields; the merge step handles feature grouping,
|
|
223
|
+
* instanceName-aware labeling, doc header, and summary counts.
|
|
224
|
+
*/
|
|
225
|
+
export interface MarkdownPart {
|
|
226
|
+
contractId: string;
|
|
227
|
+
/** Display target — equivalent to `projection.target`. */
|
|
228
|
+
endpoint: string;
|
|
229
|
+
protocol: string;
|
|
230
|
+
description?: string;
|
|
231
|
+
feature?: string;
|
|
232
|
+
instanceName?: string;
|
|
233
|
+
deprecated?: string;
|
|
234
|
+
cases: Array<{
|
|
235
|
+
key: string;
|
|
236
|
+
description?: string;
|
|
237
|
+
/** "active" | "deferred" | "deprecated". */
|
|
238
|
+
lifecycle: "active" | "deferred" | "deprecated";
|
|
239
|
+
severity: "critical" | "warning" | "info";
|
|
240
|
+
defaultRun?: "always" | "opt-in";
|
|
241
|
+
requires?: "headless" | "browser" | "out-of-band";
|
|
242
|
+
deferredReason?: string;
|
|
243
|
+
deprecatedReason?: string;
|
|
244
|
+
}>;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Protocol-agnostic default renderer. Reads raw fields off the
|
|
248
|
+
* projection; every protocol gets a valid Part even if its adapter
|
|
249
|
+
* doesn't declare `artifacts.markdown`.
|
|
250
|
+
*/
|
|
251
|
+
export declare function genericMarkdownPart(projection: ExtractedContractProjection<unknown, unknown>): MarkdownPart;
|
|
252
|
+
/**
|
|
253
|
+
* Feature-grouped, doc-level markdown assembly. Byte-for-byte output
|
|
254
|
+
* compatible with `formatMdOutline` from `packages/cli/src/commands/
|
|
255
|
+
* contracts.ts` (the legacy CLI markdown path).
|
|
256
|
+
*
|
|
257
|
+
* Structure:
|
|
258
|
+
* # Contract Specification
|
|
259
|
+
* Generated: YYYY-MM-DD | N cases | N active | ...
|
|
260
|
+
*
|
|
261
|
+
* ## <feature>
|
|
262
|
+
* 🚫 **Deprecated:** ... (if contract-level deprecated)
|
|
263
|
+
* <description or endpoint intro>
|
|
264
|
+
* - **case** — ...
|
|
265
|
+
*/
|
|
266
|
+
export declare function assembleMarkdownDocument(parts: MarkdownPart[]): string;
|
|
267
|
+
export declare const markdownArtifact: ArtifactKind<string, MarkdownPart, void>;
|
|
268
|
+
//# sourceMappingURL=contract-artifacts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-artifacts.d.ts","sourceRoot":"","sources":["../src/contract-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAMH,OAAO,KAAK,EACV,2BAA2B,EAC5B,MAAM,qBAAqB,CAAC;AAK7B,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACf,MAAM,4BAA4B,CAAC;AAMpC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,YAAY,CAAC,KAAK,EAAE,IAAI,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI;IAC/D,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,KAAK,CAAC;IAE5D;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,UAAU,EAAE,2BAA2B,CAAC,OAAO,EAAE,OAAO,CAAC,EACzD,OAAO,CAAC,EAAE,OAAO,KACd,IAAI,CAAC;IAEV;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAQD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EACvD,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,GACvC,IAAI,CAYN;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GACX,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,SAAS,CAErD;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,EACpE,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,GACvC,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAGpC;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAMD;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,CAAC;CAChD;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,+BAA+B,GAAG,kCAAkC,CAAC;CAC9E;AAED,MAAM,WAAW,qBAAqB,CAAC,KAAK;IAC1C,uDAAuD;IACvD,KAAK,EAAE,KAAK,CAAC;IACb,sCAAsC;IACtC,aAAa,EAAE,oBAAoB,EAAE,CAAC;IACtC,2DAA2D;IAC3D,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB;;;;;;OAMG;IACH,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAkFD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EACjD,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,EACxC,SAAS,EAAE,aAAa,CAAC,2BAA2B,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EACvE,OAAO,CAAC,EAAE,OAAO,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,KAAK,CAEP;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAC5D,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,EACxC,SAAS,EAAE,aAAa,CAAC,2BAA2B,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EACvE,OAAO,CAAC,EAAE,OAAO,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,qBAAqB,CAAC,KAAK,CAAC,CAE9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,aAAa,CAAC,2BAA2B,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EACvE,OAAO,CAAC,EAAE,OAAO,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAeT;AAMD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAShE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACxD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAUA;AAMD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,gEAQ1B,CAAC;AAiBH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,4CAA4C;QAC5C,SAAS,EAAE,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAC;QAChD,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;QAC1C,UAAU,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;QACjC,QAAQ,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;QAClD,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC,CAAC;CACJ;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,2BAA2B,CAAC,OAAO,EAAE,OAAO,CAAC,GACxD,YAAY,CA2Bd;AAiED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAoDtE;AAED,eAAO,MAAM,gBAAgB,0CAK3B,CAAC"}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module contract-artifacts
|
|
3
|
+
*
|
|
4
|
+
* Contract artifact registry — declarative extension point for
|
|
5
|
+
* "contracts → X" rendering (markdown / openapi / future proto / sdl /
|
|
6
|
+
* asyncapi / ...).
|
|
7
|
+
*
|
|
8
|
+
* Replaces the v0.1 per-adapter `toMarkdown?` / `toOpenApi?` hooks which
|
|
9
|
+
* (a) polluted the generic `ContractProtocolAdapter` interface with
|
|
10
|
+
* protocol-specific artifacts (OpenAPI is HTTP-only), (b) had dead
|
|
11
|
+
* declarations that nothing called, and (c) gave consumers no way to
|
|
12
|
+
* enumerate "which artifact kinds does this project support".
|
|
13
|
+
*
|
|
14
|
+
* See `internal/40-discovery/proposals/contract-artifact-registry.md` (v6).
|
|
15
|
+
*
|
|
16
|
+
* ## Concepts
|
|
17
|
+
*
|
|
18
|
+
* **ArtifactKind**: first-class description of an output format (name +
|
|
19
|
+
* merge strategy + empty skeleton + optional defaultRender). Per-contract
|
|
20
|
+
* Part and final merged Final are independently typed (markdown renders
|
|
21
|
+
* structured parts that the merge assembles into a document; openapi
|
|
22
|
+
* renders per-contract partial documents that the merge stitches together).
|
|
23
|
+
*
|
|
24
|
+
* **ContractProtocolAdapter.artifacts**: each adapter declares which
|
|
25
|
+
* kinds it produces. Mapping from kind name to per-contract renderer.
|
|
26
|
+
*
|
|
27
|
+
* **Consumers**: `renderArtifact(kind, contracts, options?, control?)`
|
|
28
|
+
* dispatches across all registered adapters; fallback to kind.defaultRender
|
|
29
|
+
* where the adapter doesn't implement the kind; ultimate fallback to
|
|
30
|
+
* kind.empty when no contract contributes a part.
|
|
31
|
+
*
|
|
32
|
+
* ## Types
|
|
33
|
+
*
|
|
34
|
+
* `KnownArtifacts` / `KnownArtifactParts` / `KnownArtifactOptions` are
|
|
35
|
+
* declared at the package root (`packages/sdk/src/index.ts`) so third-
|
|
36
|
+
* party plugins can augment via `declare module "@glubean/sdk"`. This file
|
|
37
|
+
* imports them as types from the root so declaration merging hits the
|
|
38
|
+
* same node.
|
|
39
|
+
*/
|
|
40
|
+
import { getAdapter, listRegisteredProtocols, } from "./contract-core.js";
|
|
41
|
+
import { emptyOpenApiDocument, mergeOpenApiParts, } from "./contract-http/openapi.js";
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// Kind registry
|
|
44
|
+
// =============================================================================
|
|
45
|
+
const _kinds = new Map();
|
|
46
|
+
/**
|
|
47
|
+
* Register an artifact kind by name. Idempotent when re-registering the same
|
|
48
|
+
* kind object; throws when a different object tries to take the same name
|
|
49
|
+
* (protects against typo-level collisions across plugins).
|
|
50
|
+
*
|
|
51
|
+
* `defineArtifactKind` is the ergonomic entry; this is exported for advanced
|
|
52
|
+
* scenarios where a kind is built separately from its registration.
|
|
53
|
+
*/
|
|
54
|
+
export function registerArtifactKind(kind) {
|
|
55
|
+
const existing = _kinds.get(kind.name);
|
|
56
|
+
if (existing) {
|
|
57
|
+
if (existing !== kind) {
|
|
58
|
+
throw new Error(`Artifact kind "${kind.name}" already registered with a different instance. ` +
|
|
59
|
+
`Ensure each kind is defined exactly once per process.`);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
_kinds.set(kind.name, kind);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Look up a registered kind by name. Used by `renderArtifactByName` and any
|
|
67
|
+
* other string-driven consumer (CLI --format, MCP tool inputs, etc.).
|
|
68
|
+
*/
|
|
69
|
+
export function getArtifactKind(name) {
|
|
70
|
+
return _kinds.get(name);
|
|
71
|
+
}
|
|
72
|
+
/** List all registered kind names (insertion order). */
|
|
73
|
+
export function listArtifactKinds() {
|
|
74
|
+
return [..._kinds.keys()];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Define and register an artifact kind in one call. Preferred entry point
|
|
78
|
+
* for kind authors (SDK built-ins + first-party plugins).
|
|
79
|
+
*/
|
|
80
|
+
export function defineArtifactKind(spec) {
|
|
81
|
+
registerArtifactKind(spec);
|
|
82
|
+
return spec;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Test-only: clear the kind registry. Not exposed publicly; imported by
|
|
86
|
+
* `contract-artifacts.test.ts` via relative path.
|
|
87
|
+
*
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
export function __resetArtifactKindsForTesting() {
|
|
91
|
+
_kinds.clear();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Core render pipeline implementation — produces both a value and a
|
|
95
|
+
* detailed summary. `renderArtifact` is a thin wrapper returning just the
|
|
96
|
+
* value; `renderArtifactWithSummary` returns the full summary.
|
|
97
|
+
*
|
|
98
|
+
* Generic type `Final` / `Part` / `Options` instead of referring to the
|
|
99
|
+
* kind's generics directly so runtime-typed callers (renderArtifactByName)
|
|
100
|
+
* can pass `unknown`.
|
|
101
|
+
*/
|
|
102
|
+
function runRender(kind, contracts, options, control) {
|
|
103
|
+
const parts = [];
|
|
104
|
+
const contributions = [];
|
|
105
|
+
const skipped = [];
|
|
106
|
+
for (const c of contracts) {
|
|
107
|
+
const adapter = getAdapter(c.protocol);
|
|
108
|
+
const explicitProducer = !control?.preferDefaultRender
|
|
109
|
+
? adapter?.artifacts?.[kind.name]
|
|
110
|
+
: undefined;
|
|
111
|
+
if (explicitProducer) {
|
|
112
|
+
parts.push(explicitProducer(c, options));
|
|
113
|
+
contributions.push({
|
|
114
|
+
contractId: c.id,
|
|
115
|
+
protocol: c.protocol,
|
|
116
|
+
source: "explicit-producer",
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (kind.defaultRender) {
|
|
121
|
+
parts.push(kind.defaultRender(c, options));
|
|
122
|
+
contributions.push({
|
|
123
|
+
contractId: c.id,
|
|
124
|
+
protocol: c.protocol,
|
|
125
|
+
source: "default-render",
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
skipped.push({
|
|
130
|
+
contractId: c.id,
|
|
131
|
+
protocol: c.protocol,
|
|
132
|
+
reason: control?.preferDefaultRender
|
|
133
|
+
? "prefer-default-render-no-default"
|
|
134
|
+
: "no-producer-no-default-render",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (parts.length === 0) {
|
|
138
|
+
return {
|
|
139
|
+
value: kind.empty,
|
|
140
|
+
contributions,
|
|
141
|
+
skipped,
|
|
142
|
+
usedEmptyFallback: true,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
value: kind.merge(parts, options),
|
|
147
|
+
contributions,
|
|
148
|
+
skipped,
|
|
149
|
+
usedEmptyFallback: false,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Render an artifact of the given kind from a list of contracts. Threads
|
|
154
|
+
* `options` through the producer / defaultRender / merge pipeline. When no
|
|
155
|
+
* contract contributes a part, returns `kind.empty` (guaranteed valid
|
|
156
|
+
* `Final`).
|
|
157
|
+
*
|
|
158
|
+
* Strong-typed entry point: kind argument is a concrete `ArtifactKind<...>`
|
|
159
|
+
* object, so `options` type and return type are inferred at compile time.
|
|
160
|
+
*/
|
|
161
|
+
export function renderArtifact(kind, contracts, options, control) {
|
|
162
|
+
return runRender(kind, contracts, options, control).value;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Render an artifact and return the full summary (value + contributions +
|
|
166
|
+
* skipped + usedEmptyFallback). Use this when you need to distinguish
|
|
167
|
+
* "zero contribution" from "merged to kind.empty-shaped value" — the
|
|
168
|
+
* `usedEmptyFallback` field is the authoritative signal (do not compare
|
|
169
|
+
* `value === kind.empty` yourself for object-typed Final).
|
|
170
|
+
*/
|
|
171
|
+
export function renderArtifactWithSummary(kind, contracts, options, control) {
|
|
172
|
+
return runRender(kind, contracts, options, control);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Look up a kind by name and render. Used by string-driven consumers
|
|
176
|
+
* (CLI --format <name>, MCP tools, agent introspection). Throws when the
|
|
177
|
+
* name is not registered — the error message includes the list of
|
|
178
|
+
* registered kinds so callers can surface it without extra lookups.
|
|
179
|
+
*
|
|
180
|
+
* Returns `unknown` since we don't know the Final type statically;
|
|
181
|
+
* callers that need a typed return should use `renderArtifact` directly
|
|
182
|
+
* with the kind object they import.
|
|
183
|
+
*/
|
|
184
|
+
export function renderArtifactByName(kindName, contracts, options, control) {
|
|
185
|
+
const kind = getArtifactKind(kindName);
|
|
186
|
+
if (!kind) {
|
|
187
|
+
const known = listArtifactKinds();
|
|
188
|
+
const hint = known.length > 0 ? known.join(", ") : "(none)";
|
|
189
|
+
throw new Error(`Unknown artifact kind "${kindName}". Registered kinds: ${hint}`);
|
|
190
|
+
}
|
|
191
|
+
return renderArtifact(kind, contracts, options, control);
|
|
192
|
+
}
|
|
193
|
+
// =============================================================================
|
|
194
|
+
// Introspection
|
|
195
|
+
// =============================================================================
|
|
196
|
+
/**
|
|
197
|
+
* List the protocols whose adapter **explicitly declares a producer** for
|
|
198
|
+
* the given kind. Does NOT include protocols that would fall back to
|
|
199
|
+
* `kind.defaultRender`. Use `listArtifactCapability` for the three-way split.
|
|
200
|
+
*/
|
|
201
|
+
export function listArtifactProducers(kindName) {
|
|
202
|
+
return listRegisteredProtocols().filter((p) => {
|
|
203
|
+
const adapter = getAdapter(p);
|
|
204
|
+
return (adapter !== undefined &&
|
|
205
|
+
adapter.artifacts?.[kindName] !==
|
|
206
|
+
undefined);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Static capability view — **based on installed adapters**, not on any
|
|
211
|
+
* particular project's contracts. Partitions protocols into:
|
|
212
|
+
*
|
|
213
|
+
* - `explicit` : adapter declares a producer for this kind
|
|
214
|
+
* - `fallback` : adapter has no producer but kind.defaultRender exists
|
|
215
|
+
* - `unsupported` : adapter has no producer and kind has no defaultRender
|
|
216
|
+
*
|
|
217
|
+
* To answer "does **this project** produce the artifact?", use
|
|
218
|
+
* `renderArtifactWithSummary` and inspect `contributions` + `usedEmptyFallback`.
|
|
219
|
+
* The capability view cannot know how many contracts of each protocol exist
|
|
220
|
+
* in the caller's project.
|
|
221
|
+
*/
|
|
222
|
+
export function listArtifactCapability(kindName) {
|
|
223
|
+
const kind = getArtifactKind(kindName);
|
|
224
|
+
const allProtocols = listRegisteredProtocols();
|
|
225
|
+
const explicit = listArtifactProducers(kindName);
|
|
226
|
+
const explicitSet = new Set(explicit);
|
|
227
|
+
const rest = allProtocols.filter((p) => !explicitSet.has(p));
|
|
228
|
+
if (kind?.defaultRender) {
|
|
229
|
+
return { explicit, fallback: rest, unsupported: [] };
|
|
230
|
+
}
|
|
231
|
+
return { explicit, fallback: [], unsupported: rest };
|
|
232
|
+
}
|
|
233
|
+
// =============================================================================
|
|
234
|
+
// Built-in kinds
|
|
235
|
+
// =============================================================================
|
|
236
|
+
/**
|
|
237
|
+
* OpenAPI 3.1 artifact kind. Per-contract partials are produced by HTTP
|
|
238
|
+
* adapter's `artifacts.openapi` (see `contract-http/openapi.ts`); merge
|
|
239
|
+
* combines them into a full spec. No `defaultRender` — non-HTTP protocols
|
|
240
|
+
* don't map to OpenAPI and are skipped.
|
|
241
|
+
*
|
|
242
|
+
* Ported from MCP's former `contractsToOpenApi` — CAR-1 Phase 2.
|
|
243
|
+
*/
|
|
244
|
+
export const openapiArtifact = defineArtifactKind({
|
|
245
|
+
name: "openapi",
|
|
246
|
+
merge: (parts, options) => mergeOpenApiParts(parts, options),
|
|
247
|
+
empty: emptyOpenApiDocument,
|
|
248
|
+
});
|
|
249
|
+
/**
|
|
250
|
+
* Protocol-agnostic default renderer. Reads raw fields off the
|
|
251
|
+
* projection; every protocol gets a valid Part even if its adapter
|
|
252
|
+
* doesn't declare `artifacts.markdown`.
|
|
253
|
+
*/
|
|
254
|
+
export function genericMarkdownPart(projection) {
|
|
255
|
+
return {
|
|
256
|
+
contractId: projection.id,
|
|
257
|
+
endpoint: projection.target,
|
|
258
|
+
protocol: projection.protocol,
|
|
259
|
+
description: projection.description,
|
|
260
|
+
feature: projection.feature,
|
|
261
|
+
instanceName: projection.instanceName,
|
|
262
|
+
deprecated: projection.deprecated,
|
|
263
|
+
cases: projection.cases.map((c) => ({
|
|
264
|
+
key: c.key,
|
|
265
|
+
description: c.description,
|
|
266
|
+
lifecycle: c.lifecycle ??
|
|
267
|
+
(c.deprecatedReason
|
|
268
|
+
? "deprecated"
|
|
269
|
+
: c.deferredReason
|
|
270
|
+
? "deferred"
|
|
271
|
+
: "active"),
|
|
272
|
+
severity: c.severity ?? "warning",
|
|
273
|
+
defaultRun: c.defaultRun,
|
|
274
|
+
requires: c.requires,
|
|
275
|
+
deferredReason: c.deferredReason,
|
|
276
|
+
deprecatedReason: c.deprecatedReason,
|
|
277
|
+
})),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function computeMarkdownSummary(parts) {
|
|
281
|
+
let total = 0;
|
|
282
|
+
let deferred = 0;
|
|
283
|
+
let deprecated = 0;
|
|
284
|
+
let gated = 0;
|
|
285
|
+
for (const p of parts) {
|
|
286
|
+
for (const c of p.cases) {
|
|
287
|
+
total++;
|
|
288
|
+
if (c.lifecycle === "deprecated")
|
|
289
|
+
deprecated++;
|
|
290
|
+
else if (c.lifecycle === "deferred")
|
|
291
|
+
deferred++;
|
|
292
|
+
else if (c.requires === "browser" || c.requires === "out-of-band")
|
|
293
|
+
gated++;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
total,
|
|
298
|
+
active: total - deferred - deprecated - gated,
|
|
299
|
+
deferred,
|
|
300
|
+
deprecated,
|
|
301
|
+
gated,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function formatMarkdownCase(c) {
|
|
305
|
+
const desc = c.description ? ` — ${c.description}` : "";
|
|
306
|
+
if (c.lifecycle === "deprecated") {
|
|
307
|
+
const reason = c.deprecatedReason ?? "deprecated";
|
|
308
|
+
return `- ⊘ **${c.key}** — deprecated: ${reason}`;
|
|
309
|
+
}
|
|
310
|
+
if (c.lifecycle === "deferred") {
|
|
311
|
+
const reason = c.deferredReason ?? "deferred";
|
|
312
|
+
return `- ⊘ **${c.key}** — deferred: ${reason}`;
|
|
313
|
+
}
|
|
314
|
+
if (c.requires === "browser" || c.requires === "out-of-band") {
|
|
315
|
+
return `- ⊘ **${c.key}** — requires: ${c.requires}`;
|
|
316
|
+
}
|
|
317
|
+
const severityTag = c.severity === "critical" ? " 🔴" : c.severity === "info" ? " ℹ️" : "";
|
|
318
|
+
const suffix = c.defaultRun === "opt-in" ? " *(opt-in)*" : "";
|
|
319
|
+
return `- **${c.key}**${desc}${suffix}${severityTag}`;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Compute the effective feature key for a part — applies the
|
|
323
|
+
* instanceName-aware transform when the project has any instanced
|
|
324
|
+
* contract (matches CLI `hasInstances` pre-pass behavior).
|
|
325
|
+
*/
|
|
326
|
+
function displayFeature(part, hasInstances) {
|
|
327
|
+
if (hasInstances && part.instanceName) {
|
|
328
|
+
return `${part.instanceName}: ${part.feature ?? part.endpoint}`;
|
|
329
|
+
}
|
|
330
|
+
return part.feature ?? part.endpoint;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Feature-grouped, doc-level markdown assembly. Byte-for-byte output
|
|
334
|
+
* compatible with `formatMdOutline` from `packages/cli/src/commands/
|
|
335
|
+
* contracts.ts` (the legacy CLI markdown path).
|
|
336
|
+
*
|
|
337
|
+
* Structure:
|
|
338
|
+
* # Contract Specification
|
|
339
|
+
* Generated: YYYY-MM-DD | N cases | N active | ...
|
|
340
|
+
*
|
|
341
|
+
* ## <feature>
|
|
342
|
+
* 🚫 **Deprecated:** ... (if contract-level deprecated)
|
|
343
|
+
* <description or endpoint intro>
|
|
344
|
+
* - **case** — ...
|
|
345
|
+
*/
|
|
346
|
+
export function assembleMarkdownDocument(parts) {
|
|
347
|
+
if (parts.length === 0)
|
|
348
|
+
return "";
|
|
349
|
+
const hasInstances = parts.some((p) => !!p.instanceName);
|
|
350
|
+
// Group preserving insertion order
|
|
351
|
+
const groups = new Map();
|
|
352
|
+
for (const part of parts) {
|
|
353
|
+
const key = displayFeature(part, hasInstances);
|
|
354
|
+
const list = groups.get(key) ?? [];
|
|
355
|
+
list.push(part);
|
|
356
|
+
groups.set(key, list);
|
|
357
|
+
}
|
|
358
|
+
const summary = computeMarkdownSummary(parts);
|
|
359
|
+
const lines = [];
|
|
360
|
+
lines.push("# Contract Specification");
|
|
361
|
+
lines.push("");
|
|
362
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
363
|
+
const summaryParts = [`Generated: ${date}`, `${summary.total} cases`];
|
|
364
|
+
if (summary.active > 0)
|
|
365
|
+
summaryParts.push(`${summary.active} active`);
|
|
366
|
+
if (summary.deferred > 0)
|
|
367
|
+
summaryParts.push(`${summary.deferred} deferred`);
|
|
368
|
+
if (summary.deprecated > 0)
|
|
369
|
+
summaryParts.push(`${summary.deprecated} deprecated`);
|
|
370
|
+
if (summary.gated > 0)
|
|
371
|
+
summaryParts.push(`${summary.gated} gated`);
|
|
372
|
+
lines.push(summaryParts.join(" | "));
|
|
373
|
+
lines.push("");
|
|
374
|
+
for (const [featureName, featureParts] of groups.entries()) {
|
|
375
|
+
lines.push(`## ${featureName}`);
|
|
376
|
+
lines.push("");
|
|
377
|
+
for (const contract of featureParts) {
|
|
378
|
+
const intro = contract.description ??
|
|
379
|
+
(featureName !== contract.endpoint ? contract.endpoint : undefined);
|
|
380
|
+
if (contract.deprecated) {
|
|
381
|
+
lines.push(`🚫 **Deprecated:** ${contract.deprecated}`);
|
|
382
|
+
lines.push("");
|
|
383
|
+
}
|
|
384
|
+
if (intro) {
|
|
385
|
+
lines.push(intro);
|
|
386
|
+
lines.push("");
|
|
387
|
+
}
|
|
388
|
+
for (const c of contract.cases) {
|
|
389
|
+
lines.push(formatMarkdownCase(c));
|
|
390
|
+
}
|
|
391
|
+
lines.push("");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return lines.join("\n").trimEnd() + "\n";
|
|
395
|
+
}
|
|
396
|
+
export const markdownArtifact = defineArtifactKind({
|
|
397
|
+
name: "markdown",
|
|
398
|
+
defaultRender: (projection) => genericMarkdownPart(projection),
|
|
399
|
+
merge: (parts) => assembleMarkdownDocument(parts),
|
|
400
|
+
empty: "",
|
|
401
|
+
});
|
|
402
|
+
//# sourceMappingURL=contract-artifacts.js.map
|