@wildo-ai/saas-technical-doc 1.1.3 → 1.1.5
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/esm/companion/application-documentation/application-connection-documentation.d.ts +37 -0
- package/dist/esm/companion/application-documentation/application-connection-documentation.d.ts.map +1 -1
- package/dist/esm/companion/application-documentation/application-connection-documentation.js +46 -5
- package/dist/esm/companion/application-documentation/application-connection-documentation.js.map +1 -1
- package/dist/esm/companion/application-documentation/application-documentation-chapters.d.ts +101 -0
- package/dist/esm/companion/application-documentation/application-documentation-chapters.d.ts.map +1 -0
- package/dist/esm/companion/application-documentation/application-documentation-chapters.js +205 -0
- package/dist/esm/companion/application-documentation/application-documentation-chapters.js.map +1 -0
- package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.js +3 -0
- package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.js.map +1 -1
- package/dist/esm/companion/application-documentation/docs-api-origin-substitution.d.ts +34 -0
- package/dist/esm/companion/application-documentation/docs-api-origin-substitution.d.ts.map +1 -0
- package/dist/esm/companion/application-documentation/docs-api-origin-substitution.js +45 -0
- package/dist/esm/companion/application-documentation/docs-api-origin-substitution.js.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.d.ts +24 -10
- package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.d.ts.map +1 -1
- package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.js +25 -15
- package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.js.map +1 -1
- package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.d.ts +11 -3
- package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.d.ts.map +1 -1
- package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.js +9 -2
- package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.js.map +1 -1
- package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.d.ts.map +1 -1
- package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.js +10 -4
- package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.js.map +1 -1
- package/dist/esm/companion/index.d.ts +2 -0
- package/dist/esm/companion/index.d.ts.map +1 -1
- package/dist/esm/companion/index.js +2 -0
- package/dist/esm/companion/index.js.map +1 -1
- package/dist/esm/companion/openapi-generator.d.ts +8 -4
- package/dist/esm/companion/openapi-generator.d.ts.map +1 -1
- package/dist/esm/companion/openapi-generator.js +43 -17
- package/dist/esm/companion/openapi-generator.js.map +1 -1
- package/dist/esm/companion/operation-projection.schemas.d.ts +21 -1
- package/dist/esm/companion/operation-projection.schemas.d.ts.map +1 -1
- package/dist/esm/companion/operation-projection.schemas.js +11 -0
- package/dist/esm/companion/operation-projection.schemas.js.map +1 -1
- package/dist/esm/companion/publish-result.types.d.ts +37 -4
- package/dist/esm/companion/publish-result.types.d.ts.map +1 -1
- package/dist/esm/companion/publish-result.types.js.map +1 -1
- package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.d.ts.map +1 -1
- package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.js +8 -0
- package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.js.map +1 -1
- package/dist/esm/companion/rendering/technical-documentation-render-model.d.ts.map +1 -1
- package/dist/esm/companion/rendering/technical-documentation-render-model.js +26 -10
- package/dist/esm/companion/rendering/technical-documentation-render-model.js.map +1 -1
- package/dist/esm/content/application-consumer-documentation-content.techdoc.d.ts +18 -15
- package/dist/esm/content/application-consumer-documentation-content.techdoc.d.ts.map +1 -1
- package/dist/esm/content/application-consumer-documentation-content.techdoc.js +169 -61
- package/dist/esm/content/application-consumer-documentation-content.techdoc.js.map +1 -1
- package/dist/esm/runtime/DocsAuthContext.d.ts +2 -2
- package/dist/esm/runtime/DocsAuthContext.d.ts.map +1 -1
- package/dist/esm/runtime/DocsAuthContext.js +8 -4
- package/dist/esm/runtime/DocsAuthContext.js.map +1 -1
- package/dist/esm/runtime/DocsFrontendProviders.d.ts +16 -0
- package/dist/esm/runtime/DocsFrontendProviders.d.ts.map +1 -0
- package/dist/esm/runtime/DocsFrontendProviders.js +26 -0
- package/dist/esm/runtime/DocsFrontendProviders.js.map +1 -0
- package/dist/esm/runtime/frontend-provider-registry.techdoc.d.ts +1 -2
- package/dist/esm/runtime/frontend-provider-registry.techdoc.d.ts.map +1 -1
- package/dist/esm/runtime/frontend-provider-registry.techdoc.js.map +1 -1
- package/dist/esm/runtime/index.d.ts +2 -0
- package/dist/esm/runtime/index.d.ts.map +1 -1
- package/dist/esm/runtime/index.js +2 -0
- package/dist/esm/runtime/index.js.map +1 -1
- package/dist/esm/runtime/openapi-reference-model.d.ts +5 -0
- package/dist/esm/runtime/openapi-reference-model.d.ts.map +1 -1
- package/dist/esm/runtime/openapi-reference-model.js +5 -0
- package/dist/esm/runtime/openapi-reference-model.js.map +1 -1
- package/dist/esm/runtime/openapi-reference-view.js +1 -1
- package/dist/esm/runtime/openapi-reference-view.js.map +1 -1
- package/dist/esm/runtime/use-docs-frontend-provider-registry.d.ts +1 -0
- package/dist/esm/runtime/use-docs-frontend-provider-registry.d.ts.map +1 -1
- package/dist/esm/runtime/use-docs-frontend-provider-registry.js +10 -2
- package/dist/esm/runtime/use-docs-frontend-provider-registry.js.map +1 -1
- package/dist/esm/runtime/use-docs-provider-scripts.d.ts +30 -0
- package/dist/esm/runtime/use-docs-provider-scripts.d.ts.map +1 -0
- package/dist/esm/runtime/use-docs-provider-scripts.js +40 -0
- package/dist/esm/runtime/use-docs-provider-scripts.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -24
- package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.d.ts +0 -0
- package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.d.ts.map +0 -0
- package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.js +0 -0
- package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.js.map +0 -0
package/dist/esm/companion/application-documentation/application-connection-documentation.d.ts
CHANGED
|
@@ -102,6 +102,43 @@ export interface ApplicationConnectionDocumentationFact {
|
|
|
102
102
|
readonly presentations: Readonly<Record<ApplicationConnectionPresentation, string>>;
|
|
103
103
|
readonly inlineValues: Readonly<Record<ApplicationConnectionInlineValue, string>>;
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* How the PRIMARY origin is rendered: as a build-time token, never as the origin generation happened
|
|
107
|
+
* to see (#1203).
|
|
108
|
+
*
|
|
109
|
+
* ## The defect
|
|
110
|
+
*
|
|
111
|
+
* A documentation image built for staging shipped 42 occurrences of `http://localhost:4241` — the
|
|
112
|
+
* SCIM base URL an administrator pastes into their identity provider, the MCP and A2A endpoints, and
|
|
113
|
+
* the `servers` of the published API description. Generation resolved this value on a developer
|
|
114
|
+
* machine, and the resolved bytes are committed, so every later build carried that environment's
|
|
115
|
+
* origin whatever it was built for. The CSP got a per-environment regeneration step; the CONTENT got
|
|
116
|
+
* none.
|
|
117
|
+
*
|
|
118
|
+
* ## Why a token rather than regenerating per environment
|
|
119
|
+
*
|
|
120
|
+
* Regenerating the content in the deploy workflow needs a CLI entry point for a companion-owned
|
|
121
|
+
* generation — heavy for a deploy job — and would still leave the committed artifacts describing
|
|
122
|
+
* whoever generated them last. A token makes the generated content environment-INDEPENDENT, which is
|
|
123
|
+
* the honest thing for a committed artifact, and moves the environment decision to the only place
|
|
124
|
+
* that knows it: the image build, which already receives `WILDO_DOCS_API_BASE_URL` as a docker build
|
|
125
|
+
* argument (`ci-workflow-generator.service.ts` declares it for `AppFrontendType.TECH_DOC`).
|
|
126
|
+
*
|
|
127
|
+
* It also covers the static OpenAPI documents, which are served as files and have no runtime to read
|
|
128
|
+
* `customFields` from — the reason the runtime-substitution option cannot be the whole answer.
|
|
129
|
+
*
|
|
130
|
+
* ## Only the PRIMARY origin
|
|
131
|
+
*
|
|
132
|
+
* Additional entries in `apiServers` are explicitly authored per-environment declarations; a reader
|
|
133
|
+
* who lists several is naming them deliberately and each is already correct. The primary is the one
|
|
134
|
+
* every sample interpolates, and the one an image is built FOR.
|
|
135
|
+
*/
|
|
136
|
+
export declare const DOCS_API_ORIGIN_TOKEN = "__WILDO_DOCS_API_ORIGIN__";
|
|
137
|
+
/**
|
|
138
|
+
* Shown when the application publishes no base URL at all. Unchanged by #1203: there is no origin to
|
|
139
|
+
* defer, so the reader is told to substitute their own — which is what this always said.
|
|
140
|
+
*/
|
|
141
|
+
export declare const UNPUBLISHED_BASE_URL_STAND_IN = "https://your-environment.example";
|
|
105
142
|
/**
|
|
106
143
|
* Projects the application's connection facts into the presentations a page may
|
|
107
144
|
* request. Every presentation is a pure function of the source, so two runs over
|
package/dist/esm/companion/application-documentation/application-connection-documentation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application-connection-documentation.d.ts","sourceRoot":"","sources":["../../../../../src/companion/application-documentation/application-connection-documentation.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,+CAA+C,EAAG,oDAA6D,CAAC;AAC7H,eAAO,MAAM,mDAAmD,IAAI,CAAC;AAErE,kFAAkF;AAClF,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,wCAAwC;IACvD,mFAAmF;IACnF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,0FAA0F;IAC1F,QAAQ,CAAC,UAAU,EAAE,SAAS,2BAA2B,EAAE,CAAC;IAC5D,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;QAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;KAC5B,CAAC;CACH;AAED,2FAA2F;AAC3F,oBAAY,iCAAiC;IAC3C,uFAAuF;IACvF,kBAAkB,sBAAsB;IACxC,gFAAgF;IAChF,SAAS,aAAa;IACtB;;;;;OAKG;IACH,YAAY,gBAAgB;IAC5B,yFAAyF;IACzF,cAAc,iBAAiB;CAChC;AAED;;;GAGG;AACH,oBAAY,gCAAgC;IAC1C,oGAAoG;IACpG,QAAQ,YAAY;IACpB,qDAAqD;IACrD,gBAAgB,mBAAmB;IACnC,mDAAmD;IACnD,cAAc,iBAAiB;IAC/B,sCAAsC;IACtC,cAAc,iBAAiB;IAC/B;;;;;;;;OAQG;IACH,aAAa,gBAAgB;IAC7B,kGAAkG;IAClG,YAAY,gBAAgB;IAC5B;;;;;;;;;;OAUG;IACH,kBAAkB,qBAAqB;CACxC;AAED,MAAM,WAAW,sCAAsC;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,iCAAiC,EAAE,MAAM,CAAC,CAAC,CAAC;IACpF,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,gCAAgC,EAAE,MAAM,CAAC,CAAC,CAAC;CACnF;AA0DD;;;;GAIG;AACH,wBAAgB,yCAAyC,CACvD,MAAM,EAAE,wCAAwC,GAC/C,sCAAsC,CA2BxC"}
|
|
1
|
+
{"version":3,"file":"application-connection-documentation.d.ts","sourceRoot":"","sources":["../../../../../src/companion/application-documentation/application-connection-documentation.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,+CAA+C,EAAG,oDAA6D,CAAC;AAC7H,eAAO,MAAM,mDAAmD,IAAI,CAAC;AAErE,kFAAkF;AAClF,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,wCAAwC;IACvD,mFAAmF;IACnF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,0FAA0F;IAC1F,QAAQ,CAAC,UAAU,EAAE,SAAS,2BAA2B,EAAE,CAAC;IAC5D,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;QAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;KAC5B,CAAC;CACH;AAED,2FAA2F;AAC3F,oBAAY,iCAAiC;IAC3C,uFAAuF;IACvF,kBAAkB,sBAAsB;IACxC,gFAAgF;IAChF,SAAS,aAAa;IACtB;;;;;OAKG;IACH,YAAY,gBAAgB;IAC5B,yFAAyF;IACzF,cAAc,iBAAiB;CAChC;AAED;;;GAGG;AACH,oBAAY,gCAAgC;IAC1C,oGAAoG;IACpG,QAAQ,YAAY;IACpB,qDAAqD;IACrD,gBAAgB,mBAAmB;IACnC,mDAAmD;IACnD,cAAc,iBAAiB;IAC/B,sCAAsC;IACtC,cAAc,iBAAiB;IAC/B;;;;;;;;OAQG;IACH,aAAa,gBAAgB;IAC7B,kGAAkG;IAClG,YAAY,gBAAgB;IAC5B;;;;;;;;;;OAUG;IACH,kBAAkB,qBAAqB;CACxC;AAED,MAAM,WAAW,sCAAsC;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,iCAAiC,EAAE,MAAM,CAAC,CAAC,CAAC;IACpF,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,gCAAgC,EAAE,MAAM,CAAC,CAAC,CAAC;CACnF;AA0DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,6BAA6B,qCAAqC,CAAC;AAOhF;;;;GAIG;AACH,wBAAgB,yCAAyC,CACvD,MAAM,EAAE,wCAAwC,GAC/C,sCAAsC,CA2BxC"}
|
package/dist/esm/companion/application-documentation/application-connection-documentation.js
CHANGED
|
@@ -86,7 +86,7 @@ function baseUrlsMarkdown(source) {
|
|
|
86
86
|
return `${source.applicationName} does not publish its base URLs in this documentation. Read them from the **Servers** list at the top of the API reference, or ask whoever operates the environment you are calling.`;
|
|
87
87
|
}
|
|
88
88
|
const rows = source.apiServers
|
|
89
|
-
.map((server) => `| \`${server.url}\` | ${server.description ?? 'Published base URL'} |`)
|
|
89
|
+
.map((server, index) => `| \`${renderServerUrl(source, index, server.url)}\` | ${server.description ?? 'Published base URL'} |`)
|
|
90
90
|
.join('\n');
|
|
91
91
|
return `| Base URL | Environment |\n| --- | --- |\n${rows}`;
|
|
92
92
|
}
|
|
@@ -102,9 +102,9 @@ function a2aAgentCardMarkdown(source) {
|
|
|
102
102
|
}
|
|
103
103
|
function connectionDetailsMarkdown(source) {
|
|
104
104
|
const rows = [];
|
|
105
|
-
|
|
106
|
-
rows.push(`| Base URL${server.description === undefined ? '' : ` (${server.description})`} | \`${server.url}\` |`);
|
|
107
|
-
}
|
|
105
|
+
source.apiServers.forEach((server, index) => {
|
|
106
|
+
rows.push(`| Base URL${server.description === undefined ? '' : ` (${server.description})`} | \`${renderServerUrl(source, index, server.url)}\` |`);
|
|
107
|
+
});
|
|
108
108
|
rows.push(`| API mount | \`${MAIN_API_BASE_PATH}\` — every path in the API reference already includes it |`);
|
|
109
109
|
if (source.surfaces.mcpToolServer) {
|
|
110
110
|
rows.push(`| MCP endpoint | \`${publicRoutePath(ManualControllerRouteKey.MCP_ENDPOINT)}\` |`);
|
|
@@ -125,7 +125,48 @@ function connectionDetailsMarkdown(source) {
|
|
|
125
125
|
*/
|
|
126
126
|
function primaryBaseUrl(source) {
|
|
127
127
|
const first = source.apiServers[0];
|
|
128
|
-
return first === undefined ?
|
|
128
|
+
return first === undefined ? UNPUBLISHED_BASE_URL_STAND_IN : DOCS_API_ORIGIN_TOKEN;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* How the PRIMARY origin is rendered: as a build-time token, never as the origin generation happened
|
|
132
|
+
* to see (#1203).
|
|
133
|
+
*
|
|
134
|
+
* ## The defect
|
|
135
|
+
*
|
|
136
|
+
* A documentation image built for staging shipped 42 occurrences of `http://localhost:4241` — the
|
|
137
|
+
* SCIM base URL an administrator pastes into their identity provider, the MCP and A2A endpoints, and
|
|
138
|
+
* the `servers` of the published API description. Generation resolved this value on a developer
|
|
139
|
+
* machine, and the resolved bytes are committed, so every later build carried that environment's
|
|
140
|
+
* origin whatever it was built for. The CSP got a per-environment regeneration step; the CONTENT got
|
|
141
|
+
* none.
|
|
142
|
+
*
|
|
143
|
+
* ## Why a token rather than regenerating per environment
|
|
144
|
+
*
|
|
145
|
+
* Regenerating the content in the deploy workflow needs a CLI entry point for a companion-owned
|
|
146
|
+
* generation — heavy for a deploy job — and would still leave the committed artifacts describing
|
|
147
|
+
* whoever generated them last. A token makes the generated content environment-INDEPENDENT, which is
|
|
148
|
+
* the honest thing for a committed artifact, and moves the environment decision to the only place
|
|
149
|
+
* that knows it: the image build, which already receives `WILDO_DOCS_API_BASE_URL` as a docker build
|
|
150
|
+
* argument (`ci-workflow-generator.service.ts` declares it for `AppFrontendType.TECH_DOC`).
|
|
151
|
+
*
|
|
152
|
+
* It also covers the static OpenAPI documents, which are served as files and have no runtime to read
|
|
153
|
+
* `customFields` from — the reason the runtime-substitution option cannot be the whole answer.
|
|
154
|
+
*
|
|
155
|
+
* ## Only the PRIMARY origin
|
|
156
|
+
*
|
|
157
|
+
* Additional entries in `apiServers` are explicitly authored per-environment declarations; a reader
|
|
158
|
+
* who lists several is naming them deliberately and each is already correct. The primary is the one
|
|
159
|
+
* every sample interpolates, and the one an image is built FOR.
|
|
160
|
+
*/
|
|
161
|
+
export const DOCS_API_ORIGIN_TOKEN = '__WILDO_DOCS_API_ORIGIN__';
|
|
162
|
+
/**
|
|
163
|
+
* Shown when the application publishes no base URL at all. Unchanged by #1203: there is no origin to
|
|
164
|
+
* defer, so the reader is told to substitute their own — which is what this always said.
|
|
165
|
+
*/
|
|
166
|
+
export const UNPUBLISHED_BASE_URL_STAND_IN = 'https://your-environment.example';
|
|
167
|
+
/** The primary server renders as the token; any further declared server renders as authored. */
|
|
168
|
+
function renderServerUrl(source, index, url) {
|
|
169
|
+
return index === 0 ? DOCS_API_ORIGIN_TOKEN : url;
|
|
129
170
|
}
|
|
130
171
|
/**
|
|
131
172
|
* Projects the application's connection facts into the presentations a page may
|
package/dist/esm/companion/application-documentation/application-connection-documentation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application-connection-documentation.js","sourceRoot":"","sources":["../../../../../src/companion/application-documentation/application-connection-documentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAIN_API_BASE_PATH, MANUAL_CONTROLLER_ROUTES_URLS, ManualControllerRouteKey, rfc8414AuthorizationServerMetadataPath, SCIM_SERVICE_BASE_PATH } from '@wildo-ai/saas-models';\n\n/**\n * The application's own name and the addresses an integrator connects to.\n *\n * Engine documentation is written once for every generated application, so it\n * used to hedge everything an application knows about itself: \"the server origin\n * from this application's API reference\", \"the address published for this\n * environment\", \"the application\". Every one of those values is available to the\n * companion at derivation time, and this projection is how they reach the page.\n *\n * The split is deliberate. The APPLICATION supplies what only it knows — its\n * customer-facing name and the base URLs it publishes. The ENGINE supplies the\n * paths, because a route is the engine's own contract and an application that\n * restated it would hold a second copy that can drift (see\n * `MANUAL_CONTROLLER_ROUTES_URLS`, whose values are mounted under\n * `MAIN_API_BASE_PATH`). An endpoint appears only when the companion has\n * observed that the application configures that surface, so a page never\n * advertises a door this application does not open.\n */\nexport const APPLICATION_CONNECTION_DOCUMENTATION_SOURCE_REF = 'source:companion-projection:application-connection' as const;\nexport const APPLICATION_CONNECTION_DOCUMENTATION_SOURCE_VERSION = 1;\n\n/** One base URL an application publishes for an environment a reader may call. */\nexport interface ApplicationConnectionServer {\n readonly url: string;\n readonly description?: string;\n}\n\n/**\n * What the companion measures about this application. `surfaces` mirrors the\n * evidence the derivation already computes for applicability gating: the same\n * observation that decides whether the MCP pages are published decides whether\n * the MCP endpoint is printed, so a published address and a published page can\n * never disagree.\n */\nexport interface ApplicationConnectionDocumentationSource {\n /** The name customers see. The application's `displayName`, else its `appName`. */\n readonly applicationName: string;\n /** Base URLs the application publishes, in the order it authored them. Possibly empty. */\n readonly apiServers: readonly ApplicationConnectionServer[];\n readonly surfaces: {\n readonly mcpToolServer: boolean;\n readonly a2aAgent: boolean;\n };\n}\n\n/** Named presentations a page may request with `{{APPLICATION_CONNECTION:<selector>}}`. */\nexport enum ApplicationConnectionPresentation {\n /** Every connection detail as one table: base URLs, the API mount, agent endpoints. */\n CONNECTION_DETAILS = 'connectionDetails',\n /** The base URLs alone, for a page that only needs to say where requests go. */\n BASE_URLS = 'baseUrls',\n /**\n * The MCP endpoint sentence. Never empty: an application that exposes no MCP surface gets a\n * sentence saying so. Projection runs for every unit and gating happens afterwards, so a\n * presentation that rendered nothing would leave a blank where a suppressed page's prose was,\n * and would say nothing at all if the page were ever published without its gate.\n */\n MCP_ENDPOINT = 'mcpEndpoint',\n /** The A2A agent-card sentence. Never empty, for the same reason as the MCP endpoint. */\n A2A_AGENT_CARD = 'a2aAgentCard',\n}\n\n/**\n * Values a page substitutes INSIDE a sample — a JSON client configuration, an HTTP request line —\n * where a block presentation cannot go. Each is a single string, never markdown.\n */\nexport enum ApplicationConnectionInlineValue {\n /** The first base URL the application publishes, or an explicit stand-in when it publishes none. */\n BASE_URL = 'baseUrl',\n /** The full MCP endpoint URL a client configures. */\n MCP_ENDPOINT_URL = 'mcpEndpointUrl',\n /** The full agent-card URL an A2A peer fetches. */\n AGENT_CARD_URL = 'agentCardUrl',\n /** The full A2A task endpoint URL. */\n AGENT_TASK_URL = 'agentTaskUrl',\n /**\n * The full SCIM base URL an administrator pastes into their identity provider.\n *\n * Unlike the endpoints above this one is NOT gated on an observed surface, because SCIM is\n * configured in the IdP rather than advertised by the application: the base is where traffic\n * would be pushed, and whether a given organization has enabled provisioning is a tenant setting\n * the page discusses separately. Derived from `SCIM_SERVICE_BASE_PATH` for the same reason the\n * others derive from the route map — the engine owns the mount.\n */\n SCIM_BASE_URL = 'scimBaseUrl',\n /** The OAuth issuer a client configures, and checks the discovery document's `issuer` against. */\n OAUTH_ISSUER = 'oauthIssuer',\n /**\n * Where RFC 8414 authorization-server metadata is served.\n *\n * Derived with the engine's own `rfc8414AuthorizationServerMetadataPath`, never by appending the\n * well-known segment to the issuer. The RFC inserts it BETWEEN the host and the issuer's path, so\n * an issuer of `…/api/v1` publishes at `/.well-known/oauth-authorization-server/api/v1` — the\n * opposite construction from OIDC discovery. Getting that backwards is not hypothetical: a real\n * MCP client derived the RFC location, received the SPA shell, and abandoned authorization\n * without opening a browser. Documentation that showed a hand-built path would be teaching the\n * shape that failed.\n */\n OAUTH_METADATA_URL = 'oauthMetadataUrl',\n}\n\nexport interface ApplicationConnectionDocumentationFact {\n readonly sourceRef: string;\n readonly sourceVersion: number;\n /** Substituted wherever a page writes the inline application-name token. */\n readonly applicationName: string;\n readonly presentations: Readonly<Record<ApplicationConnectionPresentation, string>>;\n readonly inlineValues: Readonly<Record<ApplicationConnectionInlineValue, string>>;\n}\n\n/** A manual route as a reader must call it: the mount plus the engine-owned path segment. */\nfunction publicRoutePath(routeKey: ManualControllerRouteKey): string {\n return `${MAIN_API_BASE_PATH}${MANUAL_CONTROLLER_ROUTES_URLS[routeKey]}`;\n}\n\nfunction baseUrlsMarkdown(source: ApplicationConnectionDocumentationSource): string {\n if (source.apiServers.length === 0) {\n // Honest rather than invented: an application that publishes no base URL cannot have one\n // printed for it, and the reader is told where the answer actually lives.\n return `${source.applicationName} does not publish its base URLs in this documentation. Read them from the **Servers** list at the top of the API reference, or ask whoever operates the environment you are calling.`;\n }\n const rows = source.apiServers\n .map((server) => `| \\`${server.url}\\` | ${server.description ?? 'Published base URL'} |`)\n .join('\\n');\n return `| Base URL | Environment |\\n| --- | --- |\\n${rows}`;\n}\n\nfunction mcpEndpointMarkdown(source: ApplicationConnectionDocumentationSource): string {\n if (!source.surfaces.mcpToolServer) return `${source.applicationName} does not publish a Model Context Protocol endpoint.`;\n return `${source.applicationName} serves its Model Context Protocol endpoint at \\`${publicRoutePath(ManualControllerRouteKey.MCP_ENDPOINT)}\\`, under the base URL of the environment you are connecting to.`;\n}\n\nfunction a2aAgentCardMarkdown(source: ApplicationConnectionDocumentationSource): string {\n if (!source.surfaces.a2aAgent) return `${source.applicationName} does not publish an agent card.`;\n return `${source.applicationName} publishes its agent card at \\`${publicRoutePath(ManualControllerRouteKey.A2A_AGENT_CARD)}\\` and accepts task calls at \\`${publicRoutePath(ManualControllerRouteKey.A2A_TASKS)}\\`, under the base URL of the environment you are connecting to.`;\n}\n\nfunction connectionDetailsMarkdown(source: ApplicationConnectionDocumentationSource): string {\n const rows: string[] = [];\n for (const server of source.apiServers) {\n rows.push(`| Base URL${server.description === undefined ? '' : ` (${server.description})`} | \\`${server.url}\\` |`);\n }\n rows.push(`| API mount | \\`${MAIN_API_BASE_PATH}\\` — every path in the API reference already includes it |`);\n if (source.surfaces.mcpToolServer) {\n rows.push(`| MCP endpoint | \\`${publicRoutePath(ManualControllerRouteKey.MCP_ENDPOINT)}\\` |`);\n }\n if (source.surfaces.a2aAgent) {\n rows.push(`| A2A agent card | \\`${publicRoutePath(ManualControllerRouteKey.A2A_AGENT_CARD)}\\` |`);\n rows.push(`| A2A task endpoint | \\`${publicRoutePath(ManualControllerRouteKey.A2A_TASKS)}\\` |`);\n }\n const table = `| What | Value |\\n| --- | --- |\\n${rows.join('\\n')}`;\n return source.apiServers.length > 0\n ? table\n : `${table}\\n\\nThis application publishes no base URL here; read it from the **Servers** list at the top of the API reference.`;\n}\n\n/**\n * The origin a sample should show. An application that publishes no base URL gets an explicit\n * stand-in rather than a fabricated host: the reader must substitute their own environment, and the\n * sample says so in the place where the value belongs.\n */\nfunction primaryBaseUrl(source: ApplicationConnectionDocumentationSource): string {\n const first = source.apiServers[0];\n return first === undefined ? 'https://your-environment.example' : first.url.replace(/\\/+$/, '');\n}\n\n/**\n * Projects the application's connection facts into the presentations a page may\n * request. Every presentation is a pure function of the source, so two runs over\n * the same application produce identical bytes.\n */\nexport function projectApplicationConnectionDocumentation(\n source: ApplicationConnectionDocumentationSource,\n): ApplicationConnectionDocumentationFact {\n const applicationName = source.applicationName.trim();\n if (applicationName.length === 0) throw new Error('application connection projection requires a customer-facing application name');\n for (const server of source.apiServers) {\n if (server.url.trim().length === 0) throw new Error('application connection projection received an empty base URL');\n }\n const named: ApplicationConnectionDocumentationSource = { ...source, applicationName };\n return {\n sourceRef: APPLICATION_CONNECTION_DOCUMENTATION_SOURCE_REF,\n sourceVersion: APPLICATION_CONNECTION_DOCUMENTATION_SOURCE_VERSION,\n applicationName,\n presentations: Object.freeze({\n [ApplicationConnectionPresentation.CONNECTION_DETAILS]: connectionDetailsMarkdown(named),\n [ApplicationConnectionPresentation.BASE_URLS]: baseUrlsMarkdown(named),\n [ApplicationConnectionPresentation.MCP_ENDPOINT]: mcpEndpointMarkdown(named),\n [ApplicationConnectionPresentation.A2A_AGENT_CARD]: a2aAgentCardMarkdown(named),\n }),\n inlineValues: Object.freeze({\n [ApplicationConnectionInlineValue.BASE_URL]: primaryBaseUrl(named),\n [ApplicationConnectionInlineValue.MCP_ENDPOINT_URL]: `${primaryBaseUrl(named)}${publicRoutePath(ManualControllerRouteKey.MCP_ENDPOINT)}`,\n [ApplicationConnectionInlineValue.AGENT_CARD_URL]: `${primaryBaseUrl(named)}${publicRoutePath(ManualControllerRouteKey.A2A_AGENT_CARD)}`,\n [ApplicationConnectionInlineValue.AGENT_TASK_URL]: `${primaryBaseUrl(named)}${publicRoutePath(ManualControllerRouteKey.A2A_TASKS)}`,\n [ApplicationConnectionInlineValue.SCIM_BASE_URL]: `${primaryBaseUrl(named)}${SCIM_SERVICE_BASE_PATH}`,\n [ApplicationConnectionInlineValue.OAUTH_ISSUER]: `${primaryBaseUrl(named)}${MAIN_API_BASE_PATH}`,\n [ApplicationConnectionInlineValue.OAUTH_METADATA_URL]: `${primaryBaseUrl(named)}${rfc8414AuthorizationServerMetadataPath(MAIN_API_BASE_PATH)}`,\n }),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"application-connection-documentation.js","sourceRoot":"","sources":["../../../../../src/companion/application-documentation/application-connection-documentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAIN_API_BASE_PATH, MANUAL_CONTROLLER_ROUTES_URLS, ManualControllerRouteKey, rfc8414AuthorizationServerMetadataPath, SCIM_SERVICE_BASE_PATH } from '@wildo-ai/saas-models';\n\n/**\n * The application's own name and the addresses an integrator connects to.\n *\n * Engine documentation is written once for every generated application, so it\n * used to hedge everything an application knows about itself: \"the server origin\n * from this application's API reference\", \"the address published for this\n * environment\", \"the application\". Every one of those values is available to the\n * companion at derivation time, and this projection is how they reach the page.\n *\n * The split is deliberate. The APPLICATION supplies what only it knows — its\n * customer-facing name and the base URLs it publishes. The ENGINE supplies the\n * paths, because a route is the engine's own contract and an application that\n * restated it would hold a second copy that can drift (see\n * `MANUAL_CONTROLLER_ROUTES_URLS`, whose values are mounted under\n * `MAIN_API_BASE_PATH`). An endpoint appears only when the companion has\n * observed that the application configures that surface, so a page never\n * advertises a door this application does not open.\n */\nexport const APPLICATION_CONNECTION_DOCUMENTATION_SOURCE_REF = 'source:companion-projection:application-connection' as const;\nexport const APPLICATION_CONNECTION_DOCUMENTATION_SOURCE_VERSION = 1;\n\n/** One base URL an application publishes for an environment a reader may call. */\nexport interface ApplicationConnectionServer {\n readonly url: string;\n readonly description?: string;\n}\n\n/**\n * What the companion measures about this application. `surfaces` mirrors the\n * evidence the derivation already computes for applicability gating: the same\n * observation that decides whether the MCP pages are published decides whether\n * the MCP endpoint is printed, so a published address and a published page can\n * never disagree.\n */\nexport interface ApplicationConnectionDocumentationSource {\n /** The name customers see. The application's `displayName`, else its `appName`. */\n readonly applicationName: string;\n /** Base URLs the application publishes, in the order it authored them. Possibly empty. */\n readonly apiServers: readonly ApplicationConnectionServer[];\n readonly surfaces: {\n readonly mcpToolServer: boolean;\n readonly a2aAgent: boolean;\n };\n}\n\n/** Named presentations a page may request with `{{APPLICATION_CONNECTION:<selector>}}`. */\nexport enum ApplicationConnectionPresentation {\n /** Every connection detail as one table: base URLs, the API mount, agent endpoints. */\n CONNECTION_DETAILS = 'connectionDetails',\n /** The base URLs alone, for a page that only needs to say where requests go. */\n BASE_URLS = 'baseUrls',\n /**\n * The MCP endpoint sentence. Never empty: an application that exposes no MCP surface gets a\n * sentence saying so. Projection runs for every unit and gating happens afterwards, so a\n * presentation that rendered nothing would leave a blank where a suppressed page's prose was,\n * and would say nothing at all if the page were ever published without its gate.\n */\n MCP_ENDPOINT = 'mcpEndpoint',\n /** The A2A agent-card sentence. Never empty, for the same reason as the MCP endpoint. */\n A2A_AGENT_CARD = 'a2aAgentCard',\n}\n\n/**\n * Values a page substitutes INSIDE a sample — a JSON client configuration, an HTTP request line —\n * where a block presentation cannot go. Each is a single string, never markdown.\n */\nexport enum ApplicationConnectionInlineValue {\n /** The first base URL the application publishes, or an explicit stand-in when it publishes none. */\n BASE_URL = 'baseUrl',\n /** The full MCP endpoint URL a client configures. */\n MCP_ENDPOINT_URL = 'mcpEndpointUrl',\n /** The full agent-card URL an A2A peer fetches. */\n AGENT_CARD_URL = 'agentCardUrl',\n /** The full A2A task endpoint URL. */\n AGENT_TASK_URL = 'agentTaskUrl',\n /**\n * The full SCIM base URL an administrator pastes into their identity provider.\n *\n * Unlike the endpoints above this one is NOT gated on an observed surface, because SCIM is\n * configured in the IdP rather than advertised by the application: the base is where traffic\n * would be pushed, and whether a given organization has enabled provisioning is a tenant setting\n * the page discusses separately. Derived from `SCIM_SERVICE_BASE_PATH` for the same reason the\n * others derive from the route map — the engine owns the mount.\n */\n SCIM_BASE_URL = 'scimBaseUrl',\n /** The OAuth issuer a client configures, and checks the discovery document's `issuer` against. */\n OAUTH_ISSUER = 'oauthIssuer',\n /**\n * Where RFC 8414 authorization-server metadata is served.\n *\n * Derived with the engine's own `rfc8414AuthorizationServerMetadataPath`, never by appending the\n * well-known segment to the issuer. The RFC inserts it BETWEEN the host and the issuer's path, so\n * an issuer of `…/api/v1` publishes at `/.well-known/oauth-authorization-server/api/v1` — the\n * opposite construction from OIDC discovery. Getting that backwards is not hypothetical: a real\n * MCP client derived the RFC location, received the SPA shell, and abandoned authorization\n * without opening a browser. Documentation that showed a hand-built path would be teaching the\n * shape that failed.\n */\n OAUTH_METADATA_URL = 'oauthMetadataUrl',\n}\n\nexport interface ApplicationConnectionDocumentationFact {\n readonly sourceRef: string;\n readonly sourceVersion: number;\n /** Substituted wherever a page writes the inline application-name token. */\n readonly applicationName: string;\n readonly presentations: Readonly<Record<ApplicationConnectionPresentation, string>>;\n readonly inlineValues: Readonly<Record<ApplicationConnectionInlineValue, string>>;\n}\n\n/** A manual route as a reader must call it: the mount plus the engine-owned path segment. */\nfunction publicRoutePath(routeKey: ManualControllerRouteKey): string {\n return `${MAIN_API_BASE_PATH}${MANUAL_CONTROLLER_ROUTES_URLS[routeKey]}`;\n}\n\nfunction baseUrlsMarkdown(source: ApplicationConnectionDocumentationSource): string {\n if (source.apiServers.length === 0) {\n // Honest rather than invented: an application that publishes no base URL cannot have one\n // printed for it, and the reader is told where the answer actually lives.\n return `${source.applicationName} does not publish its base URLs in this documentation. Read them from the **Servers** list at the top of the API reference, or ask whoever operates the environment you are calling.`;\n }\n const rows = source.apiServers\n .map((server, index) => `| \\`${renderServerUrl(source, index, server.url)}\\` | ${server.description ?? 'Published base URL'} |`)\n .join('\\n');\n return `| Base URL | Environment |\\n| --- | --- |\\n${rows}`;\n}\n\nfunction mcpEndpointMarkdown(source: ApplicationConnectionDocumentationSource): string {\n if (!source.surfaces.mcpToolServer) return `${source.applicationName} does not publish a Model Context Protocol endpoint.`;\n return `${source.applicationName} serves its Model Context Protocol endpoint at \\`${publicRoutePath(ManualControllerRouteKey.MCP_ENDPOINT)}\\`, under the base URL of the environment you are connecting to.`;\n}\n\nfunction a2aAgentCardMarkdown(source: ApplicationConnectionDocumentationSource): string {\n if (!source.surfaces.a2aAgent) return `${source.applicationName} does not publish an agent card.`;\n return `${source.applicationName} publishes its agent card at \\`${publicRoutePath(ManualControllerRouteKey.A2A_AGENT_CARD)}\\` and accepts task calls at \\`${publicRoutePath(ManualControllerRouteKey.A2A_TASKS)}\\`, under the base URL of the environment you are connecting to.`;\n}\n\nfunction connectionDetailsMarkdown(source: ApplicationConnectionDocumentationSource): string {\n const rows: string[] = [];\n source.apiServers.forEach((server, index) => {\n rows.push(`| Base URL${server.description === undefined ? '' : ` (${server.description})`} | \\`${renderServerUrl(source, index, server.url)}\\` |`);\n });\n rows.push(`| API mount | \\`${MAIN_API_BASE_PATH}\\` — every path in the API reference already includes it |`);\n if (source.surfaces.mcpToolServer) {\n rows.push(`| MCP endpoint | \\`${publicRoutePath(ManualControllerRouteKey.MCP_ENDPOINT)}\\` |`);\n }\n if (source.surfaces.a2aAgent) {\n rows.push(`| A2A agent card | \\`${publicRoutePath(ManualControllerRouteKey.A2A_AGENT_CARD)}\\` |`);\n rows.push(`| A2A task endpoint | \\`${publicRoutePath(ManualControllerRouteKey.A2A_TASKS)}\\` |`);\n }\n const table = `| What | Value |\\n| --- | --- |\\n${rows.join('\\n')}`;\n return source.apiServers.length > 0\n ? table\n : `${table}\\n\\nThis application publishes no base URL here; read it from the **Servers** list at the top of the API reference.`;\n}\n\n/**\n * The origin a sample should show. An application that publishes no base URL gets an explicit\n * stand-in rather than a fabricated host: the reader must substitute their own environment, and the\n * sample says so in the place where the value belongs.\n */\nfunction primaryBaseUrl(source: ApplicationConnectionDocumentationSource): string {\n const first = source.apiServers[0];\n return first === undefined ? UNPUBLISHED_BASE_URL_STAND_IN : DOCS_API_ORIGIN_TOKEN;\n}\n\n/**\n * How the PRIMARY origin is rendered: as a build-time token, never as the origin generation happened\n * to see (#1203).\n *\n * ## The defect\n *\n * A documentation image built for staging shipped 42 occurrences of `http://localhost:4241` — the\n * SCIM base URL an administrator pastes into their identity provider, the MCP and A2A endpoints, and\n * the `servers` of the published API description. Generation resolved this value on a developer\n * machine, and the resolved bytes are committed, so every later build carried that environment's\n * origin whatever it was built for. The CSP got a per-environment regeneration step; the CONTENT got\n * none.\n *\n * ## Why a token rather than regenerating per environment\n *\n * Regenerating the content in the deploy workflow needs a CLI entry point for a companion-owned\n * generation — heavy for a deploy job — and would still leave the committed artifacts describing\n * whoever generated them last. A token makes the generated content environment-INDEPENDENT, which is\n * the honest thing for a committed artifact, and moves the environment decision to the only place\n * that knows it: the image build, which already receives `WILDO_DOCS_API_BASE_URL` as a docker build\n * argument (`ci-workflow-generator.service.ts` declares it for `AppFrontendType.TECH_DOC`).\n *\n * It also covers the static OpenAPI documents, which are served as files and have no runtime to read\n * `customFields` from — the reason the runtime-substitution option cannot be the whole answer.\n *\n * ## Only the PRIMARY origin\n *\n * Additional entries in `apiServers` are explicitly authored per-environment declarations; a reader\n * who lists several is naming them deliberately and each is already correct. The primary is the one\n * every sample interpolates, and the one an image is built FOR.\n */\nexport const DOCS_API_ORIGIN_TOKEN = '__WILDO_DOCS_API_ORIGIN__';\n\n/**\n * Shown when the application publishes no base URL at all. Unchanged by #1203: there is no origin to\n * defer, so the reader is told to substitute their own — which is what this always said.\n */\nexport const UNPUBLISHED_BASE_URL_STAND_IN = 'https://your-environment.example';\n\n/** The primary server renders as the token; any further declared server renders as authored. */\nfunction renderServerUrl(source: ApplicationConnectionDocumentationSource, index: number, url: string): string {\n return index === 0 ? DOCS_API_ORIGIN_TOKEN : url;\n}\n\n/**\n * Projects the application's connection facts into the presentations a page may\n * request. Every presentation is a pure function of the source, so two runs over\n * the same application produce identical bytes.\n */\nexport function projectApplicationConnectionDocumentation(\n source: ApplicationConnectionDocumentationSource,\n): ApplicationConnectionDocumentationFact {\n const applicationName = source.applicationName.trim();\n if (applicationName.length === 0) throw new Error('application connection projection requires a customer-facing application name');\n for (const server of source.apiServers) {\n if (server.url.trim().length === 0) throw new Error('application connection projection received an empty base URL');\n }\n const named: ApplicationConnectionDocumentationSource = { ...source, applicationName };\n return {\n sourceRef: APPLICATION_CONNECTION_DOCUMENTATION_SOURCE_REF,\n sourceVersion: APPLICATION_CONNECTION_DOCUMENTATION_SOURCE_VERSION,\n applicationName,\n presentations: Object.freeze({\n [ApplicationConnectionPresentation.CONNECTION_DETAILS]: connectionDetailsMarkdown(named),\n [ApplicationConnectionPresentation.BASE_URLS]: baseUrlsMarkdown(named),\n [ApplicationConnectionPresentation.MCP_ENDPOINT]: mcpEndpointMarkdown(named),\n [ApplicationConnectionPresentation.A2A_AGENT_CARD]: a2aAgentCardMarkdown(named),\n }),\n inlineValues: Object.freeze({\n [ApplicationConnectionInlineValue.BASE_URL]: primaryBaseUrl(named),\n [ApplicationConnectionInlineValue.MCP_ENDPOINT_URL]: `${primaryBaseUrl(named)}${publicRoutePath(ManualControllerRouteKey.MCP_ENDPOINT)}`,\n [ApplicationConnectionInlineValue.AGENT_CARD_URL]: `${primaryBaseUrl(named)}${publicRoutePath(ManualControllerRouteKey.A2A_AGENT_CARD)}`,\n [ApplicationConnectionInlineValue.AGENT_TASK_URL]: `${primaryBaseUrl(named)}${publicRoutePath(ManualControllerRouteKey.A2A_TASKS)}`,\n [ApplicationConnectionInlineValue.SCIM_BASE_URL]: `${primaryBaseUrl(named)}${SCIM_SERVICE_BASE_PATH}`,\n [ApplicationConnectionInlineValue.OAUTH_ISSUER]: `${primaryBaseUrl(named)}${MAIN_API_BASE_PATH}`,\n [ApplicationConnectionInlineValue.OAUTH_METADATA_URL]: `${primaryBaseUrl(named)}${rfc8414AuthorizationServerMetadataPath(MAIN_API_BASE_PATH)}`,\n }),\n };\n}\n"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chapters an APPLICATION authors, joined to the plan that declares them and projected into
|
|
3
|
+
* publishable units (#874).
|
|
4
|
+
*
|
|
5
|
+
* Until this module the portal could say only what the engine wrote or what the companion projected:
|
|
6
|
+
* a customer could read a page per resource, and nothing about the journeys that cross them, and
|
|
7
|
+
* the private derivation's own contract said application content stayed absent until the
|
|
8
|
+
* app-creator owned an accepted authoring artifact. Two accepted artifacts now exist, both
|
|
9
|
+
* specification FILES accepted by being committed:
|
|
10
|
+
*
|
|
11
|
+
* | artifact | owns |
|
|
12
|
+
* | --- | --- |
|
|
13
|
+
* | the documentation plan (`documentationPlan`) | each chapter's identity — ref, title, kind, audiences |
|
|
14
|
+
* | the authored chapters (`documentationChapters`) | each chapter's body, classification, API references |
|
|
15
|
+
*
|
|
16
|
+
* This is the join, and it mirrors the framework's own catalogue: authored Markdown joined to a unit
|
|
17
|
+
* definition by key, with the projector refusing a content set that does not match its definitions.
|
|
18
|
+
*
|
|
19
|
+
* ## What is refused, and what is merely withheld
|
|
20
|
+
*
|
|
21
|
+
* | condition | outcome | why |
|
|
22
|
+
* | --- | --- | --- |
|
|
23
|
+
* | a chapter no plan declares (or no plan at all) | withheld, and reported (`authoredChapterRefsWithoutPlan`) | the plan owns the chapter's title, kind and audiences, so there is nothing true to publish it under — but the body is unfinished work, not a false statement |
|
|
24
|
+
* | a `RESTRICTED` chapter | withheld by the publication policy (`RESTRICTED_SOURCE`) | the chapter is true, and not for this audience |
|
|
25
|
+
* | a `DRAFT` / `REVIEW_REQUIRED` chapter | withheld by the policy (`EDITORIAL_NOT_APPROVED`) | the author said it is not ready |
|
|
26
|
+
* | a planned chapter with no body yet | not published, and reported | unfinished rather than false |
|
|
27
|
+
* | an API reference the rendered reference does not carry | THROWS at publication | see {@link assertApplicationDocumentationChapterApiReferencesResolve} |
|
|
28
|
+
*
|
|
29
|
+
* Refusal is kept to the one case where publishing would state something untrue — a guide telling a
|
|
30
|
+
* reader to call an operation that does not exist. Everything else is withheld and reported, so it
|
|
31
|
+
* travels with the artifact and stops nothing (`application-creation-directive.md`).
|
|
32
|
+
*
|
|
33
|
+
* Rejected alternative, kept here because it was the first version: REFUSING an unplanned body. It
|
|
34
|
+
* made one orphaned chapter — a plan ref renamed, a draft written ahead of its plan entry — stop the
|
|
35
|
+
* whole portal, including the build lane's derived publication step, although withholding that body
|
|
36
|
+
* publishes nothing untrue.
|
|
37
|
+
*/
|
|
38
|
+
import { type ApplicationDocumentationChapterApiReference, type TechnicalDocumentationApiReferenceTargetV1, type TechnicalDocumentationAuthorizedBundleManifestV1, type TechnicalDocumentationUnitV1 } from '@wildo-ai/saas-specifications/technical-documentation';
|
|
39
|
+
import { type ApiReferenceLinkIndex } from '../../openapi/api-reference-link-index';
|
|
40
|
+
/**
|
|
41
|
+
* The unit-ref namespace authored chapters live in, and the only place its shape is written.
|
|
42
|
+
*
|
|
43
|
+
* A RESERVED engine namespace, like `unit/manages/`: the renderer routes it to `guides/<slug>`, so
|
|
44
|
+
* an application chooses a chapter's slug but never its URL scheme, and nothing an application names
|
|
45
|
+
* can land in the engine's own route table.
|
|
46
|
+
*/
|
|
47
|
+
export declare const APPLICATION_DOCUMENTATION_CHAPTER_UNIT_REF_PREFIX = "technical-documentation:unit/guides/";
|
|
48
|
+
/** The portal directory every authored chapter is published under. */
|
|
49
|
+
export declare const APPLICATION_DOCUMENTATION_CHAPTER_ROUTE_DIRECTORY = "guides";
|
|
50
|
+
/** Provenance ref of one authored chapter — the file a reader traces the page back to. */
|
|
51
|
+
export declare const APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_PREFIX = "source:application-chapter:";
|
|
52
|
+
/** `chapter-close-a-deal` → `close-a-deal`: the slug a route and a unit ref are built from. */
|
|
53
|
+
export declare function applicationDocumentationChapterSlug(chapterRef: string): string;
|
|
54
|
+
export declare function applicationDocumentationChapterUnitRef(chapterRef: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* The portable target one authored API reference names.
|
|
57
|
+
*
|
|
58
|
+
* Built with `encodeURIComponent`, exactly as the OpenAPI identity constructor builds the refs it
|
|
59
|
+
* indexes (`createOpenApiOperationIdentity`) — so an operation named `complete-all` or one carrying a
|
|
60
|
+
* `:` resolves to the same key on both sides rather than failing on an encoding difference.
|
|
61
|
+
*/
|
|
62
|
+
export declare function applicationDocumentationChapterApiReferenceTarget(reference: ApplicationDocumentationChapterApiReference): TechnicalDocumentationApiReferenceTargetV1;
|
|
63
|
+
export interface ProjectApplicationDocumentationChaptersInput {
|
|
64
|
+
/** The accepted plan, as the specifications package exports it. Parsed here: the export is untyped at runtime. */
|
|
65
|
+
readonly plan: unknown;
|
|
66
|
+
/** The accepted chapter bodies, as the specifications package exports them. Parsed here too. */
|
|
67
|
+
readonly chapters: unknown;
|
|
68
|
+
}
|
|
69
|
+
export interface ApplicationDocumentationChaptersProjection {
|
|
70
|
+
/** One unit per authored chapter, unfiltered: the publication policy decides what a reader sees. */
|
|
71
|
+
readonly units: readonly TechnicalDocumentationUnitV1[];
|
|
72
|
+
/** Chapters the plan declares whose body nobody has authored yet — reported, never refused. */
|
|
73
|
+
readonly plannedChapterRefsWithoutBody: readonly string[];
|
|
74
|
+
/**
|
|
75
|
+
* Bodies authored under a `chapterRef` the accepted plan does not declare (or with no plan, or a
|
|
76
|
+
* NOT_APPLICABLE one). Withheld — they have no title, kind or audience to publish under — and
|
|
77
|
+
* reported, never refused.
|
|
78
|
+
*/
|
|
79
|
+
readonly authoredChapterRefsWithoutPlan: readonly string[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Joins the authored chapters to the plan that declares them. A pure function of its two inputs.
|
|
83
|
+
* A body the plan does not declare is withheld and reported — see the module table.
|
|
84
|
+
*/
|
|
85
|
+
export declare function projectApplicationDocumentationChapters(input: ProjectApplicationDocumentationChaptersInput): ApplicationDocumentationChaptersProjection;
|
|
86
|
+
/**
|
|
87
|
+
* Refuses a publication in which an authored chapter cites an API contract the rendered reference
|
|
88
|
+
* does not carry.
|
|
89
|
+
*
|
|
90
|
+
* The engine's own pages DEGRADE such a link to plain text (#472), and for them that is right: the
|
|
91
|
+
* engine hardcodes targets naming resources an application may switch off, so an absent target there
|
|
92
|
+
* is a configuration fact. An application's chapter has no such excuse — its author wrote the
|
|
93
|
+
* reference against THIS application, so an absent target is a typo, a renamed operation or a
|
|
94
|
+
* removed one, and a guide telling a reader to call an operation that does not exist is exactly the
|
|
95
|
+
* sentence this portal must never publish. So it fails, naming every dangling reference at once.
|
|
96
|
+
*
|
|
97
|
+
* Checked against the PUBLISHED bundle only: a withheld chapter never reaches a reader, and a
|
|
98
|
+
* restricted one may legitimately cite a contract the public reference omits.
|
|
99
|
+
*/
|
|
100
|
+
export declare function assertApplicationDocumentationChapterApiReferencesResolve(publicBundle: TechnicalDocumentationAuthorizedBundleManifestV1, apiReferenceLinkIndex: ApiReferenceLinkIndex): void;
|
|
101
|
+
//# sourceMappingURL=application-documentation-chapters.d.ts.map
|
package/dist/esm/companion/application-documentation/application-documentation-chapters.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"application-documentation-chapters.d.ts","sourceRoot":"","sources":["../../../../../src/companion/application-documentation/application-documentation-chapters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAYL,KAAK,2CAA2C,EAGhD,KAAK,0CAA0C,EAC/C,KAAK,gDAAgD,EAErD,KAAK,4BAA4B,EAClC,MAAM,uDAAuD,CAAC;AAE/D,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,wCAAwC,CAAC;AAGhD;;;;;;GAMG;AACH,eAAO,MAAM,iDAAiD,yCAAyC,CAAC;AAExG,sEAAsE;AACtE,eAAO,MAAM,iDAAiD,WAAW,CAAC;AAE1E,0FAA0F;AAC1F,eAAO,MAAM,+CAA+C,gCAAgC,CAAC;AAS7F,+FAA+F;AAC/F,wBAAgB,mCAAmC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAK9E;AAED,wBAAgB,sCAAsC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED;;;;;;GAMG;AACH,wBAAgB,iDAAiD,CAAC,SAAS,EAAE,2CAA2C,GAAG,0CAA0C,CAepK;AAED,MAAM,WAAW,4CAA4C;IAC3D,kHAAkH;IAClH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,gGAAgG;IAChG,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,0CAA0C;IACzD,oGAAoG;IACpG,QAAQ,CAAC,KAAK,EAAE,SAAS,4BAA4B,EAAE,CAAC;IACxD,+FAA+F;IAC/F,QAAQ,CAAC,6BAA6B,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,8BAA8B,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5D;AAED;;;GAGG;AACH,wBAAgB,uCAAuC,CAAC,KAAK,EAAE,4CAA4C,GAAG,0CAA0C,CAoFvJ;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yDAAyD,CACvE,YAAY,EAAE,gDAAgD,EAC9D,qBAAqB,EAAE,qBAAqB,GAC3C,IAAI,CAcN"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chapters an APPLICATION authors, joined to the plan that declares them and projected into
|
|
3
|
+
* publishable units (#874).
|
|
4
|
+
*
|
|
5
|
+
* Until this module the portal could say only what the engine wrote or what the companion projected:
|
|
6
|
+
* a customer could read a page per resource, and nothing about the journeys that cross them, and
|
|
7
|
+
* the private derivation's own contract said application content stayed absent until the
|
|
8
|
+
* app-creator owned an accepted authoring artifact. Two accepted artifacts now exist, both
|
|
9
|
+
* specification FILES accepted by being committed:
|
|
10
|
+
*
|
|
11
|
+
* | artifact | owns |
|
|
12
|
+
* | --- | --- |
|
|
13
|
+
* | the documentation plan (`documentationPlan`) | each chapter's identity — ref, title, kind, audiences |
|
|
14
|
+
* | the authored chapters (`documentationChapters`) | each chapter's body, classification, API references |
|
|
15
|
+
*
|
|
16
|
+
* This is the join, and it mirrors the framework's own catalogue: authored Markdown joined to a unit
|
|
17
|
+
* definition by key, with the projector refusing a content set that does not match its definitions.
|
|
18
|
+
*
|
|
19
|
+
* ## What is refused, and what is merely withheld
|
|
20
|
+
*
|
|
21
|
+
* | condition | outcome | why |
|
|
22
|
+
* | --- | --- | --- |
|
|
23
|
+
* | a chapter no plan declares (or no plan at all) | withheld, and reported (`authoredChapterRefsWithoutPlan`) | the plan owns the chapter's title, kind and audiences, so there is nothing true to publish it under — but the body is unfinished work, not a false statement |
|
|
24
|
+
* | a `RESTRICTED` chapter | withheld by the publication policy (`RESTRICTED_SOURCE`) | the chapter is true, and not for this audience |
|
|
25
|
+
* | a `DRAFT` / `REVIEW_REQUIRED` chapter | withheld by the policy (`EDITORIAL_NOT_APPROVED`) | the author said it is not ready |
|
|
26
|
+
* | a planned chapter with no body yet | not published, and reported | unfinished rather than false |
|
|
27
|
+
* | an API reference the rendered reference does not carry | THROWS at publication | see {@link assertApplicationDocumentationChapterApiReferencesResolve} |
|
|
28
|
+
*
|
|
29
|
+
* Refusal is kept to the one case where publishing would state something untrue — a guide telling a
|
|
30
|
+
* reader to call an operation that does not exist. Everything else is withheld and reported, so it
|
|
31
|
+
* travels with the artifact and stops nothing (`application-creation-directive.md`).
|
|
32
|
+
*
|
|
33
|
+
* Rejected alternative, kept here because it was the first version: REFUSING an unplanned body. It
|
|
34
|
+
* made one orphaned chapter — a plan ref renamed, a draft written ahead of its plan entry — stop the
|
|
35
|
+
* whole portal, including the build lane's derived publication step, although withholding that body
|
|
36
|
+
* publishes nothing untrue.
|
|
37
|
+
*/
|
|
38
|
+
import { ApplicationDocumentationPlanDisposition, TechnicalDocumentationAccessClass, TechnicalDocumentationApiReferenceSection, TechnicalDocumentationApiReferenceTargetKind, TechnicalDocumentationEditorialStatus, TechnicalDocumentationLinkKind, TechnicalDocumentationSourceOwnership, DOCUMENTATION_CHAPTER_REF_PREFIX, ApplicationDocumentationChapterSetSchema, ApplicationDocumentationPlanSchema, createTechnicalDocumentationUnitV1, } from '@wildo-ai/saas-specifications/technical-documentation';
|
|
39
|
+
import { getTechnicalDocumentationApiReferenceTargetAvailability, TechnicalDocumentationApiReferenceTargetAvailability, } from '../../openapi/api-reference-link-index.js';
|
|
40
|
+
import { parseTechnicalDocumentationUnitMarkdown } from './technical-documentation-engine-content-bundle.js';
|
|
41
|
+
/**
|
|
42
|
+
* The unit-ref namespace authored chapters live in, and the only place its shape is written.
|
|
43
|
+
*
|
|
44
|
+
* A RESERVED engine namespace, like `unit/manages/`: the renderer routes it to `guides/<slug>`, so
|
|
45
|
+
* an application chooses a chapter's slug but never its URL scheme, and nothing an application names
|
|
46
|
+
* can land in the engine's own route table.
|
|
47
|
+
*/
|
|
48
|
+
export const APPLICATION_DOCUMENTATION_CHAPTER_UNIT_REF_PREFIX = 'technical-documentation:unit/guides/';
|
|
49
|
+
/** The portal directory every authored chapter is published under. */
|
|
50
|
+
export const APPLICATION_DOCUMENTATION_CHAPTER_ROUTE_DIRECTORY = 'guides';
|
|
51
|
+
/** Provenance ref of one authored chapter — the file a reader traces the page back to. */
|
|
52
|
+
export const APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_PREFIX = 'source:application-chapter:';
|
|
53
|
+
/**
|
|
54
|
+
* The only version an authored chapter carries. A chapter is versioned by git, not by a counter in
|
|
55
|
+
* the file (`one-source-of-truth.md`); the unit schema requires a positive version, and 1 states
|
|
56
|
+
* that nothing else is being claimed.
|
|
57
|
+
*/
|
|
58
|
+
const APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_VERSION = 1;
|
|
59
|
+
/** `chapter-close-a-deal` → `close-a-deal`: the slug a route and a unit ref are built from. */
|
|
60
|
+
export function applicationDocumentationChapterSlug(chapterRef) {
|
|
61
|
+
if (!chapterRef.startsWith(DOCUMENTATION_CHAPTER_REF_PREFIX)) {
|
|
62
|
+
throw new Error(`documentation chapter ref ${chapterRef} does not start with ${DOCUMENTATION_CHAPTER_REF_PREFIX}`);
|
|
63
|
+
}
|
|
64
|
+
return chapterRef.slice(DOCUMENTATION_CHAPTER_REF_PREFIX.length);
|
|
65
|
+
}
|
|
66
|
+
export function applicationDocumentationChapterUnitRef(chapterRef) {
|
|
67
|
+
return `${APPLICATION_DOCUMENTATION_CHAPTER_UNIT_REF_PREFIX}${applicationDocumentationChapterSlug(chapterRef)}`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The portable target one authored API reference names.
|
|
71
|
+
*
|
|
72
|
+
* Built with `encodeURIComponent`, exactly as the OpenAPI identity constructor builds the refs it
|
|
73
|
+
* indexes (`createOpenApiOperationIdentity`) — so an operation named `complete-all` or one carrying a
|
|
74
|
+
* `:` resolves to the same key on both sides rather than failing on an encoding difference.
|
|
75
|
+
*/
|
|
76
|
+
export function applicationDocumentationChapterApiReferenceTarget(reference) {
|
|
77
|
+
const resourceRef = `technical-documentation:resource/${encodeURIComponent(reference.resource)}`;
|
|
78
|
+
// Omitted means the public API reference, which is where almost every contract a guide cites lives.
|
|
79
|
+
const section = reference.section ?? TechnicalDocumentationApiReferenceSection.API_REFERENCE;
|
|
80
|
+
return reference.operation === undefined
|
|
81
|
+
? {
|
|
82
|
+
section,
|
|
83
|
+
kind: TechnicalDocumentationApiReferenceTargetKind.RESOURCE,
|
|
84
|
+
resourceRef,
|
|
85
|
+
}
|
|
86
|
+
: {
|
|
87
|
+
section,
|
|
88
|
+
kind: TechnicalDocumentationApiReferenceTargetKind.OPERATION_FAMILY,
|
|
89
|
+
operationFamilyRef: `${resourceRef}/operation-family/${encodeURIComponent(reference.operation)}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Joins the authored chapters to the plan that declares them. A pure function of its two inputs.
|
|
94
|
+
* A body the plan does not declare is withheld and reported — see the module table.
|
|
95
|
+
*/
|
|
96
|
+
export function projectApplicationDocumentationChapters(input) {
|
|
97
|
+
const chapters = input.chapters === undefined
|
|
98
|
+
? {}
|
|
99
|
+
: ApplicationDocumentationChapterSetSchema.parse(input.chapters);
|
|
100
|
+
const plan = input.plan === undefined
|
|
101
|
+
? undefined
|
|
102
|
+
: ApplicationDocumentationPlanSchema.parse(input.plan);
|
|
103
|
+
const plannedChapters = plan === undefined || plan.disposition === ApplicationDocumentationPlanDisposition.NOT_APPLICABLE ? [] : plan.chapters;
|
|
104
|
+
const plannedByRef = new Map(plannedChapters.map((chapter) => [chapter.chapterRef, chapter]));
|
|
105
|
+
const authoredChapterRefsWithoutPlan = Object.keys(chapters).filter((chapterRef) => !plannedByRef.has(chapterRef)).sort();
|
|
106
|
+
const units = Object.entries(chapters)
|
|
107
|
+
.filter(([chapterRef]) => plannedByRef.has(chapterRef))
|
|
108
|
+
.sort(([left], [right]) => left.localeCompare(right, 'en-US'))
|
|
109
|
+
.map(([chapterRef, chapter]) => {
|
|
110
|
+
const planned = plannedByRef.get(chapterRef);
|
|
111
|
+
const unitRef = applicationDocumentationChapterUnitRef(chapterRef);
|
|
112
|
+
const slug = applicationDocumentationChapterSlug(chapterRef);
|
|
113
|
+
const parsed = parseTechnicalDocumentationUnitMarkdown(`# ${planned.title}\n\n${chapter.body}`, unitRef);
|
|
114
|
+
// A related chapter the plan does not declare is withheld above, so there is no page to link to.
|
|
115
|
+
const relatedLinks = chapter.relatedChapterRefs
|
|
116
|
+
.filter((relatedChapterRef) => plannedByRef.has(relatedChapterRef))
|
|
117
|
+
.map((relatedChapterRef) => ({
|
|
118
|
+
linkRef: `technical-documentation:link/guides-${slug}-to-${applicationDocumentationChapterSlug(relatedChapterRef)}`,
|
|
119
|
+
kind: TechnicalDocumentationLinkKind.UNIT,
|
|
120
|
+
// Filtered to planned chapters just above, so the title is always present.
|
|
121
|
+
label: plannedByRef.get(relatedChapterRef).title,
|
|
122
|
+
targetUnitRef: applicationDocumentationChapterUnitRef(relatedChapterRef),
|
|
123
|
+
targetRef: null,
|
|
124
|
+
externalUrl: null,
|
|
125
|
+
apiReferenceTarget: null,
|
|
126
|
+
}));
|
|
127
|
+
const apiReferenceLinks = chapter.apiReferences.map((reference, index) => ({
|
|
128
|
+
linkRef: `technical-documentation:link/guides-${slug}-api-reference-${String(index + 1).padStart(2, '0')}`,
|
|
129
|
+
kind: TechnicalDocumentationLinkKind.API_REFERENCE,
|
|
130
|
+
label: reference.label,
|
|
131
|
+
targetUnitRef: null,
|
|
132
|
+
targetRef: null,
|
|
133
|
+
externalUrl: null,
|
|
134
|
+
apiReferenceTarget: applicationDocumentationChapterApiReferenceTarget(reference),
|
|
135
|
+
}));
|
|
136
|
+
return createTechnicalDocumentationUnitV1({
|
|
137
|
+
schemaVersion: 1,
|
|
138
|
+
unitRef,
|
|
139
|
+
unitVersion: 1,
|
|
140
|
+
kind: planned.kind,
|
|
141
|
+
readerAudiences: [...planned.readerAudiences],
|
|
142
|
+
title: parsed.title,
|
|
143
|
+
summary: parsed.summary,
|
|
144
|
+
sections: parsed.sections.map((section, index) => ({
|
|
145
|
+
sectionRef: `technical-documentation:section/guides-${slug}-${index + 1}`,
|
|
146
|
+
heading: section.heading,
|
|
147
|
+
bodyMarkdown: section.bodyMarkdown,
|
|
148
|
+
})),
|
|
149
|
+
links: [...relatedLinks, ...apiReferenceLinks],
|
|
150
|
+
/*
|
|
151
|
+
* ONE provenance row, carrying the author's own classification. That row is what the
|
|
152
|
+
* publication policy reads: a `RESTRICTED` chapter is suppressed with `RESTRICTED_SOURCE`
|
|
153
|
+
* by the same rule that suppresses any unit derived from a restricted source, so there is
|
|
154
|
+
* no second, chapter-specific gate to drift from the first.
|
|
155
|
+
*/
|
|
156
|
+
provenance: [{
|
|
157
|
+
sourceRef: `${APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_PREFIX}${chapterRef}`,
|
|
158
|
+
ownership: TechnicalDocumentationSourceOwnership.APPLICATION_AUTHORED,
|
|
159
|
+
classification: chapter.classification,
|
|
160
|
+
sourceVersion: APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_VERSION,
|
|
161
|
+
sourceAnchorRef: null,
|
|
162
|
+
}],
|
|
163
|
+
applicabilityRequirements: [...chapter.applicabilityRequirements],
|
|
164
|
+
accessClass: TechnicalDocumentationAccessClass.PUBLIC,
|
|
165
|
+
editorialStatus: chapter.editorialStatus ?? TechnicalDocumentationEditorialStatus.APPROVED,
|
|
166
|
+
assetRequests: [],
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
return {
|
|
170
|
+
units,
|
|
171
|
+
plannedChapterRefsWithoutBody: plannedChapters
|
|
172
|
+
.map((chapter) => chapter.chapterRef)
|
|
173
|
+
.filter((chapterRef) => !(chapterRef in chapters))
|
|
174
|
+
.sort(),
|
|
175
|
+
authoredChapterRefsWithoutPlan,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Refuses a publication in which an authored chapter cites an API contract the rendered reference
|
|
180
|
+
* does not carry.
|
|
181
|
+
*
|
|
182
|
+
* The engine's own pages DEGRADE such a link to plain text (#472), and for them that is right: the
|
|
183
|
+
* engine hardcodes targets naming resources an application may switch off, so an absent target there
|
|
184
|
+
* is a configuration fact. An application's chapter has no such excuse — its author wrote the
|
|
185
|
+
* reference against THIS application, so an absent target is a typo, a renamed operation or a
|
|
186
|
+
* removed one, and a guide telling a reader to call an operation that does not exist is exactly the
|
|
187
|
+
* sentence this portal must never publish. So it fails, naming every dangling reference at once.
|
|
188
|
+
*
|
|
189
|
+
* Checked against the PUBLISHED bundle only: a withheld chapter never reaches a reader, and a
|
|
190
|
+
* restricted one may legitimately cite a contract the public reference omits.
|
|
191
|
+
*/
|
|
192
|
+
export function assertApplicationDocumentationChapterApiReferencesResolve(publicBundle, apiReferenceLinkIndex) {
|
|
193
|
+
const dangling = publicBundle.units
|
|
194
|
+
.filter((unit) => unit.unitRef.startsWith(APPLICATION_DOCUMENTATION_CHAPTER_UNIT_REF_PREFIX))
|
|
195
|
+
.flatMap((unit) => unit.links
|
|
196
|
+
.filter((link) => link.kind === TechnicalDocumentationLinkKind.API_REFERENCE && link.apiReferenceTarget !== null)
|
|
197
|
+
.filter((link) => getTechnicalDocumentationApiReferenceTargetAvailability(apiReferenceLinkIndex, link.apiReferenceTarget)
|
|
198
|
+
=== TechnicalDocumentationApiReferenceTargetAvailability.UNAVAILABLE)
|
|
199
|
+
.map((link) => `${unit.unitRef} → "${link.label}" (${JSON.stringify(link.apiReferenceTarget)})`));
|
|
200
|
+
if (dangling.length > 0) {
|
|
201
|
+
throw new Error(`authored documentation chapter(s) cite API contracts this application's API reference does not publish: ${dangling.join('; ')}. `
|
|
202
|
+
+ 'Correct the resource or operation name in specifications/src/technical-documentation/index.ts.');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=application-documentation-chapters.js.map
|
package/dist/esm/companion/application-documentation/application-documentation-chapters.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"application-documentation-chapters.js","sourceRoot":"","sources":["../../../../../src/companion/application-documentation/application-documentation-chapters.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The chapters an APPLICATION authors, joined to the plan that declares them and projected into\n * publishable units (#874).\n *\n * Until this module the portal could say only what the engine wrote or what the companion projected:\n * a customer could read a page per resource, and nothing about the journeys that cross them, and\n * the private derivation's own contract said application content stayed absent until the\n * app-creator owned an accepted authoring artifact. Two accepted artifacts now exist, both\n * specification FILES accepted by being committed:\n *\n * | artifact | owns |\n * | --- | --- |\n * | the documentation plan (`documentationPlan`) | each chapter's identity — ref, title, kind, audiences |\n * | the authored chapters (`documentationChapters`) | each chapter's body, classification, API references |\n *\n * This is the join, and it mirrors the framework's own catalogue: authored Markdown joined to a unit\n * definition by key, with the projector refusing a content set that does not match its definitions.\n *\n * ## What is refused, and what is merely withheld\n *\n * | condition | outcome | why |\n * | --- | --- | --- |\n * | a chapter no plan declares (or no plan at all) | withheld, and reported (`authoredChapterRefsWithoutPlan`) | the plan owns the chapter's title, kind and audiences, so there is nothing true to publish it under — but the body is unfinished work, not a false statement |\n * | a `RESTRICTED` chapter | withheld by the publication policy (`RESTRICTED_SOURCE`) | the chapter is true, and not for this audience |\n * | a `DRAFT` / `REVIEW_REQUIRED` chapter | withheld by the policy (`EDITORIAL_NOT_APPROVED`) | the author said it is not ready |\n * | a planned chapter with no body yet | not published, and reported | unfinished rather than false |\n * | an API reference the rendered reference does not carry | THROWS at publication | see {@link assertApplicationDocumentationChapterApiReferencesResolve} |\n *\n * Refusal is kept to the one case where publishing would state something untrue — a guide telling a\n * reader to call an operation that does not exist. Everything else is withheld and reported, so it\n * travels with the artifact and stops nothing (`application-creation-directive.md`).\n *\n * Rejected alternative, kept here because it was the first version: REFUSING an unplanned body. It\n * made one orphaned chapter — a plan ref renamed, a draft written ahead of its plan entry — stop the\n * whole portal, including the build lane's derived publication step, although withholding that body\n * publishes nothing untrue.\n */\nimport {\n ApplicationDocumentationPlanDisposition,\n TechnicalDocumentationAccessClass,\n TechnicalDocumentationApiReferenceSection,\n TechnicalDocumentationApiReferenceTargetKind,\n TechnicalDocumentationEditorialStatus,\n TechnicalDocumentationLinkKind,\n TechnicalDocumentationSourceOwnership,\n DOCUMENTATION_CHAPTER_REF_PREFIX,\n ApplicationDocumentationChapterSetSchema,\n ApplicationDocumentationPlanSchema,\n createTechnicalDocumentationUnitV1,\n type ApplicationDocumentationChapterApiReference,\n type ApplicationDocumentationChapterSet,\n type ApplicationDocumentationPlan,\n type TechnicalDocumentationApiReferenceTargetV1,\n type TechnicalDocumentationAuthorizedBundleManifestV1,\n type TechnicalDocumentationLinkV1,\n type TechnicalDocumentationUnitV1,\n} from '@wildo-ai/saas-specifications/technical-documentation';\n\nimport {\n getTechnicalDocumentationApiReferenceTargetAvailability,\n TechnicalDocumentationApiReferenceTargetAvailability,\n type ApiReferenceLinkIndex,\n} from '../../openapi/api-reference-link-index';\nimport { parseTechnicalDocumentationUnitMarkdown } from './technical-documentation-engine-content-bundle';\n\n/**\n * The unit-ref namespace authored chapters live in, and the only place its shape is written.\n *\n * A RESERVED engine namespace, like `unit/manages/`: the renderer routes it to `guides/<slug>`, so\n * an application chooses a chapter's slug but never its URL scheme, and nothing an application names\n * can land in the engine's own route table.\n */\nexport const APPLICATION_DOCUMENTATION_CHAPTER_UNIT_REF_PREFIX = 'technical-documentation:unit/guides/';\n\n/** The portal directory every authored chapter is published under. */\nexport const APPLICATION_DOCUMENTATION_CHAPTER_ROUTE_DIRECTORY = 'guides';\n\n/** Provenance ref of one authored chapter — the file a reader traces the page back to. */\nexport const APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_PREFIX = 'source:application-chapter:';\n\n/**\n * The only version an authored chapter carries. A chapter is versioned by git, not by a counter in\n * the file (`one-source-of-truth.md`); the unit schema requires a positive version, and 1 states\n * that nothing else is being claimed.\n */\nconst APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_VERSION = 1;\n\n/** `chapter-close-a-deal` → `close-a-deal`: the slug a route and a unit ref are built from. */\nexport function applicationDocumentationChapterSlug(chapterRef: string): string {\n if (!chapterRef.startsWith(DOCUMENTATION_CHAPTER_REF_PREFIX)) {\n throw new Error(`documentation chapter ref ${chapterRef} does not start with ${DOCUMENTATION_CHAPTER_REF_PREFIX}`);\n }\n return chapterRef.slice(DOCUMENTATION_CHAPTER_REF_PREFIX.length);\n}\n\nexport function applicationDocumentationChapterUnitRef(chapterRef: string): string {\n return `${APPLICATION_DOCUMENTATION_CHAPTER_UNIT_REF_PREFIX}${applicationDocumentationChapterSlug(chapterRef)}`;\n}\n\n/**\n * The portable target one authored API reference names.\n *\n * Built with `encodeURIComponent`, exactly as the OpenAPI identity constructor builds the refs it\n * indexes (`createOpenApiOperationIdentity`) — so an operation named `complete-all` or one carrying a\n * `:` resolves to the same key on both sides rather than failing on an encoding difference.\n */\nexport function applicationDocumentationChapterApiReferenceTarget(reference: ApplicationDocumentationChapterApiReference): TechnicalDocumentationApiReferenceTargetV1 {\n const resourceRef = `technical-documentation:resource/${encodeURIComponent(reference.resource)}`;\n // Omitted means the public API reference, which is where almost every contract a guide cites lives.\n const section = reference.section ?? TechnicalDocumentationApiReferenceSection.API_REFERENCE;\n return reference.operation === undefined\n ? {\n section,\n kind: TechnicalDocumentationApiReferenceTargetKind.RESOURCE,\n resourceRef,\n }\n : {\n section,\n kind: TechnicalDocumentationApiReferenceTargetKind.OPERATION_FAMILY,\n operationFamilyRef: `${resourceRef}/operation-family/${encodeURIComponent(reference.operation)}`,\n };\n}\n\nexport interface ProjectApplicationDocumentationChaptersInput {\n /** The accepted plan, as the specifications package exports it. Parsed here: the export is untyped at runtime. */\n readonly plan: unknown;\n /** The accepted chapter bodies, as the specifications package exports them. Parsed here too. */\n readonly chapters: unknown;\n}\n\nexport interface ApplicationDocumentationChaptersProjection {\n /** One unit per authored chapter, unfiltered: the publication policy decides what a reader sees. */\n readonly units: readonly TechnicalDocumentationUnitV1[];\n /** Chapters the plan declares whose body nobody has authored yet — reported, never refused. */\n readonly plannedChapterRefsWithoutBody: readonly string[];\n /**\n * Bodies authored under a `chapterRef` the accepted plan does not declare (or with no plan, or a\n * NOT_APPLICABLE one). Withheld — they have no title, kind or audience to publish under — and\n * reported, never refused.\n */\n readonly authoredChapterRefsWithoutPlan: readonly string[];\n}\n\n/**\n * Joins the authored chapters to the plan that declares them. A pure function of its two inputs.\n * A body the plan does not declare is withheld and reported — see the module table.\n */\nexport function projectApplicationDocumentationChapters(input: ProjectApplicationDocumentationChaptersInput): ApplicationDocumentationChaptersProjection {\n const chapters: ApplicationDocumentationChapterSet = input.chapters === undefined\n ? {}\n : ApplicationDocumentationChapterSetSchema.parse(input.chapters);\n const plan: ApplicationDocumentationPlan | undefined = input.plan === undefined\n ? undefined\n : ApplicationDocumentationPlanSchema.parse(input.plan);\n const plannedChapters = plan === undefined || plan.disposition === ApplicationDocumentationPlanDisposition.NOT_APPLICABLE ? [] : plan.chapters;\n const plannedByRef = new Map(plannedChapters.map((chapter) => [chapter.chapterRef, chapter]));\n\n const authoredChapterRefsWithoutPlan = Object.keys(chapters).filter((chapterRef) => !plannedByRef.has(chapterRef)).sort();\n\n const units = Object.entries(chapters)\n .filter(([chapterRef]) => plannedByRef.has(chapterRef))\n .sort(([left], [right]) => left.localeCompare(right, 'en-US'))\n .map(([chapterRef, chapter]) => {\n const planned = plannedByRef.get(chapterRef)!;\n const unitRef = applicationDocumentationChapterUnitRef(chapterRef);\n const slug = applicationDocumentationChapterSlug(chapterRef);\n const parsed = parseTechnicalDocumentationUnitMarkdown(`# ${planned.title}\\n\\n${chapter.body}`, unitRef);\n // A related chapter the plan does not declare is withheld above, so there is no page to link to.\n const relatedLinks: TechnicalDocumentationLinkV1[] = chapter.relatedChapterRefs\n .filter((relatedChapterRef) => plannedByRef.has(relatedChapterRef))\n .map((relatedChapterRef) => ({\n linkRef: `technical-documentation:link/guides-${slug}-to-${applicationDocumentationChapterSlug(relatedChapterRef)}`,\n kind: TechnicalDocumentationLinkKind.UNIT,\n // Filtered to planned chapters just above, so the title is always present.\n label: plannedByRef.get(relatedChapterRef)!.title,\n targetUnitRef: applicationDocumentationChapterUnitRef(relatedChapterRef),\n targetRef: null,\n externalUrl: null,\n apiReferenceTarget: null,\n }));\n const apiReferenceLinks: TechnicalDocumentationLinkV1[] = chapter.apiReferences.map((reference, index) => ({\n linkRef: `technical-documentation:link/guides-${slug}-api-reference-${String(index + 1).padStart(2, '0')}`,\n kind: TechnicalDocumentationLinkKind.API_REFERENCE,\n label: reference.label,\n targetUnitRef: null,\n targetRef: null,\n externalUrl: null,\n apiReferenceTarget: applicationDocumentationChapterApiReferenceTarget(reference),\n }));\n return createTechnicalDocumentationUnitV1({\n schemaVersion: 1,\n unitRef,\n unitVersion: 1,\n kind: planned.kind,\n readerAudiences: [...planned.readerAudiences],\n title: parsed.title,\n summary: parsed.summary,\n sections: parsed.sections.map((section, index) => ({\n sectionRef: `technical-documentation:section/guides-${slug}-${index + 1}`,\n heading: section.heading,\n bodyMarkdown: section.bodyMarkdown,\n })),\n links: [...relatedLinks, ...apiReferenceLinks],\n /*\n * ONE provenance row, carrying the author's own classification. That row is what the\n * publication policy reads: a `RESTRICTED` chapter is suppressed with `RESTRICTED_SOURCE`\n * by the same rule that suppresses any unit derived from a restricted source, so there is\n * no second, chapter-specific gate to drift from the first.\n */\n provenance: [{\n sourceRef: `${APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_PREFIX}${chapterRef}`,\n ownership: TechnicalDocumentationSourceOwnership.APPLICATION_AUTHORED,\n classification: chapter.classification,\n sourceVersion: APPLICATION_DOCUMENTATION_CHAPTER_SOURCE_VERSION,\n sourceAnchorRef: null,\n }],\n applicabilityRequirements: [...chapter.applicabilityRequirements],\n accessClass: TechnicalDocumentationAccessClass.PUBLIC,\n editorialStatus: chapter.editorialStatus ?? TechnicalDocumentationEditorialStatus.APPROVED,\n assetRequests: [],\n });\n });\n\n return {\n units,\n plannedChapterRefsWithoutBody: plannedChapters\n .map((chapter) => chapter.chapterRef)\n .filter((chapterRef) => !(chapterRef in chapters))\n .sort(),\n authoredChapterRefsWithoutPlan,\n };\n}\n\n/**\n * Refuses a publication in which an authored chapter cites an API contract the rendered reference\n * does not carry.\n *\n * The engine's own pages DEGRADE such a link to plain text (#472), and for them that is right: the\n * engine hardcodes targets naming resources an application may switch off, so an absent target there\n * is a configuration fact. An application's chapter has no such excuse — its author wrote the\n * reference against THIS application, so an absent target is a typo, a renamed operation or a\n * removed one, and a guide telling a reader to call an operation that does not exist is exactly the\n * sentence this portal must never publish. So it fails, naming every dangling reference at once.\n *\n * Checked against the PUBLISHED bundle only: a withheld chapter never reaches a reader, and a\n * restricted one may legitimately cite a contract the public reference omits.\n */\nexport function assertApplicationDocumentationChapterApiReferencesResolve(\n publicBundle: TechnicalDocumentationAuthorizedBundleManifestV1,\n apiReferenceLinkIndex: ApiReferenceLinkIndex,\n): void {\n const dangling = publicBundle.units\n .filter((unit) => unit.unitRef.startsWith(APPLICATION_DOCUMENTATION_CHAPTER_UNIT_REF_PREFIX))\n .flatMap((unit) => unit.links\n .filter((link) => link.kind === TechnicalDocumentationLinkKind.API_REFERENCE && link.apiReferenceTarget !== null)\n .filter((link) => getTechnicalDocumentationApiReferenceTargetAvailability(apiReferenceLinkIndex, link.apiReferenceTarget!)\n === TechnicalDocumentationApiReferenceTargetAvailability.UNAVAILABLE)\n .map((link) => `${unit.unitRef} → \"${link.label}\" (${JSON.stringify(link.apiReferenceTarget)})`));\n if (dangling.length > 0) {\n throw new Error(\n `authored documentation chapter(s) cite API contracts this application's API reference does not publish: ${dangling.join('; ')}. `\n + 'Correct the resource or operation name in specifications/src/technical-documentation/index.ts.',\n );\n }\n}\n"]}
|
|
@@ -55,7 +55,10 @@ export function createApplicationOrganizationUnitResourcesDocumentationFact(reso
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
/** Match the native API-reference fallback without changing resource identity. */
|
|
58
|
+
// Generated API-reference prose, as in `openapi-reference-model.ts`. The technical-doc lane has no
|
|
59
|
+
// label tree and is not translated.
|
|
58
60
|
function resourceDisplayLabel(identifier) {
|
|
61
|
+
// invented-display-text: generated API-reference prose.
|
|
59
62
|
return identifier
|
|
60
63
|
.replaceAll(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
61
64
|
.replaceAll(/[-_]+/g, ' ')
|