@wildo-ai/saas-technical-doc 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +34 -0
- package/dist/esm/.builder.pid +9 -0
- package/dist/esm/build/csp-emit.d.ts +9 -0
- package/dist/esm/build/csp-emit.d.ts.map +1 -0
- package/dist/esm/build/csp-emit.js +8 -0
- package/dist/esm/build/csp-emit.js.map +1 -0
- package/dist/esm/build/load-materialized-frontend-providers.d.ts +9 -0
- package/dist/esm/build/load-materialized-frontend-providers.d.ts.map +1 -0
- package/dist/esm/build/load-materialized-frontend-providers.js +9 -0
- package/dist/esm/build/load-materialized-frontend-providers.js.map +1 -0
- package/dist/esm/companion/application-documentation/application-organization-role-documentation.d.ts +66 -0
- package/dist/esm/companion/application-documentation/application-organization-role-documentation.d.ts.map +1 -0
- package/dist/esm/companion/application-documentation/application-organization-role-documentation.js +195 -0
- package/dist/esm/companion/application-documentation/application-organization-role-documentation.js.map +1 -0
- package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.d.ts +36 -0
- package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.d.ts.map +1 -0
- package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.js +71 -0
- package/dist/esm/companion/application-documentation/application-organization-unit-resource-documentation.js.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.d.ts +37 -0
- package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.d.ts.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.js +1865 -0
- package/dist/esm/companion/application-documentation/technical-documentation-engine-content-bundle.js.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.d.ts +22 -0
- package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.d.ts.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.js +31 -0
- package/dist/esm/companion/application-documentation/technical-documentation-operational-evidence.js.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.d.ts +31 -0
- package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.d.ts.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.js +33 -0
- package/dist/esm/companion/application-documentation/technical-documentation-private-derivation.js.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.d.ts +13 -0
- package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.d.ts.map +1 -0
- package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.js +93 -0
- package/dist/esm/companion/application-documentation/technical-documentation-publication-policy.js.map +1 -0
- package/dist/esm/companion/content/application-consumer-documentation-content-loader.d.ts +52 -0
- package/dist/esm/companion/content/application-consumer-documentation-content-loader.d.ts.map +1 -0
- package/dist/esm/companion/content/application-consumer-documentation-content-loader.js +191 -0
- package/dist/esm/companion/content/application-consumer-documentation-content-loader.js.map +1 -0
- package/dist/esm/companion/index.d.ts +39 -0
- package/dist/esm/companion/index.d.ts.map +1 -0
- package/dist/esm/companion/index.js +39 -0
- package/dist/esm/companion/index.js.map +1 -0
- package/dist/esm/companion/openapi-generator.d.ts +94 -0
- package/dist/esm/companion/openapi-generator.d.ts.map +1 -0
- package/dist/esm/companion/openapi-generator.js +1562 -0
- package/dist/esm/companion/openapi-generator.js.map +1 -0
- package/dist/esm/companion/operation-projection.schemas.d.ts +797 -0
- package/dist/esm/companion/operation-projection.schemas.d.ts.map +1 -0
- package/dist/esm/companion/operation-projection.schemas.js +610 -0
- package/dist/esm/companion/operation-projection.schemas.js.map +1 -0
- package/dist/esm/companion/publish-result.types.d.ts +124 -0
- package/dist/esm/companion/publish-result.types.d.ts.map +1 -0
- package/dist/esm/companion/publish-result.types.js +28 -0
- package/dist/esm/companion/publish-result.types.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.d.ts +26 -0
- package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.js +63 -0
- package/dist/esm/companion/rendering/technical-documentation-authorized-bundle-reader.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-build-measurement.d.ts +23 -0
- package/dist/esm/companion/rendering/technical-documentation-build-measurement.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-build-measurement.js +104 -0
- package/dist/esm/companion/rendering/technical-documentation-build-measurement.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.d.ts +9 -0
- package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.js +421 -0
- package/dist/esm/companion/rendering/technical-documentation-docusaurus-renderer.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.d.ts +72 -0
- package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.js +204 -0
- package/dist/esm/companion/rendering/technical-documentation-managed-tree-validator.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.d.ts +9 -0
- package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.js +106 -0
- package/dist/esm/companion/rendering/technical-documentation-markdown-renderer.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-mdx-escaping.d.ts +43 -0
- package/dist/esm/companion/rendering/technical-documentation-mdx-escaping.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-mdx-escaping.js +88 -0
- package/dist/esm/companion/rendering/technical-documentation-mdx-escaping.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.d.ts +7 -0
- package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.js +51 -0
- package/dist/esm/companion/rendering/technical-documentation-openapi-renderer.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-render-model.d.ts +45 -0
- package/dist/esm/companion/rendering/technical-documentation-render-model.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-render-model.js +178 -0
- package/dist/esm/companion/rendering/technical-documentation-render-model.js.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.d.ts +3 -0
- package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.d.ts.map +1 -0
- package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.js +49 -0
- package/dist/esm/companion/rendering/technical-documentation-search-index-renderer.js.map +1 -0
- package/dist/esm/companion/spec-to-operation-doc.d.ts +176 -0
- package/dist/esm/companion/spec-to-operation-doc.d.ts.map +1 -0
- package/dist/esm/companion/spec-to-operation-doc.js +326 -0
- package/dist/esm/companion/spec-to-operation-doc.js.map +1 -0
- package/dist/esm/companion/technical-documentation-asset-path.d.ts +16 -0
- package/dist/esm/companion/technical-documentation-asset-path.d.ts.map +1 -0
- package/dist/esm/companion/technical-documentation-asset-path.js +19 -0
- package/dist/esm/companion/technical-documentation-asset-path.js.map +1 -0
- package/dist/esm/companion/technical-documentation-capture-execution-port.d.ts +14 -0
- package/dist/esm/companion/technical-documentation-capture-execution-port.d.ts.map +1 -0
- package/dist/esm/companion/technical-documentation-capture-execution-port.js +1 -0
- package/dist/esm/companion/technical-documentation-capture-execution-port.js.map +1 -0
- package/dist/esm/companion/technical-documentation-diagram-materializer.d.ts +25 -0
- package/dist/esm/companion/technical-documentation-diagram-materializer.d.ts.map +1 -0
- package/dist/esm/companion/technical-documentation-diagram-materializer.js +86 -0
- package/dist/esm/companion/technical-documentation-diagram-materializer.js.map +1 -0
- package/dist/esm/companion/technical-documentation-placeholder-materializer.d.ts +17 -0
- package/dist/esm/companion/technical-documentation-placeholder-materializer.d.ts.map +1 -0
- package/dist/esm/companion/technical-documentation-placeholder-materializer.js +63 -0
- package/dist/esm/companion/technical-documentation-placeholder-materializer.js.map +1 -0
- package/dist/esm/companion/zod-to-openapi.d.ts +67 -0
- package/dist/esm/companion/zod-to-openapi.d.ts.map +1 -0
- package/dist/esm/companion/zod-to-openapi.js +211 -0
- package/dist/esm/companion/zod-to-openapi.js.map +1 -0
- package/dist/esm/companion-exports.d.ts +32 -0
- package/dist/esm/companion-exports.d.ts.map +1 -0
- package/dist/esm/companion-exports.js +32 -0
- package/dist/esm/companion-exports.js.map +1 -0
- package/dist/esm/config/define-tech-doc-config.d.ts +38 -0
- package/dist/esm/config/define-tech-doc-config.d.ts.map +1 -0
- package/dist/esm/config/define-tech-doc-config.js +40 -0
- package/dist/esm/config/define-tech-doc-config.js.map +1 -0
- package/dist/esm/config/index.d.ts +22 -0
- package/dist/esm/config/index.d.ts.map +1 -0
- package/dist/esm/config/index.js +22 -0
- package/dist/esm/config/index.js.map +1 -0
- package/dist/esm/config/wildo-tech-doc-config.schemas.d.ts +204 -0
- package/dist/esm/config/wildo-tech-doc-config.schemas.d.ts.map +1 -0
- package/dist/esm/config/wildo-tech-doc-config.schemas.js +192 -0
- package/dist/esm/config/wildo-tech-doc-config.schemas.js.map +1 -0
- package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.d.ts +82 -0
- package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.d.ts.map +1 -0
- package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.js +114 -0
- package/dist/esm/content/application-consumer-documentation-content-manifest.schemas.js.map +1 -0
- package/dist/esm/content/application-consumer-documentation-content.techdoc.d.ts +420 -0
- package/dist/esm/content/application-consumer-documentation-content.techdoc.d.ts.map +1 -0
- package/dist/esm/content/application-consumer-documentation-content.techdoc.js +9619 -0
- package/dist/esm/content/application-consumer-documentation-content.techdoc.js.map +1 -0
- package/dist/esm/content.exports.d.ts +9 -0
- package/dist/esm/content.exports.d.ts.map +1 -0
- package/dist/esm/content.exports.js +9 -0
- package/dist/esm/content.exports.js.map +1 -0
- package/dist/esm/index.d.ts +26 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +26 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/openapi/api-reference-link-index.d.ts +98 -0
- package/dist/esm/openapi/api-reference-link-index.d.ts.map +1 -0
- package/dist/esm/openapi/api-reference-link-index.js +301 -0
- package/dist/esm/openapi/api-reference-link-index.js.map +1 -0
- package/dist/esm/openapi/api-reference-targets.d.ts +71 -0
- package/dist/esm/openapi/api-reference-targets.d.ts.map +1 -0
- package/dist/esm/openapi/api-reference-targets.js +114 -0
- package/dist/esm/openapi/api-reference-targets.js.map +1 -0
- package/dist/esm/openapi/index.d.ts +20 -0
- package/dist/esm/openapi/index.d.ts.map +1 -0
- package/dist/esm/openapi/index.js +20 -0
- package/dist/esm/openapi/index.js.map +1 -0
- package/dist/esm/openapi/openapi-generation-output.schemas.d.ts +80 -0
- package/dist/esm/openapi/openapi-generation-output.schemas.d.ts.map +1 -0
- package/dist/esm/openapi/openapi-generation-output.schemas.js +76 -0
- package/dist/esm/openapi/openapi-generation-output.schemas.js.map +1 -0
- package/dist/esm/openapi-reference-model.exports.d.ts +10 -0
- package/dist/esm/openapi-reference-model.exports.d.ts.map +1 -0
- package/dist/esm/openapi-reference-model.exports.js +10 -0
- package/dist/esm/openapi-reference-model.exports.js.map +1 -0
- package/dist/esm/runtime/AuthExchangePage.d.ts +84 -0
- package/dist/esm/runtime/AuthExchangePage.d.ts.map +1 -0
- package/dist/esm/runtime/AuthExchangePage.js +188 -0
- package/dist/esm/runtime/AuthExchangePage.js.map +1 -0
- package/dist/esm/runtime/DocsAuthContext.d.ts +119 -0
- package/dist/esm/runtime/DocsAuthContext.d.ts.map +1 -0
- package/dist/esm/runtime/DocsAuthContext.js +171 -0
- package/dist/esm/runtime/DocsAuthContext.js.map +1 -0
- package/dist/esm/runtime/decode-jwt-claims.d.ts +39 -0
- package/dist/esm/runtime/decode-jwt-claims.d.ts.map +1 -0
- package/dist/esm/runtime/decode-jwt-claims.js +86 -0
- package/dist/esm/runtime/decode-jwt-claims.js.map +1 -0
- package/dist/esm/runtime/docs-auth-client.d.ts +193 -0
- package/dist/esm/runtime/docs-auth-client.d.ts.map +1 -0
- package/dist/esm/runtime/docs-auth-client.js +211 -0
- package/dist/esm/runtime/docs-auth-client.js.map +1 -0
- package/dist/esm/runtime/docs-auth-session.schemas.d.ts +77 -0
- package/dist/esm/runtime/docs-auth-session.schemas.d.ts.map +1 -0
- package/dist/esm/runtime/docs-auth-session.schemas.js +50 -0
- package/dist/esm/runtime/docs-auth-session.schemas.js.map +1 -0
- package/dist/esm/runtime/frontend-provider-registry.techdoc.d.ts +17 -0
- package/dist/esm/runtime/frontend-provider-registry.techdoc.d.ts.map +1 -0
- package/dist/esm/runtime/frontend-provider-registry.techdoc.js +23 -0
- package/dist/esm/runtime/frontend-provider-registry.techdoc.js.map +1 -0
- package/dist/esm/runtime/index.d.ts +57 -0
- package/dist/esm/runtime/index.d.ts.map +1 -0
- package/dist/esm/runtime/index.js +76 -0
- package/dist/esm/runtime/index.js.map +1 -0
- package/dist/esm/runtime/openapi-reference-conservation.d.ts +20 -0
- package/dist/esm/runtime/openapi-reference-conservation.d.ts.map +1 -0
- package/dist/esm/runtime/openapi-reference-conservation.js +102 -0
- package/dist/esm/runtime/openapi-reference-conservation.js.map +1 -0
- package/dist/esm/runtime/openapi-reference-model.d.ts +224 -0
- package/dist/esm/runtime/openapi-reference-model.d.ts.map +1 -0
- package/dist/esm/runtime/openapi-reference-model.js +579 -0
- package/dist/esm/runtime/openapi-reference-model.js.map +1 -0
- package/dist/esm/runtime/openapi-reference-view.d.ts +13 -0
- package/dist/esm/runtime/openapi-reference-view.d.ts.map +1 -0
- package/dist/esm/runtime/openapi-reference-view.js +284 -0
- package/dist/esm/runtime/openapi-reference-view.js.map +1 -0
- package/dist/esm/runtime/use-docs-auth-session.d.ts +26 -0
- package/dist/esm/runtime/use-docs-auth-session.d.ts.map +1 -0
- package/dist/esm/runtime/use-docs-auth-session.js +34 -0
- package/dist/esm/runtime/use-docs-auth-session.js.map +1 -0
- package/dist/esm/runtime/use-docs-frontend-provider-registry.d.ts +3 -0
- package/dist/esm/runtime/use-docs-frontend-provider-registry.d.ts.map +1 -0
- package/dist/esm/runtime/use-docs-frontend-provider-registry.js +5 -0
- package/dist/esm/runtime/use-docs-frontend-provider-registry.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +117 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { TechnicalDocumentationOutputKind, TechnicalDocumentationRendererKind, TechnicalDocumentationRenderOutputManifestV1Schema, } from '@wildo-ai/saas-specifications/technical-documentation';
|
|
2
|
+
import { technicalDocumentationSha256Bytes, } from './technical-documentation-render-model.js';
|
|
3
|
+
import { ApplicationConsumerDocumentationRoute, applicationConsumerDocumentationRoutePath, } from '../../openapi/api-reference-targets.js';
|
|
4
|
+
/**
|
|
5
|
+
* Ceilings applied when an application declares none.
|
|
6
|
+
*
|
|
7
|
+
* The point is that ABSENCE is bounded, not that these numbers are precisely right. An
|
|
8
|
+
* application which never thought about output size still gets a documentation tree that
|
|
9
|
+
* cannot silently grow until a build host runs out of memory; one that genuinely outgrows
|
|
10
|
+
* a ceiling raises it explicitly, and that edit is visible in review.
|
|
11
|
+
*
|
|
12
|
+
* Generous on purpose. A default tight enough to be "safe" for a small application would
|
|
13
|
+
* fire on ordinary growth, and a limit that routinely fires is a limit people learn to
|
|
14
|
+
* raise without reading — which is worse than no limit at all.
|
|
15
|
+
*/
|
|
16
|
+
export const TECHNICAL_DOCUMENTATION_DEFAULT_OUTPUT_BUDGETS = Object.freeze({
|
|
17
|
+
maximumPageCount: 2_000,
|
|
18
|
+
maximumTotalBytes: 512 * 1024 * 1024,
|
|
19
|
+
maximumSearchIndexBytes: 64 * 1024 * 1024,
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Sentinels every application inherits, which an application can EXTEND but never remove.
|
|
23
|
+
*
|
|
24
|
+
* ## Why this set is so small
|
|
25
|
+
*
|
|
26
|
+
* A sentinel is matched with `String.includes` against every public file, so a generic
|
|
27
|
+
* marker — `secret`, `token`, `password`, `Bearer` — would fire on the very guides that
|
|
28
|
+
* teach authentication, error handling and webhook signature verification. Those documents
|
|
29
|
+
* legitimately contain those words. A canary that cries on correct content gets removed,
|
|
30
|
+
* and then it protects nothing.
|
|
31
|
+
*
|
|
32
|
+
* So the mandatory set is restricted to byte sequences that **cannot legitimately appear in
|
|
33
|
+
* published product documentation** under any authoring style. A PEM private-key header
|
|
34
|
+
* qualifies: documentation teaches readers to *use* a public key, never to read a private
|
|
35
|
+
* one, so its presence in a public tree is a leak by construction rather than by judgement.
|
|
36
|
+
*
|
|
37
|
+
* Application-specific canaries — a restricted-content marker, a fixture value that must
|
|
38
|
+
* stay internal — belong in the application's own declared list, which is UNIONED with this
|
|
39
|
+
* one. The union direction is the safety property: an application must be able to add
|
|
40
|
+
* canaries and must not be able to switch these off.
|
|
41
|
+
*/
|
|
42
|
+
export const TECHNICAL_DOCUMENTATION_MANDATORY_PUBLIC_SENTINELS = Object.freeze([
|
|
43
|
+
'-----BEGIN OPENSSH PRIVATE KEY-----',
|
|
44
|
+
'-----BEGIN PGP PRIVATE KEY BLOCK-----',
|
|
45
|
+
'-----BEGIN PRIVATE KEY-----',
|
|
46
|
+
'-----BEGIN RSA PRIVATE KEY-----',
|
|
47
|
+
]);
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the effective budgets from an application's partial declaration.
|
|
50
|
+
*
|
|
51
|
+
* Per-field rather than all-or-nothing: an application that only needs a larger page
|
|
52
|
+
* count should not have to restate — and thereby freeze a copy of — every other ceiling.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveTechnicalDocumentationOutputBudgets(declared) {
|
|
55
|
+
return Object.freeze({ ...TECHNICAL_DOCUMENTATION_DEFAULT_OUTPUT_BUDGETS, ...(declared ?? {}) });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Unions the mandatory sentinels with an application's declared ones.
|
|
59
|
+
*
|
|
60
|
+
* A union, never a replacement — see
|
|
61
|
+
* {@link TECHNICAL_DOCUMENTATION_MANDATORY_PUBLIC_SENTINELS}. Deduplicated and sorted so
|
|
62
|
+
* the resolved set is a stable value: it is compared and reported, and an order that
|
|
63
|
+
* varied with authoring order would make two identical policies look different.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveTechnicalDocumentationForbiddenPublicSentinels(declared) {
|
|
66
|
+
const sentinels = new Set(TECHNICAL_DOCUMENTATION_MANDATORY_PUBLIC_SENTINELS);
|
|
67
|
+
for (const sentinel of declared ?? []) {
|
|
68
|
+
const trimmed = sentinel.trim();
|
|
69
|
+
// An empty sentinel is skipped by the validator anyway; dropping it here keeps the
|
|
70
|
+
// resolved set honest about how many canaries are actually armed.
|
|
71
|
+
if (trimmed.length > 0)
|
|
72
|
+
sentinels.add(trimmed);
|
|
73
|
+
}
|
|
74
|
+
return Object.freeze([...sentinels].sort());
|
|
75
|
+
}
|
|
76
|
+
const GENERATED_MARKDOWN_LINK_PATTERN = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^)]*)?\)/g;
|
|
77
|
+
/**
|
|
78
|
+
* Routes published by the API-reference pipeline rather than the guide renderer.
|
|
79
|
+
*
|
|
80
|
+
* API-reference links enter a guide only through the semantic OpenAPI link index,
|
|
81
|
+
* which proves the requested resource, operation, request, response or schema is
|
|
82
|
+
* present before the renderer receives its public URL. The managed-tree validator
|
|
83
|
+
* must therefore recognize the independently published route root while continuing
|
|
84
|
+
* to require every ordinary guide and static-asset target to exist in this exact
|
|
85
|
+
* render manifest.
|
|
86
|
+
*/
|
|
87
|
+
const INDEPENDENT_API_REFERENCE_ROUTES = new Set([
|
|
88
|
+
applicationConsumerDocumentationRoutePath(ApplicationConsumerDocumentationRoute.API_REFERENCE),
|
|
89
|
+
applicationConsumerDocumentationRoutePath(ApplicationConsumerDocumentationRoute.APPLICATION_ADMINISTRATION_API_REFERENCE),
|
|
90
|
+
]);
|
|
91
|
+
function validateGeneratedMarkdownLinkClosure(input) {
|
|
92
|
+
for (const match of input.text.matchAll(GENERATED_MARKDOWN_LINK_PATTERN)) {
|
|
93
|
+
const destination = match[1];
|
|
94
|
+
if (destination === undefined || destination.startsWith('#') || /^(?:https?:|mailto:)/i.test(destination))
|
|
95
|
+
continue;
|
|
96
|
+
const pathOnly = destination.split(/[?#]/, 1)[0];
|
|
97
|
+
if (pathOnly === undefined || pathOnly.length === 0)
|
|
98
|
+
continue;
|
|
99
|
+
if (pathOnly.startsWith('/')) {
|
|
100
|
+
const rootRelativePath = pathOnly.slice(1);
|
|
101
|
+
const docusaurusStaticPath = `docusaurus/static/${rootRelativePath}`;
|
|
102
|
+
if (input.docusaurusRoutes.has(pathOnly)
|
|
103
|
+
|| (input.sourcePath.startsWith('docusaurus/docs/') ? input.declaredByPath.has(docusaurusStaticPath) : input.declaredByPath.has(rootRelativePath)))
|
|
104
|
+
continue;
|
|
105
|
+
throw new Error(`technical-documentation generated link target is absent: ${input.sourcePath} -> ${destination}`);
|
|
106
|
+
}
|
|
107
|
+
const resolvedPath = posixPath.normalize(posixPath.join(posixPath.dirname(input.sourcePath), pathOnly));
|
|
108
|
+
if (resolvedPath.startsWith('../') || posixPath.isAbsolute(resolvedPath) || !input.declaredByPath.has(resolvedPath)) {
|
|
109
|
+
throw new Error(`technical-documentation generated link target is absent: ${input.sourcePath} -> ${destination}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function validateTechnicalDocumentationManagedTree(input) {
|
|
114
|
+
const manifest = TechnicalDocumentationRenderOutputManifestV1Schema.parse(input.manifest);
|
|
115
|
+
/*
|
|
116
|
+
* Binding is an identity comparison between two independently produced manifests, which is a
|
|
117
|
+
* check with two real sides. The `renderOutputDigest` recomputation that used to follow was not:
|
|
118
|
+
* it hashed this manifest and compared the result against the copy stored inside it.
|
|
119
|
+
*/
|
|
120
|
+
const boundBundle = manifest.authorizedBundleIdentity;
|
|
121
|
+
if (boundBundle.ref !== input.authorizedBundle.bundleRef || boundBundle.version !== input.authorizedBundle.bundleVersion) {
|
|
122
|
+
throw new Error('technical-documentation render manifest does not bind the authorized bundle');
|
|
123
|
+
}
|
|
124
|
+
const boundAssets = manifest.resolvedAssetBundleIdentity;
|
|
125
|
+
if (boundAssets.ref !== input.resolvedAssetBundle.assetBundleRef || boundAssets.version !== input.resolvedAssetBundle.assetBundleVersion) {
|
|
126
|
+
throw new Error('technical-documentation render manifest does not bind the resolved asset bundle');
|
|
127
|
+
}
|
|
128
|
+
const declaredByPath = new Map(manifest.outputs.map((output) => [output.normalizedRelativePath, output]));
|
|
129
|
+
const docusaurusRoutes = new Set(manifest.outputs.flatMap((output) => {
|
|
130
|
+
const path = output.normalizedRelativePath;
|
|
131
|
+
return path.startsWith('docusaurus/docs/') && path.endsWith('.mdx')
|
|
132
|
+
? [`/${path.slice('docusaurus/docs/'.length, -'.mdx'.length)}`]
|
|
133
|
+
: [];
|
|
134
|
+
}));
|
|
135
|
+
for (const apiReferenceRoute of INDEPENDENT_API_REFERENCE_ROUTES)
|
|
136
|
+
docusaurusRoutes.add(apiReferenceRoute);
|
|
137
|
+
const filesByPath = new Map();
|
|
138
|
+
for (const file of input.files) {
|
|
139
|
+
if (filesByPath.has(file.normalizedRelativePath))
|
|
140
|
+
throw new Error(`technical-documentation managed tree duplicates path: ${file.normalizedRelativePath}`);
|
|
141
|
+
filesByPath.set(file.normalizedRelativePath, file);
|
|
142
|
+
}
|
|
143
|
+
if (filesByPath.size !== declaredByPath.size)
|
|
144
|
+
throw new Error('technical-documentation managed tree inventory does not match its manifest');
|
|
145
|
+
const authorizedUnitRefs = new Set(input.authorizedBundle.units.map((unit) => unit.unitRef));
|
|
146
|
+
let totalBytes = 0;
|
|
147
|
+
let pageCount = 0;
|
|
148
|
+
let searchIndexBytes = 0;
|
|
149
|
+
for (const [path, output] of declaredByPath) {
|
|
150
|
+
const file = filesByPath.get(path);
|
|
151
|
+
if (file === undefined)
|
|
152
|
+
throw new Error(`technical-documentation managed output is missing: ${path}`);
|
|
153
|
+
if (file.outputRef !== output.outputRef || file.rendererRef !== output.rendererRef || file.outputKind !== output.outputKind
|
|
154
|
+
|| file.mediaType !== output.mediaType || [...file.sourceUnitRefs].sort().join('\0') !== output.sourceUnitRefs.join('\0')) {
|
|
155
|
+
throw new Error(`technical-documentation managed output metadata mismatch: ${path}`);
|
|
156
|
+
}
|
|
157
|
+
if (file.bytes.byteLength !== output.byteLength || technicalDocumentationSha256Bytes(file.bytes) !== output.byteDigest) {
|
|
158
|
+
throw new Error(`technical-documentation managed output byte identity mismatch: ${path}`);
|
|
159
|
+
}
|
|
160
|
+
for (const sourceUnitRef of output.sourceUnitRefs) {
|
|
161
|
+
if (!authorizedUnitRefs.has(sourceUnitRef))
|
|
162
|
+
throw new Error(`technical-documentation output cites an unauthorized unit: ${sourceUnitRef}`);
|
|
163
|
+
}
|
|
164
|
+
const text = Buffer.from(file.bytes).toString('utf8');
|
|
165
|
+
for (const sentinel of input.forbiddenPublicSentinels) {
|
|
166
|
+
if (sentinel.length > 0 && text.includes(sentinel))
|
|
167
|
+
throw new Error(`technical-documentation managed public tree contains forbidden sentinel in ${path}`);
|
|
168
|
+
}
|
|
169
|
+
if (output.outputKind === TechnicalDocumentationOutputKind.MARKDOWN_DOCUMENT) {
|
|
170
|
+
validateGeneratedMarkdownLinkClosure({ sourcePath: path, text, declaredByPath, docusaurusRoutes });
|
|
171
|
+
}
|
|
172
|
+
totalBytes += file.bytes.byteLength;
|
|
173
|
+
if (output.outputKind === TechnicalDocumentationOutputKind.HTML_PAGE || output.outputKind === TechnicalDocumentationOutputKind.MARKDOWN_DOCUMENT)
|
|
174
|
+
pageCount += 1;
|
|
175
|
+
if (path === 'indexes/search.json')
|
|
176
|
+
searchIndexBytes = file.bytes.byteLength;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Captures are source material, not unconditional public output. An API-only
|
|
180
|
+
* publication deliberately has no page renderer that can reference a
|
|
181
|
+
* screenshot, so it must not publish unused product imagery. Once the
|
|
182
|
+
* Docusaurus content renderer participates, every resolved asset remains a
|
|
183
|
+
* required managed output as before.
|
|
184
|
+
*/
|
|
185
|
+
if (manifest.rendererExecutions.some((execution) => execution.rendererKind === TechnicalDocumentationRendererKind.DOCUSAURUS)) {
|
|
186
|
+
const resolvedAssetPaths = input.resolvedAssetBundle.resolvedAssetSet.assets.flatMap((asset) => asset.normalizedRelativePath === null ? [] : [asset.normalizedRelativePath]);
|
|
187
|
+
for (const path of resolvedAssetPaths) {
|
|
188
|
+
const output = declaredByPath.get(path);
|
|
189
|
+
if (output?.outputKind !== TechnicalDocumentationOutputKind.ASSET) {
|
|
190
|
+
throw new Error(`technical-documentation resolved asset is absent from the managed output tree: ${path}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const report = { pageCount, totalBytes, searchIndexBytes };
|
|
195
|
+
if (pageCount > input.budgets.maximumPageCount)
|
|
196
|
+
throw new Error(`technical-documentation page budget exceeded: ${pageCount}`);
|
|
197
|
+
if (totalBytes > input.budgets.maximumTotalBytes)
|
|
198
|
+
throw new Error(`technical-documentation total-byte budget exceeded: ${totalBytes}`);
|
|
199
|
+
if (searchIndexBytes > input.budgets.maximumSearchIndexBytes)
|
|
200
|
+
throw new Error(`technical-documentation search-index budget exceeded: ${searchIndexBytes}`);
|
|
201
|
+
return Object.freeze(report);
|
|
202
|
+
}
|
|
203
|
+
import { posix as posixPath } from 'node:path';
|
|
204
|
+
//# sourceMappingURL=technical-documentation-managed-tree-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"technical-documentation-managed-tree-validator.js","sourceRoot":"","sources":["../../../../../src/companion/rendering/technical-documentation-managed-tree-validator.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n TechnicalDocumentationOutputKind,\n TechnicalDocumentationRendererKind,\n TechnicalDocumentationRenderOutputManifestV1Schema,\n type TechnicalDocumentationAuthorizedBundleManifestV1,\n type TechnicalDocumentationRenderOutputManifestV1,\n type TechnicalDocumentationResolvedAssetBundleManifestV1,\n} from '@wildo-ai/saas-specifications/technical-documentation';\n\nimport {\n technicalDocumentationSha256Bytes,\n type TechnicalDocumentationRenderedFile,\n} from './technical-documentation-render-model';\nimport {\n ApplicationConsumerDocumentationRoute,\n applicationConsumerDocumentationRoutePath,\n} from '../../openapi/api-reference-targets';\n\nexport interface TechnicalDocumentationOutputBudgets {\n readonly maximumPageCount: number;\n readonly maximumTotalBytes: number;\n readonly maximumSearchIndexBytes: number;\n}\n\n/**\n * Ceilings applied when an application declares none.\n *\n * The point is that ABSENCE is bounded, not that these numbers are precisely right. An\n * application which never thought about output size still gets a documentation tree that\n * cannot silently grow until a build host runs out of memory; one that genuinely outgrows\n * a ceiling raises it explicitly, and that edit is visible in review.\n *\n * Generous on purpose. A default tight enough to be \"safe\" for a small application would\n * fire on ordinary growth, and a limit that routinely fires is a limit people learn to\n * raise without reading — which is worse than no limit at all.\n */\nexport const TECHNICAL_DOCUMENTATION_DEFAULT_OUTPUT_BUDGETS: TechnicalDocumentationOutputBudgets = Object.freeze({\n maximumPageCount: 2_000,\n maximumTotalBytes: 512 * 1024 * 1024,\n maximumSearchIndexBytes: 64 * 1024 * 1024,\n});\n\n/**\n * Sentinels every application inherits, which an application can EXTEND but never remove.\n *\n * ## Why this set is so small\n *\n * A sentinel is matched with `String.includes` against every public file, so a generic\n * marker — `secret`, `token`, `password`, `Bearer` — would fire on the very guides that\n * teach authentication, error handling and webhook signature verification. Those documents\n * legitimately contain those words. A canary that cries on correct content gets removed,\n * and then it protects nothing.\n *\n * So the mandatory set is restricted to byte sequences that **cannot legitimately appear in\n * published product documentation** under any authoring style. A PEM private-key header\n * qualifies: documentation teaches readers to *use* a public key, never to read a private\n * one, so its presence in a public tree is a leak by construction rather than by judgement.\n *\n * Application-specific canaries — a restricted-content marker, a fixture value that must\n * stay internal — belong in the application's own declared list, which is UNIONED with this\n * one. The union direction is the safety property: an application must be able to add\n * canaries and must not be able to switch these off.\n */\nexport const TECHNICAL_DOCUMENTATION_MANDATORY_PUBLIC_SENTINELS: readonly string[] = Object.freeze([\n '-----BEGIN OPENSSH PRIVATE KEY-----',\n '-----BEGIN PGP PRIVATE KEY BLOCK-----',\n '-----BEGIN PRIVATE KEY-----',\n '-----BEGIN RSA PRIVATE KEY-----',\n]);\n\n/**\n * Resolves the effective budgets from an application's partial declaration.\n *\n * Per-field rather than all-or-nothing: an application that only needs a larger page\n * count should not have to restate — and thereby freeze a copy of — every other ceiling.\n */\nexport function resolveTechnicalDocumentationOutputBudgets(\n declared?: Partial<TechnicalDocumentationOutputBudgets>,\n): TechnicalDocumentationOutputBudgets {\n return Object.freeze({ ...TECHNICAL_DOCUMENTATION_DEFAULT_OUTPUT_BUDGETS, ...(declared ?? {}) });\n}\n\n/**\n * Unions the mandatory sentinels with an application's declared ones.\n *\n * A union, never a replacement — see\n * {@link TECHNICAL_DOCUMENTATION_MANDATORY_PUBLIC_SENTINELS}. Deduplicated and sorted so\n * the resolved set is a stable value: it is compared and reported, and an order that\n * varied with authoring order would make two identical policies look different.\n */\nexport function resolveTechnicalDocumentationForbiddenPublicSentinels(\n declared?: readonly string[],\n): readonly string[] {\n const sentinels = new Set<string>(TECHNICAL_DOCUMENTATION_MANDATORY_PUBLIC_SENTINELS);\n for (const sentinel of declared ?? []) {\n const trimmed = sentinel.trim();\n // An empty sentinel is skipped by the validator anyway; dropping it here keeps the\n // resolved set honest about how many canaries are actually armed.\n if (trimmed.length > 0) sentinels.add(trimmed);\n }\n return Object.freeze([...sentinels].sort());\n}\n\nexport interface TechnicalDocumentationManagedTreeValidationReport {\n readonly pageCount: number;\n readonly totalBytes: number;\n readonly searchIndexBytes: number;\n}\n\nconst GENERATED_MARKDOWN_LINK_PATTERN = /!?\\[[^\\]]*\\]\\(([^)\\s]+)(?:\\s+[\"'][^)]*)?\\)/g;\n\n/**\n * Routes published by the API-reference pipeline rather than the guide renderer.\n *\n * API-reference links enter a guide only through the semantic OpenAPI link index,\n * which proves the requested resource, operation, request, response or schema is\n * present before the renderer receives its public URL. The managed-tree validator\n * must therefore recognize the independently published route root while continuing\n * to require every ordinary guide and static-asset target to exist in this exact\n * render manifest.\n */\nconst INDEPENDENT_API_REFERENCE_ROUTES: ReadonlySet<string> = new Set([\n applicationConsumerDocumentationRoutePath(ApplicationConsumerDocumentationRoute.API_REFERENCE),\n applicationConsumerDocumentationRoutePath(ApplicationConsumerDocumentationRoute.APPLICATION_ADMINISTRATION_API_REFERENCE),\n]);\n\nfunction validateGeneratedMarkdownLinkClosure(input: {\n readonly sourcePath: string;\n readonly text: string;\n readonly declaredByPath: ReadonlyMap<string, TechnicalDocumentationRenderOutputManifestV1['outputs'][number]>;\n readonly docusaurusRoutes: ReadonlySet<string>;\n}): void {\n for (const match of input.text.matchAll(GENERATED_MARKDOWN_LINK_PATTERN)) {\n const destination = match[1];\n if (destination === undefined || destination.startsWith('#') || /^(?:https?:|mailto:)/i.test(destination)) continue;\n const pathOnly = destination.split(/[?#]/, 1)[0];\n if (pathOnly === undefined || pathOnly.length === 0) continue;\n if (pathOnly.startsWith('/')) {\n const rootRelativePath = pathOnly.slice(1);\n const docusaurusStaticPath = `docusaurus/static/${rootRelativePath}`;\n if (input.docusaurusRoutes.has(pathOnly)\n || (input.sourcePath.startsWith('docusaurus/docs/') ? input.declaredByPath.has(docusaurusStaticPath) : input.declaredByPath.has(rootRelativePath))) continue;\n throw new Error(`technical-documentation generated link target is absent: ${input.sourcePath} -> ${destination}`);\n }\n const resolvedPath = posixPath.normalize(posixPath.join(posixPath.dirname(input.sourcePath), pathOnly));\n if (resolvedPath.startsWith('../') || posixPath.isAbsolute(resolvedPath) || !input.declaredByPath.has(resolvedPath)) {\n throw new Error(`technical-documentation generated link target is absent: ${input.sourcePath} -> ${destination}`);\n }\n }\n}\n\nexport function validateTechnicalDocumentationManagedTree(input: {\n readonly manifest: unknown;\n readonly files: readonly TechnicalDocumentationRenderedFile[];\n readonly authorizedBundle: TechnicalDocumentationAuthorizedBundleManifestV1;\n readonly resolvedAssetBundle: TechnicalDocumentationResolvedAssetBundleManifestV1;\n readonly forbiddenPublicSentinels: readonly string[];\n readonly budgets: TechnicalDocumentationOutputBudgets;\n}): TechnicalDocumentationManagedTreeValidationReport {\n const manifest: TechnicalDocumentationRenderOutputManifestV1 = TechnicalDocumentationRenderOutputManifestV1Schema.parse(input.manifest);\n /*\n * Binding is an identity comparison between two independently produced manifests, which is a\n * check with two real sides. The `renderOutputDigest` recomputation that used to follow was not:\n * it hashed this manifest and compared the result against the copy stored inside it.\n */\n const boundBundle = manifest.authorizedBundleIdentity;\n if (boundBundle.ref !== input.authorizedBundle.bundleRef || boundBundle.version !== input.authorizedBundle.bundleVersion) {\n throw new Error('technical-documentation render manifest does not bind the authorized bundle');\n }\n const boundAssets = manifest.resolvedAssetBundleIdentity;\n if (boundAssets.ref !== input.resolvedAssetBundle.assetBundleRef || boundAssets.version !== input.resolvedAssetBundle.assetBundleVersion) {\n throw new Error('technical-documentation render manifest does not bind the resolved asset bundle');\n }\n\n const declaredByPath = new Map(manifest.outputs.map((output) => [output.normalizedRelativePath, output]));\n const docusaurusRoutes = new Set(manifest.outputs.flatMap((output) => {\n const path = output.normalizedRelativePath as string;\n return path.startsWith('docusaurus/docs/') && path.endsWith('.mdx')\n ? [`/${path.slice('docusaurus/docs/'.length, -'.mdx'.length)}`]\n : [];\n }));\n for (const apiReferenceRoute of INDEPENDENT_API_REFERENCE_ROUTES) docusaurusRoutes.add(apiReferenceRoute);\n const filesByPath = new Map<string, TechnicalDocumentationRenderedFile>();\n for (const file of input.files) {\n if (filesByPath.has(file.normalizedRelativePath)) throw new Error(`technical-documentation managed tree duplicates path: ${file.normalizedRelativePath}`);\n filesByPath.set(file.normalizedRelativePath, file);\n }\n if (filesByPath.size !== declaredByPath.size) throw new Error('technical-documentation managed tree inventory does not match its manifest');\n\n const authorizedUnitRefs = new Set(input.authorizedBundle.units.map((unit) => unit.unitRef));\n let totalBytes = 0;\n let pageCount = 0;\n let searchIndexBytes = 0;\n for (const [path, output] of declaredByPath) {\n const file = filesByPath.get(path);\n if (file === undefined) throw new Error(`technical-documentation managed output is missing: ${path}`);\n if (file.outputRef !== output.outputRef || file.rendererRef !== output.rendererRef || file.outputKind !== output.outputKind\n || file.mediaType !== output.mediaType || [...file.sourceUnitRefs].sort().join('\\0') !== output.sourceUnitRefs.join('\\0')) {\n throw new Error(`technical-documentation managed output metadata mismatch: ${path}`);\n }\n if (file.bytes.byteLength !== output.byteLength || technicalDocumentationSha256Bytes(file.bytes) !== output.byteDigest) {\n throw new Error(`technical-documentation managed output byte identity mismatch: ${path}`);\n }\n for (const sourceUnitRef of output.sourceUnitRefs) {\n if (!authorizedUnitRefs.has(sourceUnitRef)) throw new Error(`technical-documentation output cites an unauthorized unit: ${sourceUnitRef}`);\n }\n const text = Buffer.from(file.bytes).toString('utf8');\n for (const sentinel of input.forbiddenPublicSentinels) {\n if (sentinel.length > 0 && text.includes(sentinel)) throw new Error(`technical-documentation managed public tree contains forbidden sentinel in ${path}`);\n }\n if (output.outputKind === TechnicalDocumentationOutputKind.MARKDOWN_DOCUMENT) {\n validateGeneratedMarkdownLinkClosure({ sourcePath: path, text, declaredByPath, docusaurusRoutes });\n }\n totalBytes += file.bytes.byteLength;\n if (output.outputKind === TechnicalDocumentationOutputKind.HTML_PAGE || output.outputKind === TechnicalDocumentationOutputKind.MARKDOWN_DOCUMENT) pageCount += 1;\n if (path === 'indexes/search.json') searchIndexBytes = file.bytes.byteLength;\n }\n\n /**\n * Captures are source material, not unconditional public output. An API-only\n * publication deliberately has no page renderer that can reference a\n * screenshot, so it must not publish unused product imagery. Once the\n * Docusaurus content renderer participates, every resolved asset remains a\n * required managed output as before.\n */\n if (manifest.rendererExecutions.some((execution) => execution.rendererKind === TechnicalDocumentationRendererKind.DOCUSAURUS)) {\n const resolvedAssetPaths = input.resolvedAssetBundle.resolvedAssetSet.assets.flatMap((asset) => asset.normalizedRelativePath === null ? [] : [asset.normalizedRelativePath]);\n for (const path of resolvedAssetPaths) {\n const output = declaredByPath.get(path);\n if (output?.outputKind !== TechnicalDocumentationOutputKind.ASSET) {\n throw new Error(`technical-documentation resolved asset is absent from the managed output tree: ${path}`);\n }\n }\n }\n const report = { pageCount, totalBytes, searchIndexBytes };\n if (pageCount > input.budgets.maximumPageCount) throw new Error(`technical-documentation page budget exceeded: ${pageCount}`);\n if (totalBytes > input.budgets.maximumTotalBytes) throw new Error(`technical-documentation total-byte budget exceeded: ${totalBytes}`);\n if (searchIndexBytes > input.budgets.maximumSearchIndexBytes) throw new Error(`technical-documentation search-index budget exceeded: ${searchIndexBytes}`);\n return Object.freeze(report);\n}\nimport { posix as posixPath } from 'node:path';\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type TechnicalDocumentationApiReferenceTargetV1, type TechnicalDocumentationResolvedAssetBundleManifestV1 } from '@wildo-ai/saas-specifications/technical-documentation';
|
|
2
|
+
import { type TechnicalDocumentationRenderModel, type TechnicalDocumentationRendererResult } from './technical-documentation-render-model';
|
|
3
|
+
export declare function renderTechnicalDocumentationMarkdown(input: {
|
|
4
|
+
readonly model: TechnicalDocumentationRenderModel;
|
|
5
|
+
readonly resolvedAssetBundle: TechnicalDocumentationResolvedAssetBundleManifestV1;
|
|
6
|
+
readonly assetBytesByPath: ReadonlyMap<string, Uint8Array>;
|
|
7
|
+
readonly resolveApiReferenceTarget?: (target: TechnicalDocumentationApiReferenceTargetV1) => string;
|
|
8
|
+
}): TechnicalDocumentationRendererResult;
|
|
9
|
+
//# sourceMappingURL=technical-documentation-markdown-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"technical-documentation-markdown-renderer.d.ts","sourceRoot":"","sources":["../../../../../src/companion/rendering/technical-documentation-markdown-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,0CAA0C,EAC/C,KAAK,mDAAmD,EAEzD,MAAM,uDAAuD,CAAC;AAE/D,OAAO,EAEL,KAAK,iCAAiC,EACtC,KAAK,oCAAoC,EAC1C,MAAM,wCAAwC,CAAC;AA2DhD,wBAAgB,oCAAoC,CAAC,KAAK,EAAE;IAC1D,QAAQ,CAAC,KAAK,EAAE,iCAAiC,CAAC;IAClD,QAAQ,CAAC,mBAAmB,EAAE,mDAAmD,CAAC;IAClF,QAAQ,CAAC,gBAAgB,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC3D,QAAQ,CAAC,yBAAyB,CAAC,EAAE,CAAC,MAAM,EAAE,0CAA0C,KAAK,MAAM,CAAC;CACrG,GAAG,oCAAoC,CAkDvC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { TechnicalDocumentationAssetResolutionStatus, TechnicalDocumentationLinkKind, TechnicalDocumentationOutputKind, TechnicalDocumentationRendererKind, } from '@wildo-ai/saas-specifications/technical-documentation';
|
|
2
|
+
import { createTechnicalDocumentationRendererExecution, } from './technical-documentation-render-model.js';
|
|
3
|
+
import { technicalDocumentationAssetFileStem, } from '../technical-documentation-asset-path.js';
|
|
4
|
+
/**
|
|
5
|
+
* Statuses whose asset carries real bytes at a real path, and therefore renders.
|
|
6
|
+
*
|
|
7
|
+
* `PLACEHOLDER` belongs here with `RESOLVED`: the framework materializes a stand-in image,
|
|
8
|
+
* so the page shows a picture rather than a hole. That is the whole distinction from
|
|
9
|
+
* `MISSING` — both mean "the real capture has not run", but only one of them SHIPS
|
|
10
|
+
* something. Rendering is a question about bytes; whether provisional imagery may be
|
|
11
|
+
* published at all is an environment policy decided at publication, not here.
|
|
12
|
+
*/
|
|
13
|
+
const RENDERABLE_ASSET_STATUSES = new Set([
|
|
14
|
+
TechnicalDocumentationAssetResolutionStatus.PLACEHOLDER,
|
|
15
|
+
TechnicalDocumentationAssetResolutionStatus.RESOLVED,
|
|
16
|
+
]);
|
|
17
|
+
const MARKDOWN_RENDERER_REF = 'technical-documentation:renderer/markdown';
|
|
18
|
+
function relativeMarkdownPath(sourcePath, targetPath) {
|
|
19
|
+
const relativePath = path.relative(path.dirname(sourcePath), targetPath);
|
|
20
|
+
return relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
|
|
21
|
+
}
|
|
22
|
+
function renderLink(link, model, sourcePath, resolveApiReferenceTarget) {
|
|
23
|
+
if (link.kind === TechnicalDocumentationLinkKind.UNIT) {
|
|
24
|
+
const targetPath = model.unitPathByRef.get(link.targetUnitRef);
|
|
25
|
+
if (targetPath === undefined)
|
|
26
|
+
throw new Error(`technical-documentation link target is not in the authorized render model: ${link.targetUnitRef}`);
|
|
27
|
+
return `- [${link.label}](${relativeMarkdownPath(sourcePath, `markdown/${targetPath}.md`)})`;
|
|
28
|
+
}
|
|
29
|
+
if (link.kind === TechnicalDocumentationLinkKind.EXTERNAL)
|
|
30
|
+
return `- [${link.label}](${link.externalUrl})`;
|
|
31
|
+
if (link.kind === TechnicalDocumentationLinkKind.API_REFERENCE) {
|
|
32
|
+
if (resolveApiReferenceTarget === undefined)
|
|
33
|
+
throw new Error(`technical-documentation API-reference link was not resolved before Markdown rendering: ${link.linkRef}`);
|
|
34
|
+
return `- [${link.label}](${resolveApiReferenceTarget(link.apiReferenceTarget)})`;
|
|
35
|
+
}
|
|
36
|
+
return `- ${link.label}`;
|
|
37
|
+
}
|
|
38
|
+
function renderUnit(unit, model, assetPathByRef, resolveApiReferenceTarget) {
|
|
39
|
+
const unitPath = model.unitPathByRef.get(unit.unitRef);
|
|
40
|
+
if (unitPath === undefined)
|
|
41
|
+
throw new Error(`technical-documentation render model has no path for ${unit.unitRef}`);
|
|
42
|
+
const sourcePath = `markdown/${unitPath}.md`;
|
|
43
|
+
const sections = unit.sections.map((section) => `## ${section.heading}\n\n${section.bodyMarkdown}`).join('\n\n');
|
|
44
|
+
const links = unit.links.length === 0 ? '' : `\n\n## Related\n\n${unit.links.map((link) => renderLink(link, model, sourcePath, resolveApiReferenceTarget)).join('\n')}`;
|
|
45
|
+
const figures = unit.assetRequests.flatMap((request) => {
|
|
46
|
+
const assetPath = assetPathByRef.get(request.assetRef);
|
|
47
|
+
return assetPath === undefined ? [] : [`})`];
|
|
48
|
+
});
|
|
49
|
+
const assetSection = figures.length === 0 ? '' : `\n\n## Product view\n\n${figures.join('\n\n')}`;
|
|
50
|
+
return `# ${unit.title}\n\n${unit.summary}\n\n${sections}${assetSection}${links}\n`;
|
|
51
|
+
}
|
|
52
|
+
export function renderTechnicalDocumentationMarkdown(input) {
|
|
53
|
+
const assetPathByRef = new Map(input.resolvedAssetBundle.resolvedAssetSet.assets.flatMap((asset) => RENDERABLE_ASSET_STATUSES.has(asset.status) ? [[asset.assetRef, asset.normalizedRelativePath]] : []));
|
|
54
|
+
const sourceUnitRefsByAssetRef = new Map();
|
|
55
|
+
for (const unit of input.model.units) {
|
|
56
|
+
for (const request of unit.assetRequests) {
|
|
57
|
+
const owners = sourceUnitRefsByAssetRef.get(request.assetRef) ?? [];
|
|
58
|
+
owners.push(unit.unitRef);
|
|
59
|
+
sourceUnitRefsByAssetRef.set(request.assetRef, owners);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const assetFiles = input.resolvedAssetBundle.resolvedAssetSet.assets.flatMap((asset) => {
|
|
63
|
+
if (!RENDERABLE_ASSET_STATUSES.has(asset.status))
|
|
64
|
+
return [];
|
|
65
|
+
const path = asset.normalizedRelativePath;
|
|
66
|
+
const bytes = input.assetBytesByPath.get(path);
|
|
67
|
+
if (bytes === undefined)
|
|
68
|
+
throw new Error(`technical-documentation Markdown asset bytes are absent: ${path}`);
|
|
69
|
+
const sourceUnitRefs = [...(sourceUnitRefsByAssetRef.get(asset.assetRef) ?? [])].sort();
|
|
70
|
+
if (sourceUnitRefs.length === 0)
|
|
71
|
+
throw new Error(`technical-documentation resolved asset has no authorized unit owner: ${asset.assetRef}`);
|
|
72
|
+
return [{
|
|
73
|
+
outputRef: `technical-documentation:output/markdown-asset/${technicalDocumentationAssetFileStem(asset.assetRef)}`,
|
|
74
|
+
rendererRef: MARKDOWN_RENDERER_REF,
|
|
75
|
+
outputKind: TechnicalDocumentationOutputKind.ASSET,
|
|
76
|
+
sourceUnitRefs,
|
|
77
|
+
normalizedRelativePath: path,
|
|
78
|
+
mediaType: asset.mediaType,
|
|
79
|
+
bytes,
|
|
80
|
+
}];
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
execution: createTechnicalDocumentationRendererExecution({
|
|
84
|
+
rendererKind: TechnicalDocumentationRendererKind.MARKDOWN,
|
|
85
|
+
rendererRef: MARKDOWN_RENDERER_REF,
|
|
86
|
+
rendererVersion: 1,
|
|
87
|
+
configurationRef: 'technical-documentation:renderer-configuration/markdown-v1',
|
|
88
|
+
}),
|
|
89
|
+
files: [...input.model.units.map((unit) => {
|
|
90
|
+
const path = input.model.unitPathByRef.get(unit.unitRef);
|
|
91
|
+
return {
|
|
92
|
+
// Keep the complete route identity: final path segments are not a
|
|
93
|
+
// globally unique page identity once consumer documentation has domains.
|
|
94
|
+
outputRef: `technical-documentation:output/markdown/${path.split('/').join(':')}`,
|
|
95
|
+
rendererRef: MARKDOWN_RENDERER_REF,
|
|
96
|
+
outputKind: TechnicalDocumentationOutputKind.MARKDOWN_DOCUMENT,
|
|
97
|
+
sourceUnitRefs: [unit.unitRef],
|
|
98
|
+
normalizedRelativePath: `markdown/${path}.md`,
|
|
99
|
+
mediaType: 'text/markdown',
|
|
100
|
+
bytes: Buffer.from(renderUnit(unit, input.model, assetPathByRef, input.resolveApiReferenceTarget), 'utf8'),
|
|
101
|
+
};
|
|
102
|
+
}), ...assetFiles],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
import { posix as path } from 'node:path';
|
|
106
|
+
//# sourceMappingURL=technical-documentation-markdown-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"technical-documentation-markdown-renderer.js","sourceRoot":"","sources":["../../../../../src/companion/rendering/technical-documentation-markdown-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n TechnicalDocumentationAssetResolutionStatus,\n TechnicalDocumentationLinkKind,\n TechnicalDocumentationOutputKind,\n TechnicalDocumentationRendererKind,\n type TechnicalDocumentationLinkV1,\n type TechnicalDocumentationApiReferenceTargetV1,\n type TechnicalDocumentationResolvedAssetBundleManifestV1,\n type TechnicalDocumentationUnitV1,\n} from '@wildo-ai/saas-specifications/technical-documentation';\n\nimport {\n createTechnicalDocumentationRendererExecution,\n type TechnicalDocumentationRenderModel,\n type TechnicalDocumentationRendererResult,\n} from './technical-documentation-render-model';\nimport {\n technicalDocumentationAssetFileStem,\n} from '../technical-documentation-asset-path';\n\n/**\n * Statuses whose asset carries real bytes at a real path, and therefore renders.\n *\n * `PLACEHOLDER` belongs here with `RESOLVED`: the framework materializes a stand-in image,\n * so the page shows a picture rather than a hole. That is the whole distinction from\n * `MISSING` — both mean \"the real capture has not run\", but only one of them SHIPS\n * something. Rendering is a question about bytes; whether provisional imagery may be\n * published at all is an environment policy decided at publication, not here.\n */\nconst RENDERABLE_ASSET_STATUSES: ReadonlySet<TechnicalDocumentationAssetResolutionStatus> = new Set([\n TechnicalDocumentationAssetResolutionStatus.PLACEHOLDER,\n TechnicalDocumentationAssetResolutionStatus.RESOLVED,\n]);\n\nconst MARKDOWN_RENDERER_REF = 'technical-documentation:renderer/markdown';\n\nfunction relativeMarkdownPath(sourcePath: string, targetPath: string): string {\n const relativePath = path.relative(path.dirname(sourcePath), targetPath);\n return relativePath.startsWith('.') ? relativePath : `./${relativePath}`;\n}\n\nfunction renderLink(link: TechnicalDocumentationLinkV1, model: TechnicalDocumentationRenderModel, sourcePath: string, resolveApiReferenceTarget?: (target: TechnicalDocumentationApiReferenceTargetV1) => string): string {\n if (link.kind === TechnicalDocumentationLinkKind.UNIT) {\n const targetPath = model.unitPathByRef.get(link.targetUnitRef as string);\n if (targetPath === undefined) throw new Error(`technical-documentation link target is not in the authorized render model: ${link.targetUnitRef}`);\n return `- [${link.label}](${relativeMarkdownPath(sourcePath, `markdown/${targetPath}.md`)})`;\n }\n if (link.kind === TechnicalDocumentationLinkKind.EXTERNAL) return `- [${link.label}](${link.externalUrl})`;\n if (link.kind === TechnicalDocumentationLinkKind.API_REFERENCE) {\n if (resolveApiReferenceTarget === undefined) throw new Error(`technical-documentation API-reference link was not resolved before Markdown rendering: ${link.linkRef}`);\n return `- [${link.label}](${resolveApiReferenceTarget(link.apiReferenceTarget as TechnicalDocumentationApiReferenceTargetV1)})`;\n }\n return `- ${link.label}`;\n}\n\nfunction renderUnit(\n unit: TechnicalDocumentationUnitV1,\n model: TechnicalDocumentationRenderModel,\n assetPathByRef: ReadonlyMap<string, string>,\n resolveApiReferenceTarget?: (target: TechnicalDocumentationApiReferenceTargetV1) => string,\n): string {\n const unitPath = model.unitPathByRef.get(unit.unitRef);\n if (unitPath === undefined) throw new Error(`technical-documentation render model has no path for ${unit.unitRef}`);\n const sourcePath = `markdown/${unitPath}.md`;\n const sections = unit.sections.map((section) => `## ${section.heading}\\n\\n${section.bodyMarkdown}`).join('\\n\\n');\n const links = unit.links.length === 0 ? '' : `\\n\\n## Related\\n\\n${unit.links.map((link) => renderLink(link, model, sourcePath, resolveApiReferenceTarget)).join('\\n')}`;\n const figures = unit.assetRequests.flatMap((request) => {\n const assetPath = assetPathByRef.get(request.assetRef);\n return assetPath === undefined ? [] : [`})`];\n });\n const assetSection = figures.length === 0 ? '' : `\\n\\n## Product view\\n\\n${figures.join('\\n\\n')}`;\n return `# ${unit.title}\\n\\n${unit.summary}\\n\\n${sections}${assetSection}${links}\\n`;\n}\n\nexport function renderTechnicalDocumentationMarkdown(input: {\n readonly model: TechnicalDocumentationRenderModel;\n readonly resolvedAssetBundle: TechnicalDocumentationResolvedAssetBundleManifestV1;\n readonly assetBytesByPath: ReadonlyMap<string, Uint8Array>;\n readonly resolveApiReferenceTarget?: (target: TechnicalDocumentationApiReferenceTargetV1) => string;\n}): TechnicalDocumentationRendererResult {\n const assetPathByRef = new Map(input.resolvedAssetBundle.resolvedAssetSet.assets.flatMap((asset) =>\n RENDERABLE_ASSET_STATUSES.has(asset.status) ? [[asset.assetRef, asset.normalizedRelativePath as string]] : []));\n const sourceUnitRefsByAssetRef = new Map<string, string[]>();\n for (const unit of input.model.units) {\n for (const request of unit.assetRequests) {\n const owners = sourceUnitRefsByAssetRef.get(request.assetRef) ?? [];\n owners.push(unit.unitRef);\n sourceUnitRefsByAssetRef.set(request.assetRef, owners);\n }\n }\n const assetFiles = input.resolvedAssetBundle.resolvedAssetSet.assets.flatMap((asset) => {\n if (!RENDERABLE_ASSET_STATUSES.has(asset.status)) return [];\n const path = asset.normalizedRelativePath as string;\n const bytes = input.assetBytesByPath.get(path);\n if (bytes === undefined) throw new Error(`technical-documentation Markdown asset bytes are absent: ${path}`);\n const sourceUnitRefs = [...(sourceUnitRefsByAssetRef.get(asset.assetRef) ?? [])].sort();\n if (sourceUnitRefs.length === 0) throw new Error(`technical-documentation resolved asset has no authorized unit owner: ${asset.assetRef}`);\n return [{\n outputRef: `technical-documentation:output/markdown-asset/${technicalDocumentationAssetFileStem(asset.assetRef)}`,\n rendererRef: MARKDOWN_RENDERER_REF,\n outputKind: TechnicalDocumentationOutputKind.ASSET,\n sourceUnitRefs,\n normalizedRelativePath: path,\n mediaType: asset.mediaType,\n bytes,\n }];\n });\n return {\n execution: createTechnicalDocumentationRendererExecution({\n rendererKind: TechnicalDocumentationRendererKind.MARKDOWN,\n rendererRef: MARKDOWN_RENDERER_REF,\n rendererVersion: 1,\n configurationRef: 'technical-documentation:renderer-configuration/markdown-v1',\n }),\n files: [...input.model.units.map((unit) => {\n const path = input.model.unitPathByRef.get(unit.unitRef) as string;\n return {\n // Keep the complete route identity: final path segments are not a\n // globally unique page identity once consumer documentation has domains.\n outputRef: `technical-documentation:output/markdown/${path.split('/').join(':')}`,\n rendererRef: MARKDOWN_RENDERER_REF,\n outputKind: TechnicalDocumentationOutputKind.MARKDOWN_DOCUMENT,\n sourceUnitRefs: [unit.unitRef],\n normalizedRelativePath: `markdown/${path}.md`,\n mediaType: 'text/markdown',\n bytes: Buffer.from(renderUnit(unit, input.model, assetPathByRef, input.resolveApiReferenceTarget), 'utf8'),\n };\n }), ...assetFiles],\n };\n}\nimport { posix as path } from 'node:path';\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes MDX-significant syntax in prose that was authored as, or derived into, MARKDOWN.
|
|
3
|
+
*
|
|
4
|
+
* ## The defect this exists to prevent
|
|
5
|
+
*
|
|
6
|
+
* Docusaurus compiles `.mdx`, not `.md`. In MDX, `{` opens a JavaScript expression and `<`
|
|
7
|
+
* opens a JSX element. Both are ordinary literal characters in Markdown, so any producer
|
|
8
|
+
* that emits correct Markdown can emit invalid MDX without doing anything wrong.
|
|
9
|
+
*
|
|
10
|
+
* Documentation is full of exactly these characters, because route templates are:
|
|
11
|
+
*
|
|
12
|
+
* ```text
|
|
13
|
+
* **PUT /api/v1/organizations/{organizationId}/todos/{todoId}/assign**
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* That line renders as a client-side crash — `ReferenceError: organizationId is not defined`
|
|
17
|
+
* — thrown from inside `<MDXContent>`, with the page replaced by "This page crashed." It is
|
|
18
|
+
* not a build failure: the MDX compiles, because `{organizationId}` is a syntactically valid
|
|
19
|
+
* expression. It fails at RENDER, in the reader's browser, which is the worst place to find it.
|
|
20
|
+
*
|
|
21
|
+
* ## Why escaping happens here rather than at the producer
|
|
22
|
+
*
|
|
23
|
+
* The producers are content authors and the derivation service, and neither of them should
|
|
24
|
+
* need to know which renderer their prose eventually reaches. The Markdown renderer next door
|
|
25
|
+
* emits `.md` and must NOT escape — a `\{` there is a literal backslash on the page. So the
|
|
26
|
+
* obligation belongs to the renderer that chose MDX, and to no one else.
|
|
27
|
+
*
|
|
28
|
+
* ## Why it is fence-aware
|
|
29
|
+
*
|
|
30
|
+
* MDX does not parse expressions inside code spans or fenced blocks, so escaping there would
|
|
31
|
+
* be visible corruption — a curl example would render a stray backslash. Code is also where
|
|
32
|
+
* these characters are MOST common, which is why a naive global replace looks like it works
|
|
33
|
+
* (the crash stops) while quietly damaging every example on the page.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Returns `markdown` with MDX-significant characters escaped everywhere MDX would
|
|
37
|
+
* interpret them, and untouched inside fenced blocks and inline code spans.
|
|
38
|
+
*
|
|
39
|
+
* Markdown structure is preserved exactly: headings, emphasis, lists, links and tables
|
|
40
|
+
* use none of the characters this touches.
|
|
41
|
+
*/
|
|
42
|
+
export declare function escapeMarkdownForMdx(markdown: string): string;
|
|
43
|
+
//# sourceMappingURL=technical-documentation-mdx-escaping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"technical-documentation-mdx-escaping.d.ts","sourceRoot":"","sources":["../../../../../src/companion/rendering/technical-documentation-mdx-escaping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAyCH;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY7D"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes MDX-significant syntax in prose that was authored as, or derived into, MARKDOWN.
|
|
3
|
+
*
|
|
4
|
+
* ## The defect this exists to prevent
|
|
5
|
+
*
|
|
6
|
+
* Docusaurus compiles `.mdx`, not `.md`. In MDX, `{` opens a JavaScript expression and `<`
|
|
7
|
+
* opens a JSX element. Both are ordinary literal characters in Markdown, so any producer
|
|
8
|
+
* that emits correct Markdown can emit invalid MDX without doing anything wrong.
|
|
9
|
+
*
|
|
10
|
+
* Documentation is full of exactly these characters, because route templates are:
|
|
11
|
+
*
|
|
12
|
+
* ```text
|
|
13
|
+
* **PUT /api/v1/organizations/{organizationId}/todos/{todoId}/assign**
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* That line renders as a client-side crash — `ReferenceError: organizationId is not defined`
|
|
17
|
+
* — thrown from inside `<MDXContent>`, with the page replaced by "This page crashed." It is
|
|
18
|
+
* not a build failure: the MDX compiles, because `{organizationId}` is a syntactically valid
|
|
19
|
+
* expression. It fails at RENDER, in the reader's browser, which is the worst place to find it.
|
|
20
|
+
*
|
|
21
|
+
* ## Why escaping happens here rather than at the producer
|
|
22
|
+
*
|
|
23
|
+
* The producers are content authors and the derivation service, and neither of them should
|
|
24
|
+
* need to know which renderer their prose eventually reaches. The Markdown renderer next door
|
|
25
|
+
* emits `.md` and must NOT escape — a `\{` there is a literal backslash on the page. So the
|
|
26
|
+
* obligation belongs to the renderer that chose MDX, and to no one else.
|
|
27
|
+
*
|
|
28
|
+
* ## Why it is fence-aware
|
|
29
|
+
*
|
|
30
|
+
* MDX does not parse expressions inside code spans or fenced blocks, so escaping there would
|
|
31
|
+
* be visible corruption — a curl example would render a stray backslash. Code is also where
|
|
32
|
+
* these characters are MOST common, which is why a naive global replace looks like it works
|
|
33
|
+
* (the crash stops) while quietly damaging every example on the page.
|
|
34
|
+
*/
|
|
35
|
+
/** Opens or closes a fenced code block: ``` or ~~~, optionally indented, with any info string. */
|
|
36
|
+
const FENCE_LINE = /^\s{0,3}(`{3,}|~{3,})/;
|
|
37
|
+
/** One inline code span: a backtick run, the shortest content, then a matching run. */
|
|
38
|
+
const INLINE_CODE_SPAN = /(`+)([\s\S]*?)\1/g;
|
|
39
|
+
/**
|
|
40
|
+
* A `<` that MDX would read as the start of a JSX element — followed by a name or a
|
|
41
|
+
* closing slash. A `<` used as "less than" (`a < b`, `<= 3`) is left alone, because
|
|
42
|
+
* escaping it would put a visible entity into ordinary prose.
|
|
43
|
+
*/
|
|
44
|
+
const JSX_LIKE_OPEN = /<(?=[A-Za-z/])/g;
|
|
45
|
+
/**
|
|
46
|
+
* A `{` that is not already escaped.
|
|
47
|
+
*
|
|
48
|
+
* The lookbehind is what makes this function idempotent, and idempotence is not a
|
|
49
|
+
* theoretical nicety here: an author may reasonably write `\{` themselves, and a second
|
|
50
|
+
* escaping pass would turn it into `\\{` — which renders a VISIBLE backslash and leaves
|
|
51
|
+
* the expression evaluating anyway, i.e. strictly worse than doing nothing.
|
|
52
|
+
*/
|
|
53
|
+
const UNESCAPED_BRACE = /(?<!\\)\{/g;
|
|
54
|
+
function escapeOutsideCode(text) {
|
|
55
|
+
return text.replace(UNESCAPED_BRACE, '\\{').replace(JSX_LIKE_OPEN, '<');
|
|
56
|
+
}
|
|
57
|
+
function escapeLineOutsideInlineCode(line) {
|
|
58
|
+
let result = '';
|
|
59
|
+
let lastIndex = 0;
|
|
60
|
+
INLINE_CODE_SPAN.lastIndex = 0;
|
|
61
|
+
for (let match = INLINE_CODE_SPAN.exec(line); match !== null; match = INLINE_CODE_SPAN.exec(line)) {
|
|
62
|
+
result += escapeOutsideCode(line.slice(lastIndex, match.index));
|
|
63
|
+
result += match[0];
|
|
64
|
+
lastIndex = match.index + match[0].length;
|
|
65
|
+
}
|
|
66
|
+
return result + escapeOutsideCode(line.slice(lastIndex));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns `markdown` with MDX-significant characters escaped everywhere MDX would
|
|
70
|
+
* interpret them, and untouched inside fenced blocks and inline code spans.
|
|
71
|
+
*
|
|
72
|
+
* Markdown structure is preserved exactly: headings, emphasis, lists, links and tables
|
|
73
|
+
* use none of the characters this touches.
|
|
74
|
+
*/
|
|
75
|
+
export function escapeMarkdownForMdx(markdown) {
|
|
76
|
+
let insideFence = false;
|
|
77
|
+
return markdown
|
|
78
|
+
.split('\n')
|
|
79
|
+
.map((line) => {
|
|
80
|
+
if (FENCE_LINE.test(line)) {
|
|
81
|
+
insideFence = !insideFence;
|
|
82
|
+
return line;
|
|
83
|
+
}
|
|
84
|
+
return insideFence ? line : escapeLineOutsideInlineCode(line);
|
|
85
|
+
})
|
|
86
|
+
.join('\n');
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=technical-documentation-mdx-escaping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"technical-documentation-mdx-escaping.js","sourceRoot":"","sources":["../../../../../src/companion/rendering/technical-documentation-mdx-escaping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,kGAAkG;AAClG,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAE3C,uFAAuF;AACvF,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAE7C;;;;GAIG;AACH,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,eAAe,GAAG,YAAY,CAAC;AAErC,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,2BAA2B,CAAC,IAAY;IAC/C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,gBAAgB,CAAC,SAAS,GAAG,CAAC,CAAC;IAC/B,KAAK,IAAI,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,KAAK,IAAI,EAAE,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClG,MAAM,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QACnB,SAAS,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,OAAO,QAAQ;SACZ,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,WAAW,GAAG,CAAC,WAAW,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,2BAA2B,CAAC,IAAI,CAAC,CAAC;IAChE,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC","sourcesContent":["/**\n * Escapes MDX-significant syntax in prose that was authored as, or derived into, MARKDOWN.\n *\n * ## The defect this exists to prevent\n *\n * Docusaurus compiles `.mdx`, not `.md`. In MDX, `{` opens a JavaScript expression and `<`\n * opens a JSX element. Both are ordinary literal characters in Markdown, so any producer\n * that emits correct Markdown can emit invalid MDX without doing anything wrong.\n *\n * Documentation is full of exactly these characters, because route templates are:\n *\n * ```text\n * **PUT /api/v1/organizations/{organizationId}/todos/{todoId}/assign**\n * ```\n *\n * That line renders as a client-side crash — `ReferenceError: organizationId is not defined`\n * — thrown from inside `<MDXContent>`, with the page replaced by \"This page crashed.\" It is\n * not a build failure: the MDX compiles, because `{organizationId}` is a syntactically valid\n * expression. It fails at RENDER, in the reader's browser, which is the worst place to find it.\n *\n * ## Why escaping happens here rather than at the producer\n *\n * The producers are content authors and the derivation service, and neither of them should\n * need to know which renderer their prose eventually reaches. The Markdown renderer next door\n * emits `.md` and must NOT escape — a `\\{` there is a literal backslash on the page. So the\n * obligation belongs to the renderer that chose MDX, and to no one else.\n *\n * ## Why it is fence-aware\n *\n * MDX does not parse expressions inside code spans or fenced blocks, so escaping there would\n * be visible corruption — a curl example would render a stray backslash. Code is also where\n * these characters are MOST common, which is why a naive global replace looks like it works\n * (the crash stops) while quietly damaging every example on the page.\n */\n\n/** Opens or closes a fenced code block: ``` or ~~~, optionally indented, with any info string. */\nconst FENCE_LINE = /^\\s{0,3}(`{3,}|~{3,})/;\n\n/** One inline code span: a backtick run, the shortest content, then a matching run. */\nconst INLINE_CODE_SPAN = /(`+)([\\s\\S]*?)\\1/g;\n\n/**\n * A `<` that MDX would read as the start of a JSX element — followed by a name or a\n * closing slash. A `<` used as \"less than\" (`a < b`, `<= 3`) is left alone, because\n * escaping it would put a visible entity into ordinary prose.\n */\nconst JSX_LIKE_OPEN = /<(?=[A-Za-z/])/g;\n\n/**\n * A `{` that is not already escaped.\n *\n * The lookbehind is what makes this function idempotent, and idempotence is not a\n * theoretical nicety here: an author may reasonably write `\\{` themselves, and a second\n * escaping pass would turn it into `\\\\{` — which renders a VISIBLE backslash and leaves\n * the expression evaluating anyway, i.e. strictly worse than doing nothing.\n */\nconst UNESCAPED_BRACE = /(?<!\\\\)\\{/g;\n\nfunction escapeOutsideCode(text: string): string {\n return text.replace(UNESCAPED_BRACE, '\\\\{').replace(JSX_LIKE_OPEN, '<');\n}\n\nfunction escapeLineOutsideInlineCode(line: string): string {\n let result = '';\n let lastIndex = 0;\n INLINE_CODE_SPAN.lastIndex = 0;\n for (let match = INLINE_CODE_SPAN.exec(line); match !== null; match = INLINE_CODE_SPAN.exec(line)) {\n result += escapeOutsideCode(line.slice(lastIndex, match.index));\n result += match[0];\n lastIndex = match.index + match[0].length;\n }\n return result + escapeOutsideCode(line.slice(lastIndex));\n}\n\n/**\n * Returns `markdown` with MDX-significant characters escaped everywhere MDX would\n * interpret them, and untouched inside fenced blocks and inline code spans.\n *\n * Markdown structure is preserved exactly: headings, emphasis, lists, links and tables\n * use none of the characters this touches.\n */\nexport function escapeMarkdownForMdx(markdown: string): string {\n let insideFence = false;\n return markdown\n .split('\\n')\n .map((line) => {\n if (FENCE_LINE.test(line)) {\n insideFence = !insideFence;\n return line;\n }\n return insideFence ? line : escapeLineOutsideInlineCode(line);\n })\n .join('\\n');\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type OpenApiGenerationOutput } from '../../openapi/openapi-generation-output.schemas';
|
|
2
|
+
import { type TechnicalDocumentationRendererResult } from './technical-documentation-render-model';
|
|
3
|
+
export declare function renderVerifiedTechnicalDocumentationOpenApi(input: {
|
|
4
|
+
readonly documents: OpenApiGenerationOutput;
|
|
5
|
+
readonly sourceUnitRefs: readonly string[];
|
|
6
|
+
}): TechnicalDocumentationRendererResult;
|
|
7
|
+
//# sourceMappingURL=technical-documentation-openapi-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"technical-documentation-openapi-renderer.d.ts","sourceRoot":"","sources":["../../../../../src/companion/rendering/technical-documentation-openapi-renderer.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAEL,KAAK,oCAAoC,EAC1C,MAAM,wCAAwC,CAAC;AAIhD,wBAAgB,2CAA2C,CAAC,KAAK,EAAE;IACjE,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C,GAAG,oCAAoC,CA2CvC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { TechnicalDocumentationOutputKind, TechnicalDocumentationRendererKind } from '@wildo-ai/saas-specifications/technical-documentation';
|
|
2
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
3
|
+
import { OPENAPI_OUTPUT_FILENAMES, } from '../../openapi/openapi-generation-output.schemas.js';
|
|
4
|
+
import { createTechnicalDocumentationRendererExecution, } from './technical-documentation-render-model.js';
|
|
5
|
+
const OPENAPI_RENDERER_REF = 'technical-documentation:renderer/openapi';
|
|
6
|
+
export function renderVerifiedTechnicalDocumentationOpenApi(input) {
|
|
7
|
+
if (input.sourceUnitRefs.length === 0)
|
|
8
|
+
throw new Error('technical-documentation OpenAPI rendering requires authorized source units');
|
|
9
|
+
return {
|
|
10
|
+
execution: createTechnicalDocumentationRendererExecution({
|
|
11
|
+
rendererKind: TechnicalDocumentationRendererKind.OPENAPI,
|
|
12
|
+
rendererRef: OPENAPI_RENDERER_REF,
|
|
13
|
+
rendererVersion: 1,
|
|
14
|
+
configurationRef: 'technical-documentation:renderer-configuration/openapi-3-1-0',
|
|
15
|
+
}),
|
|
16
|
+
files: input.documents.sections.flatMap((section) => {
|
|
17
|
+
const sectionPath = section.section.toLowerCase().replaceAll('_', '-');
|
|
18
|
+
const sourceUnitRefs = [...input.sourceUnitRefs].sort();
|
|
19
|
+
const filenames = OPENAPI_OUTPUT_FILENAMES[section.section];
|
|
20
|
+
const yaml = stringifyYaml(section.document, { lineWidth: 0, sortMapEntries: false });
|
|
21
|
+
const json = `${JSON.stringify(section.document, null, 2)}\n`;
|
|
22
|
+
/**
|
|
23
|
+
* YAML and JSON are two deterministic serializations of one generator
|
|
24
|
+
* document. YAML is the reviewable/downloadable contract; JSON is the
|
|
25
|
+
* browser renderer input. Neither file contains renderer-authored API
|
|
26
|
+
* meaning.
|
|
27
|
+
*/
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
outputRef: `technical-documentation:output/openapi/${sectionPath}/yaml`,
|
|
31
|
+
rendererRef: OPENAPI_RENDERER_REF,
|
|
32
|
+
outputKind: TechnicalDocumentationOutputKind.OPENAPI_DOCUMENT,
|
|
33
|
+
sourceUnitRefs,
|
|
34
|
+
normalizedRelativePath: `openapi/${filenames.yaml}`,
|
|
35
|
+
mediaType: 'application/yaml',
|
|
36
|
+
bytes: Buffer.from(yaml, 'utf8'),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
outputRef: `technical-documentation:output/openapi/${sectionPath}/json`,
|
|
40
|
+
rendererRef: OPENAPI_RENDERER_REF,
|
|
41
|
+
outputKind: TechnicalDocumentationOutputKind.OPENAPI_DOCUMENT,
|
|
42
|
+
sourceUnitRefs,
|
|
43
|
+
normalizedRelativePath: `openapi/${filenames.json}`,
|
|
44
|
+
mediaType: 'application/json',
|
|
45
|
+
bytes: Buffer.from(json, 'utf8'),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=technical-documentation-openapi-renderer.js.map
|