@astryxdesign/cli 0.6.3-canary.ea2f048 → 0.6.3-canary.ebaebc4
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/README.md +2 -1
- package/api/build/build.type.d.mts +2 -2
- package/api/build/build.type.mjs +2 -2
- package/api/component/component.type.d.mts +6 -6
- package/api/component/component.type.mjs +19 -19
- package/api/discover/discover.type.d.mts +4 -4
- package/api/discover/discover.type.mjs +10 -10
- package/api/docs/_adapter.d.mts +37 -24
- package/api/docs/_adapter.mjs +169 -83
- package/api/docs/compiled-topics.test.mjs +78 -0
- package/api/docs/detail/detail.mjs +14 -63
- package/api/docs/detail/section/section.d.mts +1 -1
- package/api/docs/detail/section/section.mjs +44 -20
- package/api/docs/detail/section/section.test.mjs +41 -0
- package/api/docs/docs.d.mts +7 -2
- package/api/docs/docs.doc.mjs +27 -10
- package/api/docs/docs.mjs +16 -9
- package/api/docs/docs.test.mjs +6 -0
- package/api/docs/docs.type.d.mts +40 -3
- package/api/docs/docs.type.mjs +36 -8
- package/api/docs/index/index.d.mts +18 -0
- package/api/docs/index/index.mjs +32 -0
- package/api/docs/index/index.test.mjs +62 -0
- package/api/docs/integrationDocs.test.mjs +106 -0
- package/api/doctor/doctor.d.mts +48 -0
- package/api/doctor/doctor.mjs +232 -0
- package/api/doctor/doctor.test.mjs +196 -0
- package/api/hook/hook.type.d.mts +3 -3
- package/api/hook/hook.type.mjs +11 -11
- package/api/hook/list/list.d.mts +1 -1
- package/api/integration/add-contribution.mjs +5 -3
- package/api/integration/add-contribution.test.mjs +4 -4
- package/api/integration/integration-authoring.type.d.mts +1 -1
- package/api/integration/pack-check.mjs +49 -7
- package/api/integration/pack-check.test.mjs +249 -0
- package/api/search/search.d.mts +1 -1
- package/api/search/search.mjs +5 -5
- package/api/search/search.type.d.mts +2 -2
- package/api/search/search.type.mjs +1 -1
- package/api/swizzle/swizzle.type.d.mts +2 -2
- package/api/swizzle/swizzle.type.mjs +2 -2
- package/api/template/template.d.mts +1 -1
- package/api/template/template.type.d.mts +6 -6
- package/api/template/template.type.mjs +12 -12
- package/api/theme/build/build.mjs +20 -6
- package/api/theme/build/build.test.mjs +127 -0
- package/api/theme/palette/generate/generate.mjs +1 -1
- package/api/theme/palette/generate/generator.d.mts +10 -13
- package/api/theme/palette/generate/generator.mjs +7 -3
- package/api/theme/theme.type.d.mts +170 -11
- package/api/theme/theme.type.mjs +94 -27
- package/api/upgrade/_adapter.mjs +71 -5
- package/api/upgrade/project-context.test.mjs +272 -0
- package/api/upgrade/upgrade.doc.mjs +4 -3
- package/api/upgrade/upgrade.type.d.mts +5 -5
- package/api/upgrade/upgrade.type.mjs +11 -11
- package/assets/codemods/integration-discovery.mjs +40 -2
- package/assets/codemods/integration-discovery.test.mjs +58 -0
- package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
- package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
- package/assets/docs/README.md +9 -0
- package/assets/docs/authoring.doc.mjs +14 -0
- package/assets/docs/cli-integrations.doc.mjs +86 -15
- package/assets/docs/styling-libraries.doc.mjs +1 -1
- package/assets/docs/working-with-ai.doc.mjs +1 -1
- package/authoring/_shared/contract.ts +22 -0
- package/authoring/codemod/codemod.doc.mjs +6 -1
- package/authoring/codemod/parse.d.mts +8 -8
- package/authoring/codemod/parse.mjs +8 -6
- package/authoring/config/parse.d.mts +13 -13
- package/authoring/config/parse.mjs +8 -8
- package/authoring/config/type.ts +3 -3
- package/authoring/debug/parse.d.mts +5 -5
- package/authoring/debug/parse.mjs +3 -3
- package/authoring/doctypes/_schema.d.mts +788 -23
- package/authoring/doctypes/_schema.mjs +492 -39
- package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +62 -0
- package/authoring/doctypes/base/type.ts +40 -0
- package/authoring/doctypes/command/command.doc.mjs +3 -2
- package/authoring/doctypes/command/parse.d.mts +2 -2
- package/authoring/doctypes/command/parse.mjs +1 -1
- package/authoring/doctypes/command/type.ts +3 -2
- package/authoring/doctypes/component/component.doc.mjs +6 -3
- package/authoring/doctypes/component/parse.d.mts +2 -2
- package/authoring/doctypes/component/parse.mjs +1 -1
- package/authoring/doctypes/component/type.ts +4 -3
- package/authoring/doctypes/enum/parse.d.mts +2 -2
- package/authoring/doctypes/enum/parse.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +3 -1
- package/authoring/doctypes/function/function.doc.mjs +4 -0
- package/authoring/doctypes/function/parse.d.mts +2 -2
- package/authoring/doctypes/function/parse.mjs +1 -1
- package/authoring/doctypes/function/type.ts +6 -2
- package/authoring/doctypes/hook/hook.doc.mjs +4 -0
- package/authoring/doctypes/hook/parse.d.mts +2 -2
- package/authoring/doctypes/hook/parse.mjs +1 -1
- package/authoring/doctypes/hook/type.ts +3 -2
- package/authoring/doctypes/legacy.d.mts +8 -6
- package/authoring/doctypes/legacy.mjs +5 -4
- package/authoring/doctypes/load-contract.test.mjs +207 -0
- package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
- package/authoring/doctypes/namespace/namespace.doc.mjs +132 -0
- package/authoring/doctypes/namespace/parse.d.mts +12 -0
- package/authoring/doctypes/namespace/parse.mjs +25 -0
- package/authoring/doctypes/namespace/parse.test.mjs +165 -0
- package/authoring/doctypes/namespace/type.ts +71 -0
- package/authoring/doctypes/parse.d.mts +20 -18
- package/authoring/doctypes/parse.mjs +16 -10
- package/authoring/doctypes/parse.test.mjs +77 -3
- package/authoring/doctypes/reference/parse.d.mts +2 -2
- package/authoring/doctypes/reference/parse.mjs +8 -5
- package/authoring/doctypes/reference/reference.doc.mjs +17 -4
- package/authoring/doctypes/reference/type.ts +51 -5
- package/authoring/doctypes/schema/parse.d.mts +2 -2
- package/authoring/doctypes/schema/parse.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +3 -2
- package/authoring/doctypes/template/parse.d.mts +92 -1
- package/authoring/doctypes/template/parse.mjs +36 -2
- package/authoring/doctypes/template/parse.test.mjs +8 -2
- package/authoring/doctypes/template/template.doc.mjs +4 -0
- package/authoring/doctypes/template/type.ts +5 -2
- package/authoring/doctypes/types.ts +10 -9
- package/authoring/gap-report/parse.d.mts +10 -10
- package/authoring/gap-report/parse.mjs +6 -6
- package/authoring/gap-report/type.ts +1 -1
- package/authoring/identity/identity.doc.d.mts +9 -0
- package/authoring/identity/identity.doc.mjs +61 -0
- package/authoring/identity/type.ts +132 -0
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +49 -17
- package/authoring/index.mjs +1 -0
- package/authoring/integration/integration.doc.mjs +13 -6
- package/authoring/integration/parse.d.mts +2 -2
- package/authoring/integration/parse.mjs +1 -1
- package/authoring/integration/parse.test.mjs +10 -1
- package/authoring/integration/schema.d.mts +6 -4
- package/authoring/integration/schema.mjs +9 -3
- package/authoring/integration/type.ts +23 -6
- package/authoring/shadcn/receipt.d.mts +6 -6
- package/clients/cli/commands/docs.doc.mjs +13 -3
- package/clients/cli/commands/docs.mjs +121 -21
- package/clients/cli/commands/docs.test.mjs +88 -0
- package/clients/cli/commands/integration-authoring.test.mjs +13 -9
- package/clients/cli/commands/theme-palette-generate.doc.mjs +8 -4
- package/clients/cli/commands/upgrade.doc.mjs +2 -2
- package/clients/cli/formatters/index.mjs +162 -1
- package/clients/cli/formatters/index.test.mjs +91 -0
- package/clients/cli/lib/manifest.mjs +7 -2
- package/foundation/config/project.mjs +21 -6
- package/foundation/discovery/authoring-self-docs.d.mts +69 -0
- package/foundation/discovery/authoring-self-docs.mjs +214 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +154 -0
- package/foundation/discovery/component-discovery.d.mts +1 -1
- package/foundation/discovery/component-discovery.mjs +2 -1
- package/foundation/discovery/docs-discovery.d.mts +11 -4
- package/foundation/discovery/docs-discovery.mjs +208 -88
- package/foundation/discovery/docs-discovery.test.mjs +279 -13
- package/foundation/discovery/docs-output-budget.d.mts +28 -0
- package/foundation/discovery/docs-output-budget.mjs +50 -0
- package/foundation/discovery/docs-section-key.d.mts +98 -0
- package/foundation/discovery/docs-section-key.mjs +221 -0
- package/foundation/discovery/docs-section-key.test.mjs +224 -0
- package/foundation/discovery/template-adapter.mjs +2 -1
- package/foundation/discovery/theming-targets.test.mjs +4 -0
- package/foundation/doc-compiler/compile.d.mts +162 -0
- package/foundation/doc-compiler/compile.mjs +262 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +687 -0
- package/foundation/doc-compiler/ir.d.mts +9 -0
- package/foundation/doc-compiler/ir.mjs +287 -0
- package/foundation/doc-compiler/lenses.d.mts +33 -0
- package/foundation/doc-compiler/lenses.mjs +127 -0
- package/foundation/identity/provider-identity.d.mts +90 -0
- package/foundation/identity/provider-identity.mjs +320 -0
- package/foundation/identity/provider-identity.test.mjs +254 -0
- package/foundation/identity/providers.d.mts +7 -0
- package/foundation/identity/providers.mjs +16 -0
- package/foundation/integrations/autolink.mjs +12 -5
- package/foundation/integrations/integration-warnings.mjs +6 -0
- package/foundation/integrations/integrations.d.mts +46 -2
- package/foundation/integrations/integrations.mjs +167 -8
- package/foundation/integrations/integrations.test.mjs +384 -1
- package/foundation/integrations/provider-conflicts.test.mjs +125 -0
- package/foundation/integrations/validate-contributions.d.mts +2 -0
- package/foundation/integrations/validate-contributions.mjs +10 -0
- package/foundation/response/json-contract.test.mjs +46 -17
- package/foundation/response/response-types.doc.mjs +6 -1
- package/package.json +9 -11
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The compiled-node contract and its sealed parser.
|
|
5
|
+
*
|
|
6
|
+
* @input Any value that claims to be a compiled reference node — typically one
|
|
7
|
+
* read back from JSON.
|
|
8
|
+
* @output The same value once it validates; a thrown Error naming the problems
|
|
9
|
+
* otherwise. An unsupported schema version fails with its own message before
|
|
10
|
+
* anything else is checked.
|
|
11
|
+
* @position The load boundary for compiled nodes that did not come straight
|
|
12
|
+
* from ./compile.mjs in this process. The value is returned as given, not
|
|
13
|
+
* rebuilt, so key order (which response JSON follows) survives. A node is
|
|
14
|
+
* plain JSON throughout, so nothing it holds can surprise a reader.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {SECTION_KEY_RE} from '../discovery/docs-section-key.mjs';
|
|
18
|
+
import {COMPILED_DOC_SCHEMA_VERSION} from './compile.mjs';
|
|
19
|
+
|
|
20
|
+
const NODE_FIELDS = new Set([
|
|
21
|
+
'schemaVersion',
|
|
22
|
+
'kind',
|
|
23
|
+
'stage',
|
|
24
|
+
'id',
|
|
25
|
+
'lang',
|
|
26
|
+
'provenance',
|
|
27
|
+
'sourceTitles',
|
|
28
|
+
'doc',
|
|
29
|
+
]);
|
|
30
|
+
const RESOLVED_FIELDS = new Set([
|
|
31
|
+
'status',
|
|
32
|
+
'topic',
|
|
33
|
+
'section',
|
|
34
|
+
'previewType',
|
|
35
|
+
'content',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/** How many problems one message lists before it stops. */
|
|
39
|
+
const MAX_PROBLEMS = 10;
|
|
40
|
+
|
|
41
|
+
/** @param {unknown} value @returns {value is Record<string, any>} */
|
|
42
|
+
const isRecord = value =>
|
|
43
|
+
value != null && typeof value === 'object' && !Array.isArray(value);
|
|
44
|
+
|
|
45
|
+
/** @param {unknown} value @returns {value is string} */
|
|
46
|
+
const isText = value => typeof value === 'string' && value !== '';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A package name, never a location: provenance must not leak a path.
|
|
50
|
+
* @param {unknown} value
|
|
51
|
+
* @returns {boolean}
|
|
52
|
+
*/
|
|
53
|
+
const isPackageName = value =>
|
|
54
|
+
isText(value) &&
|
|
55
|
+
!value.startsWith('/') &&
|
|
56
|
+
!value.startsWith('.') &&
|
|
57
|
+
!value.startsWith('\\') &&
|
|
58
|
+
!value.startsWith('file:') &&
|
|
59
|
+
!/^[A-Za-z]:[\\/]/.test(value);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Validate a compiled reference node.
|
|
63
|
+
* @param {unknown} value
|
|
64
|
+
* @returns {import('./compile.mjs').CompiledReferenceNode}
|
|
65
|
+
*/
|
|
66
|
+
export function parseCompiledReferenceNode(value) {
|
|
67
|
+
const node = /** @type {any} */ (value);
|
|
68
|
+
if (node?.schemaVersion !== COMPILED_DOC_SCHEMA_VERSION) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Compiled doc schema version ${JSON.stringify(node?.schemaVersion)} is not supported; this CLI reads version ${COMPILED_DOC_SCHEMA_VERSION}. Compile the docs again with this CLI.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const problems = jsonProblems(node, 'node');
|
|
74
|
+
if (problems.length === 0) problems.push(...structureProblems(node));
|
|
75
|
+
if (problems.length > 0) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Invalid compiled doc node: ${problems.slice(0, MAX_PROBLEMS).join('; ')}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Where a value stops being plain JSON: anything but null, booleans, finite
|
|
85
|
+
* numbers, strings, arrays and plain objects; a symbol key; or a cycle.
|
|
86
|
+
* @param {unknown} value
|
|
87
|
+
* @param {string} at
|
|
88
|
+
* @param {Set<object>} [ancestors]
|
|
89
|
+
* @param {string[]} [out]
|
|
90
|
+
* @returns {string[]}
|
|
91
|
+
*/
|
|
92
|
+
function jsonProblems(value, at, ancestors = new Set(), out = []) {
|
|
93
|
+
if (out.length >= MAX_PROBLEMS) return out;
|
|
94
|
+
if (
|
|
95
|
+
value === null ||
|
|
96
|
+
typeof value === 'string' ||
|
|
97
|
+
typeof value === 'boolean'
|
|
98
|
+
) {
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
if (typeof value === 'number') {
|
|
102
|
+
if (!Number.isFinite(value))
|
|
103
|
+
out.push(`${at}: ${value} is not a JSON number`);
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
if (value === undefined) {
|
|
107
|
+
out.push(`${at}: undefined is not JSON`);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
if (typeof value !== 'object') {
|
|
111
|
+
out.push(`${at}: a ${typeof value} is not JSON`);
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
if (ancestors.has(value)) {
|
|
115
|
+
out.push(`${at}: refers back to itself`);
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
const proto = Object.getPrototypeOf(value);
|
|
119
|
+
if (!Array.isArray(value) && proto !== Object.prototype && proto !== null) {
|
|
120
|
+
out.push(
|
|
121
|
+
`${at}: a ${proto?.constructor?.name ?? 'non-plain object'} is not JSON`,
|
|
122
|
+
);
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
126
|
+
out.push(`${at}: has symbol keys`);
|
|
127
|
+
}
|
|
128
|
+
ancestors.add(value);
|
|
129
|
+
if (Array.isArray(value)) {
|
|
130
|
+
value.forEach((item, index) =>
|
|
131
|
+
jsonProblems(item, `${at}[${index}]`, ancestors, out),
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
for (const [key, item] of Object.entries(value)) {
|
|
135
|
+
jsonProblems(item, `${at}.${key}`, ancestors, out);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
ancestors.delete(value);
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The node's own shape, once it is known to be JSON.
|
|
144
|
+
* @param {Record<string, any>} node
|
|
145
|
+
* @returns {string[]}
|
|
146
|
+
*/
|
|
147
|
+
function structureProblems(node) {
|
|
148
|
+
/** @type {string[]} */
|
|
149
|
+
const problems = [];
|
|
150
|
+
const unknown = Object.keys(node).filter(key => !NODE_FIELDS.has(key));
|
|
151
|
+
if (unknown.length > 0)
|
|
152
|
+
problems.push(`unknown fields: ${unknown.join(', ')}`);
|
|
153
|
+
if (node.kind !== 'reference') problems.push('kind: expected "reference"');
|
|
154
|
+
if (node.stage !== 'lowered' && node.stage !== 'linked') {
|
|
155
|
+
problems.push('stage: expected "lowered" or "linked"');
|
|
156
|
+
}
|
|
157
|
+
if (!isText(node.id)) problems.push('id: expected a topic name');
|
|
158
|
+
if (node.lang !== null && !isText(node.lang)) {
|
|
159
|
+
problems.push('lang: expected a language or null');
|
|
160
|
+
}
|
|
161
|
+
const provenance = node.provenance;
|
|
162
|
+
if (
|
|
163
|
+
!isRecord(provenance) ||
|
|
164
|
+
!isPackageName(provenance.provider) ||
|
|
165
|
+
(provenance.replaces !== null && !isText(provenance.replaces)) ||
|
|
166
|
+
!Array.isArray(provenance.extensions) ||
|
|
167
|
+
!provenance.extensions.every(isPackageName)
|
|
168
|
+
) {
|
|
169
|
+
problems.push(
|
|
170
|
+
'provenance: expected {provider, replaces, extensions} naming packages, not paths',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const titles = isRecord(node.sourceTitles) ? node.sourceTitles : null;
|
|
174
|
+
if (!titles || !Object.values(titles).every(isText)) {
|
|
175
|
+
problems.push('sourceTitles: expected section key -> authored title');
|
|
176
|
+
}
|
|
177
|
+
const doc = node.doc;
|
|
178
|
+
if (
|
|
179
|
+
!isRecord(doc) ||
|
|
180
|
+
!isText(doc.name) ||
|
|
181
|
+
!isText(doc.title) ||
|
|
182
|
+
typeof doc.description !== 'string' ||
|
|
183
|
+
!Array.isArray(doc.sections) ||
|
|
184
|
+
doc.sections.length === 0
|
|
185
|
+
) {
|
|
186
|
+
problems.push('doc: expected {name, title, description, sections}');
|
|
187
|
+
} else {
|
|
188
|
+
problems.push(...sectionProblems(doc.sections, titles, node.stage));
|
|
189
|
+
}
|
|
190
|
+
return problems;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {any[]} sections
|
|
195
|
+
* @param {Record<string, any> | null} titles
|
|
196
|
+
* @param {unknown} stage
|
|
197
|
+
* @returns {string[]}
|
|
198
|
+
*/
|
|
199
|
+
function sectionProblems(sections, titles, stage) {
|
|
200
|
+
/** @type {string[]} */
|
|
201
|
+
const problems = [];
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
sections.forEach((section, index) => {
|
|
204
|
+
const at = `doc.sections[${index}]`;
|
|
205
|
+
if (
|
|
206
|
+
!isRecord(section) ||
|
|
207
|
+
typeof section.id !== 'string' ||
|
|
208
|
+
!SECTION_KEY_RE.test(section.id)
|
|
209
|
+
) {
|
|
210
|
+
problems.push(`${at}.id: expected a section key`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (seen.has(section.id)) {
|
|
214
|
+
problems.push(`${at}.id: two sections have the key "${section.id}"`);
|
|
215
|
+
}
|
|
216
|
+
seen.add(section.id);
|
|
217
|
+
if (!isText(section.title)) problems.push(`${at}.title: expected a title`);
|
|
218
|
+
if (titles && !Object.hasOwn(titles, section.id)) {
|
|
219
|
+
problems.push(
|
|
220
|
+
`sourceTitles: no authored title for section "${section.id}"`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (!Array.isArray(section.content)) {
|
|
224
|
+
problems.push(`${at}.content: expected an array of blocks`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
section.content.forEach((/** @type {unknown} */ block, blockIndex) => {
|
|
228
|
+
const where = `${at}.content[${blockIndex}]`;
|
|
229
|
+
if (!isRecord(block) || !isText(block.type)) {
|
|
230
|
+
problems.push(`${where}: expected a block with a type`);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (block.type !== 'token-ref') return;
|
|
234
|
+
const ref = `${where}: token reference to "${block.topic}"`;
|
|
235
|
+
if (stage === 'lowered') {
|
|
236
|
+
if ('resolved' in block) {
|
|
237
|
+
problems.push(`${ref}: a lowered node carries no resolution`);
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (!('resolved' in block)) {
|
|
242
|
+
problems.push(`${ref}: a linked node resolves every reference`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const problem = resolutionProblem(block.resolved);
|
|
246
|
+
if (problem) problems.push(`${ref}: ${problem}`);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
return problems;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {unknown} resolved
|
|
254
|
+
* @returns {string | null}
|
|
255
|
+
*/
|
|
256
|
+
function resolutionProblem(resolved) {
|
|
257
|
+
if (!isRecord(resolved)) return 'expected a resolution';
|
|
258
|
+
switch (resolved.status) {
|
|
259
|
+
case 'unknown-topic':
|
|
260
|
+
case 'unknown-section':
|
|
261
|
+
return Object.keys(resolved).length === 1 ? null : 'unexpected fields';
|
|
262
|
+
case 'resolved': {
|
|
263
|
+
if (!isText(resolved.topic)) return 'topic: expected a topic name';
|
|
264
|
+
if (
|
|
265
|
+
typeof resolved.section !== 'string' ||
|
|
266
|
+
!SECTION_KEY_RE.test(resolved.section)
|
|
267
|
+
) {
|
|
268
|
+
return 'section: expected a section key';
|
|
269
|
+
}
|
|
270
|
+
if ('previewType' in resolved && !isText(resolved.previewType)) {
|
|
271
|
+
return 'previewType: expected a preview type';
|
|
272
|
+
}
|
|
273
|
+
if (
|
|
274
|
+
!Array.isArray(resolved.content) ||
|
|
275
|
+
!resolved.content.every(block => isRecord(block) && isText(block.type))
|
|
276
|
+
) {
|
|
277
|
+
return 'content: expected blocks with a type';
|
|
278
|
+
}
|
|
279
|
+
const extra = Object.keys(resolved).filter(
|
|
280
|
+
key => !RESOLVED_FIELDS.has(key),
|
|
281
|
+
);
|
|
282
|
+
return extra.length > 0 ? `unexpected fields: ${extra.join(', ')}` : null;
|
|
283
|
+
}
|
|
284
|
+
default:
|
|
285
|
+
return `status: expected resolved, unknown-topic or unknown-section, got ${JSON.stringify(resolved.status)}`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The node's sections as readers look them up: each one knows its authored
|
|
6
|
+
* title, so a query in the authoring language finds a translated section.
|
|
7
|
+
* For lookup only; a section a reader gets back comes from
|
|
8
|
+
* {@link sectionView}.
|
|
9
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
10
|
+
* @returns {any[]}
|
|
11
|
+
*/
|
|
12
|
+
export function readerSections(node: import("./compile.mjs").CompiledReferenceNode): any[];
|
|
13
|
+
/**
|
|
14
|
+
* `docs.detail`: the whole topic, with every token reference inlined.
|
|
15
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node a linked node
|
|
16
|
+
* @returns {any}
|
|
17
|
+
*/
|
|
18
|
+
export function detailView(node: import("./compile.mjs").CompiledReferenceNode): any;
|
|
19
|
+
/**
|
|
20
|
+
* `docs.index`: what the topic is, and each section's key, title and summary.
|
|
21
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
22
|
+
* @returns {import('../../api/docs/docs.type.mjs').DocsIndex}
|
|
23
|
+
*/
|
|
24
|
+
export function indexView(node: import("./compile.mjs").CompiledReferenceNode): import("../../api/docs/docs.type.mjs").DocsIndex;
|
|
25
|
+
/**
|
|
26
|
+
* `docs.detail.section`: one section with its token references inlined. A
|
|
27
|
+
* referenced section's content takes the reference's place; the section takes
|
|
28
|
+
* the preview type of the last reference that has one, unless it has its own.
|
|
29
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
30
|
+
* @param {any} section a linked section of `node`
|
|
31
|
+
* @returns {any}
|
|
32
|
+
*/
|
|
33
|
+
export function sectionView(node: import("./compile.mjs").CompiledReferenceNode, section: any): any;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Lenses — the docs API's response shapes, read off compiled nodes.
|
|
5
|
+
*
|
|
6
|
+
* @input A compiled reference node from ./compile.mjs: lowered for the index
|
|
7
|
+
* and for section lookup, linked for anything that inlines token references.
|
|
8
|
+
* @output The `docs.detail` topic, the `docs.index` section index, one
|
|
9
|
+
* `docs.detail.section` section, and the sections as readers look them up.
|
|
10
|
+
* Every view is a fresh copy, so a reader may edit what it gets back without
|
|
11
|
+
* touching the node, which other reads of the same catalog share.
|
|
12
|
+
* @position Between the compiler and api/docs. A lens only projects: it never
|
|
13
|
+
* loads, merges, overlays, keys, or resolves a reference itself.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
buildDocsIndexData,
|
|
18
|
+
withSourceTitle,
|
|
19
|
+
} from '../discovery/docs-section-key.mjs';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
23
|
+
* @param {any} section
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function authoredTitle(node, section) {
|
|
27
|
+
return node.sourceTitles[section.id] ?? section.title;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The node's sections as readers look them up: each one knows its authored
|
|
32
|
+
* title, so a query in the authoring language finds a translated section.
|
|
33
|
+
* For lookup only; a section a reader gets back comes from
|
|
34
|
+
* {@link sectionView}.
|
|
35
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
36
|
+
* @returns {any[]}
|
|
37
|
+
*/
|
|
38
|
+
export function readerSections(node) {
|
|
39
|
+
return node.doc.sections.map((/** @type {any} */ section) =>
|
|
40
|
+
withSourceTitle({...section}, authoredTitle(node, section)),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `docs.detail`: the whole topic, with every token reference inlined.
|
|
46
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node a linked node
|
|
47
|
+
* @returns {any}
|
|
48
|
+
*/
|
|
49
|
+
export function detailView(node) {
|
|
50
|
+
if (node.stage !== 'linked') {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`"${node.id}" must be linked before its whole doc is read.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
// Assigning `sections` keeps it where the authored doc put it.
|
|
56
|
+
const view = structuredClone({...node.doc, sections: []});
|
|
57
|
+
view.sections = node.doc.sections.map((/** @type {any} */ section) =>
|
|
58
|
+
sectionView(node, section),
|
|
59
|
+
);
|
|
60
|
+
return view;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `docs.index`: what the topic is, and each section's key, title and summary.
|
|
65
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
66
|
+
* @returns {import('../../api/docs/docs.type.mjs').DocsIndex}
|
|
67
|
+
*/
|
|
68
|
+
export function indexView(node) {
|
|
69
|
+
return buildDocsIndexData(node.doc);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `docs.detail.section`: one section with its token references inlined. A
|
|
74
|
+
* referenced section's content takes the reference's place; the section takes
|
|
75
|
+
* the preview type of the last reference that has one, unless it has its own.
|
|
76
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
77
|
+
* @param {any} section a linked section of `node`
|
|
78
|
+
* @returns {any}
|
|
79
|
+
*/
|
|
80
|
+
export function sectionView(node, section) {
|
|
81
|
+
/** @type {any[]} */
|
|
82
|
+
const content = [];
|
|
83
|
+
/** @type {string | null} */
|
|
84
|
+
let previewType = null;
|
|
85
|
+
for (const block of section.content) {
|
|
86
|
+
if (block?.type !== 'token-ref') {
|
|
87
|
+
content.push(structuredClone(block));
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const target = block.resolved;
|
|
91
|
+
if (target == null) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`The token reference to "${block.topic}" in "${node.id}" was read before it was linked.`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (target.status === 'unknown-topic') {
|
|
97
|
+
content.push({
|
|
98
|
+
type: 'prose',
|
|
99
|
+
text: `[token-ref: unknown topic "${block.topic}"]`,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (target.status === 'unknown-section') {
|
|
104
|
+
content.push({
|
|
105
|
+
type: 'prose',
|
|
106
|
+
text: `[token-ref: section "${block.section}" not found in "${block.topic}"]`,
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// A copy per reference: two references to one section share nothing.
|
|
111
|
+
for (const refBlock of target.content) {
|
|
112
|
+
content.push(structuredClone(refBlock));
|
|
113
|
+
}
|
|
114
|
+
if (target.previewType && !section.previewType) {
|
|
115
|
+
previewType = target.previewType;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Assigning `content` keeps it where the section put it; a carried preview
|
|
119
|
+
// type lands after the section's own keys, as it always has.
|
|
120
|
+
const view = structuredClone(
|
|
121
|
+
previewType == null
|
|
122
|
+
? {...section, content: []}
|
|
123
|
+
: {...section, previewType, content: []},
|
|
124
|
+
);
|
|
125
|
+
view.content = content;
|
|
126
|
+
return withSourceTitle(view, authoredTitle(node, section));
|
|
127
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize an npm package name into a ProviderId.
|
|
6
|
+
* @param {string} packageName
|
|
7
|
+
* @returns {ProviderId}
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeProviderId(packageName: string): ProviderId;
|
|
10
|
+
/**
|
|
11
|
+
* Normalize a SHA-256 digest.
|
|
12
|
+
* @param {string} digest
|
|
13
|
+
* @returns {ContentDigest}
|
|
14
|
+
*/
|
|
15
|
+
export function normalizeContentDigest(digest: string): ContentDigest;
|
|
16
|
+
/**
|
|
17
|
+
* Build a stable provider + contribution kind + artifact-name ID.
|
|
18
|
+
* @param {string} provider
|
|
19
|
+
* @param {ContributionKind} kind
|
|
20
|
+
* @param {string} name
|
|
21
|
+
* @returns {ArtifactId}
|
|
22
|
+
*/
|
|
23
|
+
export function createArtifactId(provider: string, kind: ContributionKind, name: string): ArtifactId;
|
|
24
|
+
/**
|
|
25
|
+
* Build a stable document ID from provider + authored kind + stable name.
|
|
26
|
+
* @param {string} provider
|
|
27
|
+
* @param {AuthoredDocKind} kind
|
|
28
|
+
* @param {string} name
|
|
29
|
+
* @returns {DocId}
|
|
30
|
+
*/
|
|
31
|
+
export function createDocId(provider: string, kind: AuthoredDocKind, name: string): DocId;
|
|
32
|
+
/**
|
|
33
|
+
* Build a normalized logical artifact record.
|
|
34
|
+
* @param {string} provider
|
|
35
|
+
* @param {ContributionKind} kind
|
|
36
|
+
* @param {string} name
|
|
37
|
+
* @returns {ArtifactIdentity}
|
|
38
|
+
*/
|
|
39
|
+
export function createArtifactIdentity(provider: string, kind: ContributionKind, name: string): ArtifactIdentity;
|
|
40
|
+
/**
|
|
41
|
+
* Parse and canonicalize an ArtifactId.
|
|
42
|
+
* @param {string} value
|
|
43
|
+
* @returns {ArtifactIdentity}
|
|
44
|
+
*/
|
|
45
|
+
export function parseArtifactId(value: string): ArtifactIdentity;
|
|
46
|
+
/**
|
|
47
|
+
* Build one immutable provider instance.
|
|
48
|
+
* @param {{providerId: string, packageName: string, packageVersion: string, sourceDigest: string}} input
|
|
49
|
+
* @returns {ProviderInstance}
|
|
50
|
+
*/
|
|
51
|
+
export function createProviderInstance(input: {
|
|
52
|
+
providerId: string;
|
|
53
|
+
packageName: string;
|
|
54
|
+
packageVersion: string;
|
|
55
|
+
sourceDigest: string;
|
|
56
|
+
}): ProviderInstance;
|
|
57
|
+
/**
|
|
58
|
+
* Bind one validated authored document to immutable provider and source
|
|
59
|
+
* provenance. Runtime lifecycle state is intentionally absent.
|
|
60
|
+
* @param {{
|
|
61
|
+
* provider: ProviderInstance,
|
|
62
|
+
* kind: AuthoredDocKind,
|
|
63
|
+
* stableName: string,
|
|
64
|
+
* source: {group: string, path: string, digest: string},
|
|
65
|
+
* authored: AuthoredDoc,
|
|
66
|
+
* }} input
|
|
67
|
+
* @returns {AuthoredDocEntry}
|
|
68
|
+
*/
|
|
69
|
+
export function createAuthoredDocEntry(input: {
|
|
70
|
+
provider: ProviderInstance;
|
|
71
|
+
kind: AuthoredDocKind;
|
|
72
|
+
stableName: string;
|
|
73
|
+
source: {
|
|
74
|
+
group: string;
|
|
75
|
+
path: string;
|
|
76
|
+
digest: string;
|
|
77
|
+
};
|
|
78
|
+
authored: AuthoredDoc;
|
|
79
|
+
}): AuthoredDocEntry;
|
|
80
|
+
export type ArtifactId = import("../../authoring/identity/type").ArtifactId;
|
|
81
|
+
export type ArtifactIdentity = import("../../authoring/identity/type").ArtifactIdentity;
|
|
82
|
+
export type AuthoredDoc = import("../../authoring/identity/type").AuthoredDoc;
|
|
83
|
+
export type AuthoredDocEntry = import("../../authoring/identity/type").AuthoredDocEntry;
|
|
84
|
+
export type AuthoredDocKind = import("../../authoring/doctypes/base/type").AuthoredDocKind;
|
|
85
|
+
export type ContentDigest = import("../../authoring/identity/type").ContentDigest;
|
|
86
|
+
export type ContributionKind = import("../../authoring/identity/type").ContributionKind;
|
|
87
|
+
export type DocId = import("../../authoring/identity/type").DocId;
|
|
88
|
+
export type ProviderId = import("../../authoring/identity/type").ProviderId;
|
|
89
|
+
export type ProviderInstance = import("../../authoring/identity/type").ProviderInstance;
|
|
90
|
+
export type ProviderInstanceId = import("../../authoring/identity/type").ProviderInstanceId;
|