@astryxdesign/cli 0.6.3-canary.f22695a → 0.6.3
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 +1 -2
- 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 +24 -37
- package/api/docs/_adapter.mjs +83 -169
- package/api/docs/detail/detail.mjs +63 -14
- package/api/docs/detail/section/section.d.mts +1 -1
- package/api/docs/detail/section/section.mjs +20 -44
- package/api/docs/detail/section/section.test.mjs +0 -41
- package/api/docs/docs.d.mts +2 -7
- package/api/docs/docs.doc.mjs +10 -27
- package/api/docs/docs.mjs +9 -16
- package/api/docs/docs.test.mjs +0 -6
- package/api/docs/docs.type.d.mts +3 -40
- package/api/docs/docs.type.mjs +8 -36
- package/api/docs/integrationDocs.test.mjs +0 -106
- package/api/doctor/doctor.d.mts +0 -48
- package/api/doctor/doctor.mjs +0 -232
- package/api/doctor/doctor.test.mjs +0 -196
- 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 +3 -5
- 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 +7 -49
- package/api/integration/pack-check.test.mjs +0 -249
- 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 +6 -20
- package/api/theme/build/build.test.mjs +0 -127
- package/api/theme/palette/generate/generate.mjs +1 -1
- package/api/theme/palette/generate/generator.d.mts +13 -10
- package/api/theme/palette/generate/generator.mjs +3 -7
- package/api/theme/theme.type.d.mts +11 -170
- package/api/theme/theme.type.mjs +27 -94
- package/api/upgrade/_adapter.mjs +5 -71
- package/api/upgrade/upgrade.doc.mjs +3 -4
- package/api/upgrade/upgrade.type.d.mts +5 -5
- package/api/upgrade/upgrade.type.mjs +11 -11
- package/assets/codemods/integration-discovery.mjs +2 -40
- package/assets/codemods/integration-discovery.test.mjs +0 -58
- package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +5 -27
- package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +5 -20
- package/assets/docs/README.md +0 -9
- package/assets/docs/cli-integrations.doc.mjs +15 -86
- package/assets/docs/styling-libraries.doc.mjs +1 -1
- package/assets/docs/working-with-ai.doc.mjs +1 -1
- package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +3 -19
- package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +65 -383
- package/authoring/_shared/contract.ts +0 -22
- package/authoring/codemod/codemod.doc.mjs +1 -6
- package/authoring/codemod/parse.d.mts +8 -8
- package/authoring/codemod/parse.mjs +6 -8
- 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 +23 -788
- package/authoring/doctypes/_schema.mjs +39 -492
- package/authoring/doctypes/base/type.ts +0 -40
- package/authoring/doctypes/command/command.doc.mjs +2 -3
- package/authoring/doctypes/command/parse.d.mts +2 -2
- package/authoring/doctypes/command/parse.mjs +1 -1
- package/authoring/doctypes/command/type.ts +2 -3
- package/authoring/doctypes/component/component.doc.mjs +3 -6
- package/authoring/doctypes/component/parse.d.mts +2 -2
- package/authoring/doctypes/component/parse.mjs +1 -1
- package/authoring/doctypes/component/type.ts +3 -4
- package/authoring/doctypes/enum/parse.d.mts +2 -2
- package/authoring/doctypes/enum/parse.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +1 -3
- package/authoring/doctypes/function/function.doc.mjs +0 -4
- package/authoring/doctypes/function/parse.d.mts +2 -2
- package/authoring/doctypes/function/parse.mjs +1 -1
- package/authoring/doctypes/function/type.ts +2 -6
- package/authoring/doctypes/hook/hook.doc.mjs +0 -4
- package/authoring/doctypes/hook/parse.d.mts +2 -2
- package/authoring/doctypes/hook/parse.mjs +1 -1
- package/authoring/doctypes/hook/type.ts +2 -3
- package/authoring/doctypes/legacy.d.mts +6 -8
- package/authoring/doctypes/legacy.mjs +4 -5
- package/authoring/doctypes/parse.d.mts +18 -20
- package/authoring/doctypes/parse.mjs +10 -16
- package/authoring/doctypes/parse.test.mjs +3 -77
- package/authoring/doctypes/reference/parse.d.mts +2 -2
- package/authoring/doctypes/reference/parse.mjs +5 -8
- package/authoring/doctypes/reference/reference.doc.mjs +4 -17
- package/authoring/doctypes/reference/type.ts +5 -51
- package/authoring/doctypes/schema/parse.d.mts +2 -2
- package/authoring/doctypes/schema/parse.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +2 -3
- package/authoring/doctypes/template/parse.d.mts +1 -92
- package/authoring/doctypes/template/parse.mjs +2 -36
- package/authoring/doctypes/template/parse.test.mjs +2 -8
- package/authoring/doctypes/template/template.doc.mjs +0 -4
- package/authoring/doctypes/template/type.ts +2 -5
- package/authoring/doctypes/types.ts +9 -10
- 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/index.d.mts +0 -1
- package/authoring/index.d.ts +17 -49
- package/authoring/index.mjs +0 -1
- package/authoring/integration/integration.doc.mjs +6 -13
- package/authoring/integration/parse.d.mts +2 -2
- package/authoring/integration/parse.mjs +1 -1
- package/authoring/integration/parse.test.mjs +1 -10
- package/authoring/integration/schema.d.mts +4 -6
- package/authoring/integration/schema.mjs +3 -9
- package/authoring/integration/type.ts +6 -23
- package/authoring/shadcn/receipt.d.mts +6 -6
- package/clients/cli/commands/docs.doc.mjs +3 -13
- package/clients/cli/commands/docs.mjs +21 -121
- package/clients/cli/commands/docs.test.mjs +0 -88
- package/clients/cli/commands/integration-authoring.test.mjs +9 -13
- package/clients/cli/commands/theme-palette-generate.doc.mjs +4 -8
- package/clients/cli/commands/upgrade.doc.mjs +2 -2
- package/clients/cli/formatters/index.mjs +1 -162
- package/clients/cli/formatters/index.test.mjs +0 -91
- package/clients/cli/lib/manifest.mjs +2 -7
- package/foundation/config/project.mjs +6 -21
- package/foundation/discovery/component-discovery.d.mts +1 -1
- package/foundation/discovery/component-discovery.mjs +1 -2
- package/foundation/discovery/docs-discovery.d.mts +4 -11
- package/foundation/discovery/docs-discovery.mjs +88 -208
- package/foundation/discovery/docs-discovery.test.mjs +13 -279
- package/foundation/discovery/template-adapter.mjs +1 -2
- package/foundation/integrations/autolink.mjs +5 -12
- package/foundation/integrations/integration-warnings.mjs +0 -6
- package/foundation/integrations/integrations.d.mts +2 -46
- package/foundation/integrations/integrations.mjs +8 -167
- package/foundation/integrations/integrations.test.mjs +1 -384
- package/foundation/integrations/validate-contributions.d.mts +0 -2
- package/foundation/integrations/validate-contributions.mjs +0 -10
- package/foundation/response/json-contract.test.mjs +17 -46
- package/foundation/response/response-types.doc.mjs +1 -6
- package/package.json +11 -9
- package/api/docs/compiled-topics.test.mjs +0 -78
- package/api/docs/index/index.d.mts +0 -18
- package/api/docs/index/index.mjs +0 -32
- package/api/docs/index/index.test.mjs +0 -62
- package/api/upgrade/project-context.test.mjs +0 -272
- package/assets/docs/authoring.doc.mjs +0 -14
- package/assets/templates/blocks/components/Timer/TimerFormats.doc.mjs +0 -14
- package/assets/templates/blocks/components/Timer/TimerFormats.tsx +0 -34
- package/assets/templates/blocks/components/Timer/TimerInline.doc.mjs +0 -14
- package/assets/templates/blocks/components/Timer/TimerInline.tsx +0 -14
- package/assets/templates/blocks/components/Timer/TimerShowcase.doc.mjs +0 -13
- package/assets/templates/blocks/components/Timer/TimerShowcase.tsx +0 -47
- package/assets/templates/blocks/components/Timer/TimerTypography.doc.mjs +0 -14
- package/assets/templates/blocks/components/Timer/TimerTypography.tsx +0 -31
- package/authoring/doctypes/base/graph-fields.doc.d.mts +0 -9
- package/authoring/doctypes/base/graph-fields.doc.mjs +0 -62
- package/authoring/doctypes/load-contract.test.mjs +0 -207
- package/authoring/doctypes/namespace/namespace.doc.d.mts +0 -9
- package/authoring/doctypes/namespace/namespace.doc.mjs +0 -132
- package/authoring/doctypes/namespace/parse.d.mts +0 -12
- package/authoring/doctypes/namespace/parse.mjs +0 -25
- package/authoring/doctypes/namespace/parse.test.mjs +0 -165
- package/authoring/doctypes/namespace/type.ts +0 -71
- package/authoring/identity/identity.doc.d.mts +0 -9
- package/authoring/identity/identity.doc.mjs +0 -61
- package/authoring/identity/type.ts +0 -132
- package/foundation/discovery/authoring-self-docs.d.mts +0 -69
- package/foundation/discovery/authoring-self-docs.mjs +0 -214
- package/foundation/discovery/authoring-self-docs.test.mjs +0 -154
- package/foundation/discovery/docs-output-budget.d.mts +0 -28
- package/foundation/discovery/docs-output-budget.mjs +0 -50
- package/foundation/discovery/docs-section-key.d.mts +0 -98
- package/foundation/discovery/docs-section-key.mjs +0 -221
- package/foundation/discovery/docs-section-key.test.mjs +0 -224
- package/foundation/doc-compiler/compile.d.mts +0 -162
- package/foundation/doc-compiler/compile.mjs +0 -262
- package/foundation/doc-compiler/doc-compiler.test.mjs +0 -687
- package/foundation/doc-compiler/ir.d.mts +0 -9
- package/foundation/doc-compiler/ir.mjs +0 -287
- package/foundation/doc-compiler/lenses.d.mts +0 -33
- package/foundation/doc-compiler/lenses.mjs +0 -127
- package/foundation/identity/provider-identity.d.mts +0 -90
- package/foundation/identity/provider-identity.mjs +0 -320
- package/foundation/identity/provider-identity.test.mjs +0 -254
- package/foundation/identity/providers.d.mts +0 -7
- package/foundation/identity/providers.mjs +0 -16
- package/foundation/integrations/provider-conflicts.test.mjs +0 -125
|
@@ -33,16 +33,7 @@ import * as fs from 'node:fs';
|
|
|
33
33
|
import * as path from 'node:path';
|
|
34
34
|
import {CLI_ROOT} from '../fs/paths.mjs';
|
|
35
35
|
import {importUserModule} from '../fs/module-loader.mjs';
|
|
36
|
-
import {CLI_PROVIDER_ID} from '../identity/providers.mjs';
|
|
37
36
|
import {parseDoc} from '../../authoring/doctypes/parse.mjs';
|
|
38
|
-
import {
|
|
39
|
-
sectionKey,
|
|
40
|
-
sectionKeyProblems,
|
|
41
|
-
sourceTitle,
|
|
42
|
-
withSourceTitle,
|
|
43
|
-
} from './docs-section-key.mjs';
|
|
44
|
-
|
|
45
|
-
export {withSourceTitle};
|
|
46
37
|
|
|
47
38
|
/** Where the CLI's own topics live. */
|
|
48
39
|
const BUILTIN_DOCS_DIR = path.join(CLI_ROOT, 'assets', 'docs');
|
|
@@ -52,7 +43,7 @@ const BUILTIN_DOCS_DIR = path.join(CLI_ROOT, 'assets', 'docs');
|
|
|
52
43
|
* (assets/docs), not in @astryxdesign/core, so this is the CLI's own name —
|
|
53
44
|
* unlike component discovery, whose built-ins belong to core.
|
|
54
45
|
*/
|
|
55
|
-
export const BUILTIN_DOCS_PACKAGE =
|
|
46
|
+
export const BUILTIN_DOCS_PACKAGE = '@astryxdesign/cli';
|
|
56
47
|
|
|
57
48
|
/**
|
|
58
49
|
* A built-in topic file: `{topic}.doc.mjs`. Anchored at both ends so a
|
|
@@ -137,25 +128,12 @@ const BLOCK_FIELDS = {
|
|
|
137
128
|
'token-ref': ['topic', 'section'],
|
|
138
129
|
};
|
|
139
130
|
|
|
140
|
-
/** Blocks that are valid authoring but require the compiled graph renderer. */
|
|
141
|
-
export const GRAPH_BLOCK_TYPES = new Set([
|
|
142
|
-
'workflow',
|
|
143
|
-
'collection',
|
|
144
|
-
'reference',
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
/** Doc fields only the docs graph reads; a topic that sets one fails to load. */
|
|
148
|
-
export const GRAPH_ONLY_FIELDS = ['placement', 'aliases', 'audience'];
|
|
149
|
-
|
|
150
131
|
/**
|
|
151
132
|
* Fields a block kind may carry but does not need. Kept per kind rather than
|
|
152
133
|
* globally: only a code block renders a `label`, so allowing it everywhere
|
|
153
134
|
* would wave through the misspellings this check exists to catch.
|
|
154
135
|
*/
|
|
155
|
-
|
|
156
|
-
const OPTIONAL_BLOCK_FIELDS = {
|
|
157
|
-
code: ['label'],
|
|
158
|
-
};
|
|
136
|
+
const OPTIONAL_BLOCK_FIELDS = {code: ['label']};
|
|
159
137
|
|
|
160
138
|
/**
|
|
161
139
|
* Fields whose value has to be one of a set, because the renderer indexes on
|
|
@@ -169,7 +147,7 @@ const BLOCK_FIELD_VALUES = {
|
|
|
169
147
|
};
|
|
170
148
|
|
|
171
149
|
/** Keys a section may carry. */
|
|
172
|
-
const SECTION_FIELDS = ['
|
|
150
|
+
const SECTION_FIELDS = ['title', 'category', 'content', 'previewType'];
|
|
173
151
|
|
|
174
152
|
/**
|
|
175
153
|
* Check the fields the docs surfaces actually read. `parseDoc` is the outer
|
|
@@ -183,13 +161,6 @@ const SECTION_FIELDS = ['id', 'title', 'category', 'content', 'previewType'];
|
|
|
183
161
|
* @returns {string[]} problems, each already pointed at a place in the doc
|
|
184
162
|
*/
|
|
185
163
|
export function problemsInTopic(doc) {
|
|
186
|
-
// A namespace doc is valid authoring that only the docs graph reads. Said
|
|
187
|
-
// plainly, instead of as the topic fields it does not have.
|
|
188
|
-
if (doc?.type === 'namespace') {
|
|
189
|
-
return [
|
|
190
|
-
`"${doc.name}" is a namespace doc. Only the docs graph reads namespace docs, and it is not built yet; remove this file from the docs directory.`,
|
|
191
|
-
];
|
|
192
|
-
}
|
|
193
164
|
/** @type {string[]} */
|
|
194
165
|
const problems = [];
|
|
195
166
|
for (const field of ['name', 'title', 'description']) {
|
|
@@ -202,119 +173,83 @@ export function problemsInTopic(doc) {
|
|
|
202
173
|
`name: "${doc.name}" is not URL-safe. A topic name is its CLI argument and its docsite path, so it may hold only letters, digits, "_" and "-".`,
|
|
203
174
|
);
|
|
204
175
|
}
|
|
205
|
-
for (const field of GRAPH_ONLY_FIELDS) {
|
|
206
|
-
if (doc?.[field] != null) {
|
|
207
|
-
problems.push(
|
|
208
|
-
`${field}: requires the compiled graph reader and is not supported by legacy topic readers`,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
176
|
if (!Array.isArray(doc?.sections) || doc.sections.length === 0) {
|
|
213
177
|
problems.push('sections: expected at least one section');
|
|
214
178
|
return problems;
|
|
215
179
|
}
|
|
216
180
|
|
|
217
|
-
doc.sections.forEach(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
181
|
+
doc.sections.forEach((/** @type {any} */ section, /** @type {number} */ s) => {
|
|
182
|
+
const at = `sections[${s}]`;
|
|
183
|
+
if (typeof section?.title !== 'string' || section.title === '') {
|
|
184
|
+
problems.push(`${at}.title: expected a non-empty string`);
|
|
185
|
+
}
|
|
186
|
+
for (const key of Object.keys(section ?? {})) {
|
|
187
|
+
if (!SECTION_FIELDS.includes(key)) {
|
|
188
|
+
problems.push(`${at}.${key}: not a field of a section`);
|
|
222
189
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
190
|
+
}
|
|
191
|
+
if (!Array.isArray(section?.content)) {
|
|
192
|
+
problems.push(`${at}.content: expected an array of blocks`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
section.content.forEach((/** @type {any} */ block, /** @type {number} */ b) => {
|
|
196
|
+
const blockAt = `${at}.content[${b}]`;
|
|
197
|
+
const fields = /** @type {Record<string, string[]>} */ (BLOCK_FIELDS)[block?.type];
|
|
198
|
+
if (fields == null) {
|
|
199
|
+
problems.push(
|
|
200
|
+
`${blockAt}.type: ${JSON.stringify(block?.type)} is not one of ${Object.keys(BLOCK_FIELDS).join(', ')}`,
|
|
201
|
+
);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
for (const field of fields) {
|
|
205
|
+
const value = block[field];
|
|
206
|
+
// Empty counts as missing, the way it does for the doc's own title: a
|
|
207
|
+
// block whose text is '' passes every other check and renders as a gap.
|
|
208
|
+
if (value == null) {
|
|
209
|
+
problems.push(`${blockAt}.${field}: required for a ${block.type} block`);
|
|
210
|
+
} else if (typeof value === 'string' && value.trim() === '') {
|
|
211
|
+
problems.push(`${blockAt}.${field}: expected a non-empty string`);
|
|
212
|
+
} else if (Array.isArray(value) && value.length === 0) {
|
|
213
|
+
problems.push(`${blockAt}.${field}: expected a non-empty array`);
|
|
226
214
|
}
|
|
227
215
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
216
|
+
const allowedValues =
|
|
217
|
+
/** @type {Record<string, Record<string, unknown[]>>} */ (BLOCK_FIELD_VALUES)[block.type] ?? {};
|
|
218
|
+
for (const [field, values] of Object.entries(allowedValues)) {
|
|
219
|
+
const value = block[field];
|
|
220
|
+
if (value != null && !values.includes(value)) {
|
|
221
|
+
problems.push(
|
|
222
|
+
`${blockAt}.${field}: ${JSON.stringify(value)} is not one of ${values.join(', ')}`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
231
225
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
`${blockAt}.type: ${JSON.stringify(block.type)} requires the compiled graph renderer and is not supported by legacy topic readers`,
|
|
242
|
-
);
|
|
243
|
-
} else {
|
|
244
|
-
problems.push(
|
|
245
|
-
`${blockAt}.type: ${JSON.stringify(block?.type)} is not one of ${Object.keys(BLOCK_FIELDS).join(', ')}`,
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
for (const field of fields) {
|
|
251
|
-
const value = block[field];
|
|
252
|
-
// Empty counts as missing, the way it does for the doc's own title: a
|
|
253
|
-
// block whose text is '' passes every other check and renders as a gap.
|
|
254
|
-
if (value == null) {
|
|
255
|
-
problems.push(
|
|
256
|
-
`${blockAt}.${field}: required for a ${block.type} block`,
|
|
257
|
-
);
|
|
258
|
-
} else if (typeof value === 'string' && value.trim() === '') {
|
|
259
|
-
problems.push(`${blockAt}.${field}: expected a non-empty string`);
|
|
260
|
-
} else if (Array.isArray(value) && value.length === 0) {
|
|
261
|
-
problems.push(`${blockAt}.${field}: expected a non-empty array`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const allowedValues =
|
|
265
|
-
/** @type {Record<string, Record<string, unknown[]>>} */ (
|
|
266
|
-
BLOCK_FIELD_VALUES
|
|
267
|
-
)[block.type] ?? {};
|
|
268
|
-
for (const [field, values] of Object.entries(allowedValues)) {
|
|
269
|
-
const value = block[field];
|
|
270
|
-
if (value != null && !values.includes(value)) {
|
|
271
|
-
problems.push(
|
|
272
|
-
`${blockAt}.${field}: ${JSON.stringify(value)} is not one of ${values.join(', ')}`,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
// A table's cells are read by column index, so a short row renders blank
|
|
277
|
-
// cells and a long one drops its tail — both silently.
|
|
278
|
-
if (
|
|
279
|
-
block.type === 'table' &&
|
|
280
|
-
Array.isArray(block.headers) &&
|
|
281
|
-
Array.isArray(block.rows)
|
|
282
|
-
) {
|
|
283
|
-
block.rows.forEach(
|
|
284
|
-
(/** @type {any} */ row, /** @type {number} */ r) => {
|
|
285
|
-
if (!Array.isArray(row)) {
|
|
286
|
-
problems.push(
|
|
287
|
-
`${blockAt}.rows[${r}]: expected an array of cells`,
|
|
288
|
-
);
|
|
289
|
-
} else if (row.length !== block.headers.length) {
|
|
290
|
-
problems.push(
|
|
291
|
-
`${blockAt}.rows[${r}]: has ${row.length} cells but the table has ${block.headers.length} headers`,
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
},
|
|
226
|
+
// A table's cells are read by column index, so a short row renders blank
|
|
227
|
+
// cells and a long one drops its tail — both silently.
|
|
228
|
+
if (block.type === 'table' && Array.isArray(block.headers) && Array.isArray(block.rows)) {
|
|
229
|
+
block.rows.forEach((/** @type {any} */ row, /** @type {number} */ r) => {
|
|
230
|
+
if (!Array.isArray(row)) {
|
|
231
|
+
problems.push(`${blockAt}.rows[${r}]: expected an array of cells`);
|
|
232
|
+
} else if (row.length !== block.headers.length) {
|
|
233
|
+
problems.push(
|
|
234
|
+
`${blockAt}.rows[${r}]: has ${row.length} cells but the table has ${block.headers.length} headers`,
|
|
295
235
|
);
|
|
296
236
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
},
|
|
314
|
-
);
|
|
315
|
-
// Readers address a section by its key, so two sections sharing one would
|
|
316
|
-
// make one of them unreachable.
|
|
317
|
-
problems.push(...sectionKeyProblems(doc.sections));
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// An unknown key is almost always a misspelled required one, and it
|
|
240
|
+
// would otherwise reach a reader as a block that renders nothing.
|
|
241
|
+
const allowed = [
|
|
242
|
+
'type',
|
|
243
|
+
...fields,
|
|
244
|
+
...(/** @type {Record<string, string[]>} */ (OPTIONAL_BLOCK_FIELDS)[block.type] ?? []),
|
|
245
|
+
];
|
|
246
|
+
for (const key of Object.keys(block)) {
|
|
247
|
+
if (!allowed.includes(key)) {
|
|
248
|
+
problems.push(`${blockAt}.${key}: not a field of a ${block.type} block`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
});
|
|
318
253
|
return problems;
|
|
319
254
|
}
|
|
320
255
|
|
|
@@ -348,9 +283,7 @@ export async function discoverIntegrationDocs(integration) {
|
|
|
348
283
|
const full = path.join(dirPath, entry.name);
|
|
349
284
|
if (entry.isDirectory()) {
|
|
350
285
|
scanDir(full);
|
|
351
|
-
} else if (
|
|
352
|
-
INTEGRATION_DOC_SUFFIXES.some(suffix => entry.name.endsWith(suffix))
|
|
353
|
-
) {
|
|
286
|
+
} else if (INTEGRATION_DOC_SUFFIXES.some(suffix => entry.name.endsWith(suffix))) {
|
|
354
287
|
files.push(full);
|
|
355
288
|
}
|
|
356
289
|
}
|
|
@@ -365,11 +298,7 @@ export async function discoverIntegrationDocs(integration) {
|
|
|
365
298
|
try {
|
|
366
299
|
doc = parseDoc(await loadTopicModule(file), path.basename(file));
|
|
367
300
|
} catch (err) {
|
|
368
|
-
errors.push(
|
|
369
|
-
new Error(
|
|
370
|
-
`${path.relative(docsDir, file)}: ${/** @type {any} */ (err).message}`,
|
|
371
|
-
),
|
|
372
|
-
);
|
|
301
|
+
errors.push(new Error(`${path.relative(docsDir, file)}: ${/** @type {any} */ (err).message}`));
|
|
373
302
|
continue;
|
|
374
303
|
}
|
|
375
304
|
const problems = problemsInTopic(doc);
|
|
@@ -386,8 +315,7 @@ export async function discoverIntegrationDocs(integration) {
|
|
|
386
315
|
const parsed = /** @type {any} */ (doc);
|
|
387
316
|
// Two files claiming one name would collapse into a single entry, and the
|
|
388
317
|
// one that lost would never be reachable. Named here, where both files are.
|
|
389
|
-
const
|
|
390
|
-
const previous = seen.get(topicKey);
|
|
318
|
+
const previous = seen.get(parsed.name);
|
|
391
319
|
if (previous) {
|
|
392
320
|
errors.push(
|
|
393
321
|
new Error(
|
|
@@ -396,7 +324,7 @@ export async function discoverIntegrationDocs(integration) {
|
|
|
396
324
|
);
|
|
397
325
|
continue;
|
|
398
326
|
}
|
|
399
|
-
seen.set(
|
|
327
|
+
seen.set(parsed.name, path.relative(docsDir, file));
|
|
400
328
|
if (parsed.replaces != null && parsed.extends != null) {
|
|
401
329
|
errors.push(
|
|
402
330
|
new Error(
|
|
@@ -421,10 +349,9 @@ export async function discoverIntegrationDocs(integration) {
|
|
|
421
349
|
}
|
|
422
350
|
|
|
423
351
|
/**
|
|
424
|
-
* Merge an extension onto a base topic: a section
|
|
425
|
-
* the base section
|
|
426
|
-
* title
|
|
427
|
-
* are taken from the extension when it states them.
|
|
352
|
+
* Merge an extension onto a base topic: a section whose title matches one in
|
|
353
|
+
* the base replaces it, a section the base does not have is appended, and the
|
|
354
|
+
* title/description are taken from the extension when it states them.
|
|
428
355
|
*
|
|
429
356
|
* Keyed by section TITLE rather than by position, the way the localization
|
|
430
357
|
* overlays are — position keying grafts an overlay onto whichever section
|
|
@@ -438,18 +365,9 @@ export async function discoverIntegrationDocs(integration) {
|
|
|
438
365
|
export function mergeTopic(base, overlay) {
|
|
439
366
|
const sections = [...(base.sections ?? [])];
|
|
440
367
|
for (const section of overlay.sections ?? []) {
|
|
441
|
-
const at =
|
|
442
|
-
if (at === -1)
|
|
443
|
-
|
|
444
|
-
} else {
|
|
445
|
-
// A legacy extension that replaces a section which has since gained a
|
|
446
|
-
// stable ID keeps that ID, so readers addressing it keep working.
|
|
447
|
-
const replaced = sections[at];
|
|
448
|
-
sections[at] =
|
|
449
|
-
section.id == null && replaced.id != null
|
|
450
|
-
? withSourceTitle({...section, id: replaced.id}, sourceTitle(section))
|
|
451
|
-
: section;
|
|
452
|
-
}
|
|
368
|
+
const at = sections.findIndex((/** @type {any} */ s) => s.title === section.title);
|
|
369
|
+
if (at === -1) sections.push(section);
|
|
370
|
+
else sections[at] = section;
|
|
453
371
|
}
|
|
454
372
|
return {
|
|
455
373
|
...base,
|
|
@@ -459,41 +377,6 @@ export function mergeTopic(base, overlay) {
|
|
|
459
377
|
};
|
|
460
378
|
}
|
|
461
379
|
|
|
462
|
-
/**
|
|
463
|
-
* The base section an extension section replaces. A stable ID matches first.
|
|
464
|
-
* Otherwise the exact title matches when at least one side has no ID: the
|
|
465
|
-
* migration window in which the base or the extension adopts stable IDs
|
|
466
|
-
* before the other does. Two different authored IDs stay distinct even under
|
|
467
|
-
* one title.
|
|
468
|
-
*
|
|
469
|
-
* @param {any[]} sections
|
|
470
|
-
* @param {any} section
|
|
471
|
-
* @returns {number}
|
|
472
|
-
*/
|
|
473
|
-
function findMergeTarget(sections, section) {
|
|
474
|
-
const title = sourceTitle(section);
|
|
475
|
-
const key = sectionKey(section);
|
|
476
|
-
// A section is addressed by its key: an authored id, or the key its title
|
|
477
|
-
// derives, which is the key the topic's index shows. Matching on it means an
|
|
478
|
-
// extension never appends a second section under a key already in use.
|
|
479
|
-
const byKey = () =>
|
|
480
|
-
sections.findIndex(candidate => sectionKey(candidate) === key);
|
|
481
|
-
const legacyTitleMatch = () =>
|
|
482
|
-
sections.findIndex(
|
|
483
|
-
candidate => candidate.id == null && sourceTitle(candidate) === title,
|
|
484
|
-
);
|
|
485
|
-
if (section.id != null) {
|
|
486
|
-
const byId = byKey();
|
|
487
|
-
return byId === -1 ? legacyTitleMatch() : byId;
|
|
488
|
-
}
|
|
489
|
-
const legacy = legacyTitleMatch();
|
|
490
|
-
if (legacy !== -1) return legacy;
|
|
491
|
-
const sameTitle = sections.findIndex(
|
|
492
|
-
candidate => sourceTitle(candidate) === title,
|
|
493
|
-
);
|
|
494
|
-
return sameTitle === -1 ? byKey() : sameTitle;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
380
|
/**
|
|
498
381
|
* Every topic a project can read, and the relationships between them.
|
|
499
382
|
*
|
|
@@ -516,7 +399,7 @@ export class DocsCatalog {
|
|
|
516
399
|
static fromBuiltins(builtins = discoverBuiltinTopics()) {
|
|
517
400
|
const catalog = new DocsCatalog();
|
|
518
401
|
for (const [name, file] of Object.entries(builtins)) {
|
|
519
|
-
catalog.#topics.set(name
|
|
402
|
+
catalog.#topics.set(name, {
|
|
520
403
|
name,
|
|
521
404
|
package: BUILTIN_DOCS_PACKAGE,
|
|
522
405
|
path: file,
|
|
@@ -572,9 +455,7 @@ export class DocsCatalog {
|
|
|
572
455
|
// The replacement takes the base topic's slot, so a reader that opens
|
|
573
456
|
// the first topic (or the nth) sees the same one it did before.
|
|
574
457
|
const replaced = target.name;
|
|
575
|
-
|
|
576
|
-
const replacementKey = record.name.toLowerCase();
|
|
577
|
-
this.#replaceAt(replacedKey, {
|
|
458
|
+
this.#replaceAt(replaced, {
|
|
578
459
|
name: record.name,
|
|
579
460
|
package: record.package,
|
|
580
461
|
path: record.path,
|
|
@@ -585,18 +466,17 @@ export class DocsCatalog {
|
|
|
585
466
|
// Extensions were authored against the content that just went away.
|
|
586
467
|
extensions: [],
|
|
587
468
|
});
|
|
588
|
-
if (
|
|
589
|
-
this.#aliases.set(
|
|
469
|
+
if (record.name !== replaced) {
|
|
470
|
+
this.#aliases.set(replaced, record.name);
|
|
590
471
|
// A topic renamed twice keeps every name it has ever answered to.
|
|
591
472
|
for (const [from, to] of this.#aliases) {
|
|
592
|
-
if (to ===
|
|
473
|
+
if (to === replaced) this.#aliases.set(from, record.name);
|
|
593
474
|
}
|
|
594
475
|
}
|
|
595
476
|
return warning;
|
|
596
477
|
}
|
|
597
478
|
|
|
598
|
-
const
|
|
599
|
-
const existing = this.#topics.get(topicKey);
|
|
479
|
+
const existing = this.#topics.get(record.name);
|
|
600
480
|
if (existing) {
|
|
601
481
|
return {
|
|
602
482
|
code: 'invalid_doc',
|
|
@@ -604,7 +484,7 @@ export class DocsCatalog {
|
|
|
604
484
|
message: `Topic "${record.name}" is already provided by ${existing.package}. Give it another name, or declare \`replaces: '${record.name}'\` to take its place.`,
|
|
605
485
|
};
|
|
606
486
|
}
|
|
607
|
-
this.#topics.set(
|
|
487
|
+
this.#topics.set(record.name, {
|
|
608
488
|
name: record.name,
|
|
609
489
|
package: record.package,
|
|
610
490
|
path: record.path,
|
|
@@ -639,7 +519,7 @@ export class DocsCatalog {
|
|
|
639
519
|
|
|
640
520
|
/** @returns {string[]} every topic name, in read order */
|
|
641
521
|
names() {
|
|
642
|
-
return [...this.#topics.
|
|
522
|
+
return [...this.#topics.keys()];
|
|
643
523
|
}
|
|
644
524
|
|
|
645
525
|
/** @returns {DocsTopicEntry[]} every topic, in read order */
|
|
@@ -656,7 +536,7 @@ export class DocsCatalog {
|
|
|
656
536
|
/** @type {Map<string, DocsTopicEntry>} */
|
|
657
537
|
const next = new Map();
|
|
658
538
|
for (const [key, value] of this.#topics) {
|
|
659
|
-
if (key === name
|
|
539
|
+
if (key === name) next.set(entry.name, entry);
|
|
660
540
|
else next.set(key, value);
|
|
661
541
|
}
|
|
662
542
|
this.#topics = next;
|