@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
|
@@ -31,7 +31,9 @@ function topic(fields) {
|
|
|
31
31
|
name: 'deploying',
|
|
32
32
|
title: 'Deploying',
|
|
33
33
|
description: 'How to ship it.',
|
|
34
|
-
sections: [
|
|
34
|
+
sections: [
|
|
35
|
+
{title: 'Overview', content: [{type: 'prose', text: 'Ship it.'}]},
|
|
36
|
+
],
|
|
35
37
|
...fields,
|
|
36
38
|
};
|
|
37
39
|
}
|
|
@@ -97,7 +99,9 @@ describe('discoverIntegrationDocs', () => {
|
|
|
97
99
|
}),
|
|
98
100
|
);
|
|
99
101
|
expect(errors).toEqual([]);
|
|
100
|
-
expect(
|
|
102
|
+
expect(
|
|
103
|
+
records.map(r => [r.name, r.package, r.replaces, r.extendsTopic]),
|
|
104
|
+
).toEqual([
|
|
101
105
|
['deploying', '@acme/widgets', undefined, undefined],
|
|
102
106
|
['getting-started', '@acme/widgets', 'getting-started', undefined],
|
|
103
107
|
['theme-extra', '@acme/widgets', undefined, 'theme'],
|
|
@@ -126,7 +130,9 @@ describe('discoverIntegrationDocs', () => {
|
|
|
126
130
|
|
|
127
131
|
it('reports a doc that exports nothing, rather than skipping it silently', async () => {
|
|
128
132
|
const {records, errors} = await discoverIntegrationDocs(
|
|
129
|
-
integration('@acme/empty', {
|
|
133
|
+
integration('@acme/empty', {
|
|
134
|
+
'deploying.doc.mjs': 'export const nope = 1;\n',
|
|
135
|
+
}),
|
|
130
136
|
);
|
|
131
137
|
expect(records).toEqual([]);
|
|
132
138
|
expect(errors).toHaveLength(1);
|
|
@@ -153,6 +159,49 @@ describe('discoverIntegrationDocs', () => {
|
|
|
153
159
|
expect(records).toEqual([]);
|
|
154
160
|
expect(errors[0].message).toContain('declares both');
|
|
155
161
|
});
|
|
162
|
+
|
|
163
|
+
it('rejects graph blocks at the legacy topic-reader boundary', async () => {
|
|
164
|
+
const {records, errors} = await discoverIntegrationDocs(
|
|
165
|
+
integration('@acme/graph-block', {
|
|
166
|
+
'deploying.doc.mjs': topic({
|
|
167
|
+
sections: [
|
|
168
|
+
{
|
|
169
|
+
id: 'steps',
|
|
170
|
+
title: 'Steps',
|
|
171
|
+
content: [
|
|
172
|
+
{
|
|
173
|
+
type: 'workflow',
|
|
174
|
+
steps: [{title: 'Install', description: 'Run install.'}],
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
}),
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
expect(records).toEqual([]);
|
|
183
|
+
expect(errors[0].message).toContain('requires the compiled graph renderer');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('names a namespace doc instead of listing topic fields it lacks', async () => {
|
|
187
|
+
const {records, errors} = await discoverIntegrationDocs(
|
|
188
|
+
integration('@acme/namespace', {
|
|
189
|
+
'guides.doc.mjs': {
|
|
190
|
+
type: 'namespace',
|
|
191
|
+
name: 'guides',
|
|
192
|
+
title: 'Guides',
|
|
193
|
+
summary: 'Every guide.',
|
|
194
|
+
slots: {guides: {title: 'Guides', accepts: {kinds: ['generic']}}},
|
|
195
|
+
},
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
expect(records).toEqual([]);
|
|
199
|
+
expect(errors).toHaveLength(1);
|
|
200
|
+
expect(errors[0].message).toContain(
|
|
201
|
+
'"guides" is a namespace doc. Only the docs graph reads namespace docs',
|
|
202
|
+
);
|
|
203
|
+
expect(errors[0].message).not.toContain('sections:');
|
|
204
|
+
});
|
|
156
205
|
});
|
|
157
206
|
|
|
158
207
|
describe('problemsInTopic', () => {
|
|
@@ -170,7 +219,11 @@ describe('problemsInTopic', () => {
|
|
|
170
219
|
// A misspelled required field: the block renders nothing at all.
|
|
171
220
|
expect(
|
|
172
221
|
problemsInTopic(
|
|
173
|
-
topic({
|
|
222
|
+
topic({
|
|
223
|
+
sections: [
|
|
224
|
+
{title: 'Overview', content: [{type: 'prose', txt: 'oops'}]},
|
|
225
|
+
],
|
|
226
|
+
}),
|
|
174
227
|
),
|
|
175
228
|
).toEqual([
|
|
176
229
|
'sections[0].content[0].text: required for a prose block',
|
|
@@ -180,7 +233,12 @@ describe('problemsInTopic', () => {
|
|
|
180
233
|
expect(
|
|
181
234
|
problemsInTopic(
|
|
182
235
|
topic({
|
|
183
|
-
sections: [
|
|
236
|
+
sections: [
|
|
237
|
+
{
|
|
238
|
+
title: 'Overview',
|
|
239
|
+
content: [{type: 'heading', level: 2, text: 'x'}],
|
|
240
|
+
},
|
|
241
|
+
],
|
|
184
242
|
}),
|
|
185
243
|
),
|
|
186
244
|
).toContain('sections[0].content[0].level: 2 is not one of 3, 4, 5, 6');
|
|
@@ -196,11 +254,27 @@ describe('problemsInTopic', () => {
|
|
|
196
254
|
],
|
|
197
255
|
}),
|
|
198
256
|
),
|
|
199
|
-
).toContain(
|
|
257
|
+
).toContain(
|
|
258
|
+
'sections[0].content[0].rows[0]: has 1 cells but the table has 2 headers',
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('rejects graph metadata at the legacy topic-reader boundary', () => {
|
|
263
|
+
for (const fields of [
|
|
264
|
+
{placement: {parent: 'namespace:cli'}},
|
|
265
|
+
{aliases: ['setup']},
|
|
266
|
+
{audience: 'internal'},
|
|
267
|
+
]) {
|
|
268
|
+
expect(problemsInTopic(topic(fields)).join('\n')).toContain(
|
|
269
|
+
'requires the compiled graph reader',
|
|
270
|
+
);
|
|
271
|
+
}
|
|
200
272
|
});
|
|
201
273
|
|
|
202
274
|
it('rejects a name that is not URL-safe', () => {
|
|
203
|
-
expect(problemsInTopic(topic({name: 'not a topic'})).join('\n')).toContain(
|
|
275
|
+
expect(problemsInTopic(topic({name: 'not a topic'})).join('\n')).toContain(
|
|
276
|
+
'URL-safe',
|
|
277
|
+
);
|
|
204
278
|
});
|
|
205
279
|
});
|
|
206
280
|
|
|
@@ -228,11 +302,23 @@ describe('DocsCatalog', () => {
|
|
|
228
302
|
expect(catalog.resolve('deploying').package).toBe('@acme/widgets');
|
|
229
303
|
});
|
|
230
304
|
|
|
305
|
+
it('treats topic identities case-insensitively without losing display case', () => {
|
|
306
|
+
const catalog = DocsCatalog.fromBuiltins();
|
|
307
|
+
expect(catalog.add(record({name: 'Deploying'}))).toBeNull();
|
|
308
|
+
expect(catalog.resolve('deploying').name).toBe('Deploying');
|
|
309
|
+
expect(catalog.resolve('DEPLOYING').name).toBe('Deploying');
|
|
310
|
+
|
|
311
|
+
const issue = catalog.add(
|
|
312
|
+
record({name: 'deploying', package: '@acme/duplicate'}),
|
|
313
|
+
);
|
|
314
|
+
expect(issue).toMatchObject({code: 'invalid_doc', severity: 'error'});
|
|
315
|
+
});
|
|
316
|
+
|
|
231
317
|
it('refuses to shadow an existing topic by name alone', () => {
|
|
232
318
|
const catalog = DocsCatalog.fromBuiltins({tokens: '/cli/tokens.doc.mjs'});
|
|
233
319
|
const issue = catalog.add(record({name: 'tokens'}));
|
|
234
320
|
expect(issue).toMatchObject({code: 'invalid_doc', severity: 'error'});
|
|
235
|
-
expect(issue.message).toContain(
|
|
321
|
+
expect(issue.message).toContain('already provided by @astryxdesign/cli');
|
|
236
322
|
expect(issue.message).toContain("replaces: 'tokens'");
|
|
237
323
|
// The built-in topic is untouched.
|
|
238
324
|
expect(catalog.resolve('tokens').package).toBe(BUILTIN_DOCS_PACKAGE);
|
|
@@ -244,7 +330,9 @@ describe('DocsCatalog', () => {
|
|
|
244
330
|
tokens: '/cli/tokens.doc.mjs',
|
|
245
331
|
});
|
|
246
332
|
expect(
|
|
247
|
-
catalog.add(
|
|
333
|
+
catalog.add(
|
|
334
|
+
record({name: 'getting-started', replaces: 'getting-started'}),
|
|
335
|
+
),
|
|
248
336
|
).toBeNull();
|
|
249
337
|
expect(catalog.names()).toEqual(['getting-started', 'tokens']);
|
|
250
338
|
const entry = catalog.resolve('getting-started');
|
|
@@ -276,12 +364,16 @@ describe('DocsCatalog', () => {
|
|
|
276
364
|
|
|
277
365
|
it('warns, and lets the later package win, when two replace one topic', () => {
|
|
278
366
|
const catalog = DocsCatalog.fromBuiltins({tokens: '/cli/tokens.doc.mjs'});
|
|
279
|
-
expect(
|
|
367
|
+
expect(
|
|
368
|
+
catalog.add(record({name: 'tokens', replaces: 'tokens'})),
|
|
369
|
+
).toBeNull();
|
|
280
370
|
const issue = catalog.add(
|
|
281
371
|
record({name: 'tokens', package: '@acme/later', replaces: 'tokens'}),
|
|
282
372
|
);
|
|
283
373
|
expect(issue).toMatchObject({code: 'duplicate_doc', severity: 'warning'});
|
|
284
|
-
expect(issue.message).toContain(
|
|
374
|
+
expect(issue.message).toContain(
|
|
375
|
+
'@acme/later is configured later, so it wins',
|
|
376
|
+
);
|
|
285
377
|
expect(catalog.resolve('tokens').package).toBe('@acme/later');
|
|
286
378
|
});
|
|
287
379
|
|
|
@@ -327,15 +419,189 @@ describe('mergeTopic', () => {
|
|
|
327
419
|
{title: 'Internal', content: [{type: 'prose', text: 'the meta way'}]},
|
|
328
420
|
],
|
|
329
421
|
});
|
|
330
|
-
expect(merged.sections.map(s => s.title)).toEqual([
|
|
422
|
+
expect(merged.sections.map(s => s.title)).toEqual([
|
|
423
|
+
'Install',
|
|
424
|
+
'Tokens',
|
|
425
|
+
'Internal',
|
|
426
|
+
]);
|
|
331
427
|
expect(merged.sections[0].content[0].text).toBe('yarn add');
|
|
332
428
|
expect(merged.sections[1].content[0].text).toBe('use tokens');
|
|
333
429
|
// The base is untouched.
|
|
334
430
|
expect(base.sections[0].content[0].text).toBe('npm i');
|
|
335
431
|
});
|
|
336
432
|
|
|
433
|
+
it('replaces a section by stable ID even when its title changes', () => {
|
|
434
|
+
const merged = mergeTopic(
|
|
435
|
+
{
|
|
436
|
+
...base,
|
|
437
|
+
sections: [
|
|
438
|
+
{id: 'install', title: 'Install', content: []},
|
|
439
|
+
{id: 'tokens', title: 'Tokens', content: []},
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
sections: [{id: 'install', title: 'Setup', content: []}],
|
|
444
|
+
},
|
|
445
|
+
);
|
|
446
|
+
expect(merged.sections.map(section => [section.id, section.title])).toEqual(
|
|
447
|
+
[
|
|
448
|
+
['install', 'Setup'],
|
|
449
|
+
['tokens', 'Tokens'],
|
|
450
|
+
],
|
|
451
|
+
);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('migrates a legacy section to a stable ID without duplicating it', () => {
|
|
455
|
+
const merged = mergeTopic(base, {
|
|
456
|
+
sections: [
|
|
457
|
+
{
|
|
458
|
+
id: 'install',
|
|
459
|
+
title: 'Install',
|
|
460
|
+
content: [{type: 'prose', text: 'yarn add'}],
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
});
|
|
464
|
+
expect(
|
|
465
|
+
merged.sections.map(section => [section.id ?? null, section.title]),
|
|
466
|
+
).toEqual([
|
|
467
|
+
['install', 'Install'],
|
|
468
|
+
[null, 'Tokens'],
|
|
469
|
+
]);
|
|
470
|
+
expect(merged.sections[0].content[0].text).toBe('yarn add');
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('lets a legacy extension replace a section that has since gained an ID', () => {
|
|
474
|
+
const merged = mergeTopic(
|
|
475
|
+
{
|
|
476
|
+
...base,
|
|
477
|
+
sections: [
|
|
478
|
+
{id: 'setup-steps', title: 'Install', content: []},
|
|
479
|
+
{title: 'Tokens', content: []},
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
sections: [
|
|
484
|
+
{title: 'Install', content: [{type: 'prose', text: 'yarn add'}]},
|
|
485
|
+
],
|
|
486
|
+
},
|
|
487
|
+
);
|
|
488
|
+
expect(
|
|
489
|
+
merged.sections.map(section => [section.id ?? null, section.title]),
|
|
490
|
+
).toEqual([
|
|
491
|
+
['setup-steps', 'Install'],
|
|
492
|
+
[null, 'Tokens'],
|
|
493
|
+
]);
|
|
494
|
+
expect(merged.sections[0].content[0].text).toBe('yarn add');
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('keeps two different authored IDs distinct under one title', () => {
|
|
498
|
+
const merged = mergeTopic(
|
|
499
|
+
{...base, sections: [{id: 'install', title: 'Install', content: []}]},
|
|
500
|
+
{sections: [{id: 'install-yarn', title: 'Install', content: []}]},
|
|
501
|
+
);
|
|
502
|
+
expect(merged.sections.map(section => section.id)).toEqual([
|
|
503
|
+
'install',
|
|
504
|
+
'install-yarn',
|
|
505
|
+
]);
|
|
506
|
+
});
|
|
507
|
+
|
|
337
508
|
it('takes the title and description only when the overlay states them', () => {
|
|
338
509
|
expect(mergeTopic(base, {sections: []}).title).toBe('Theme');
|
|
339
|
-
expect(mergeTopic(base, {title: 'Theming', sections: []}).title).toBe(
|
|
510
|
+
expect(mergeTopic(base, {title: 'Theming', sections: []}).title).toBe(
|
|
511
|
+
'Theming',
|
|
512
|
+
);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
describe('problemsInTopic section keys', () => {
|
|
517
|
+
/** @param {object[]} sections */
|
|
518
|
+
const doc = sections => ({
|
|
519
|
+
type: 'generic',
|
|
520
|
+
name: 'keys',
|
|
521
|
+
title: 'Keys',
|
|
522
|
+
description: 'Section keys.',
|
|
523
|
+
sections: sections.map(s => ({
|
|
524
|
+
content: [{type: 'prose', text: 'x'}],
|
|
525
|
+
...s,
|
|
526
|
+
})),
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('rejects two sections that derive the same key', () => {
|
|
530
|
+
expect(
|
|
531
|
+
problemsInTopic(doc([{title: 'Quick Start'}, {title: 'Quick-start'}])),
|
|
532
|
+
).toEqual([
|
|
533
|
+
expect.stringContaining('"quick-start" is already used by sections[0]'),
|
|
534
|
+
]);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('rejects an unsafe id', () => {
|
|
538
|
+
expect(
|
|
539
|
+
problemsInTopic(doc([{id: 'Quick Start', title: 'Quick Start'}])),
|
|
540
|
+
).toEqual([expect.stringContaining('is not a stable key')]);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('rejects a title no key derives from, unless it has an id', () => {
|
|
544
|
+
expect(problemsInTopic(doc([{title: '亮/暗模式'}]))).toEqual([
|
|
545
|
+
expect.stringContaining('Give the section an id'),
|
|
546
|
+
]);
|
|
547
|
+
expect(
|
|
548
|
+
problemsInTopic(doc([{id: 'light-dark', title: '亮/暗模式'}])),
|
|
549
|
+
).toEqual([]);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe('mergeTopic by section key', () => {
|
|
554
|
+
const base = {
|
|
555
|
+
title: 'Theme',
|
|
556
|
+
sections: [
|
|
557
|
+
{title: 'Quick Start', content: [{type: 'prose', text: 'base'}]},
|
|
558
|
+
{title: 'Light/Dark Mode', content: [{type: 'prose', text: 'base'}]},
|
|
559
|
+
],
|
|
560
|
+
};
|
|
561
|
+
const titles = doc =>
|
|
562
|
+
doc.sections.map(s => [s.id ?? null, s.title, s.content[0].text]);
|
|
563
|
+
|
|
564
|
+
it('replaces the section whose derived key an extension id names', () => {
|
|
565
|
+
const merged = mergeTopic(base, {
|
|
566
|
+
sections: [
|
|
567
|
+
{
|
|
568
|
+
id: 'quick-start',
|
|
569
|
+
title: 'Quick Start with Acme',
|
|
570
|
+
content: [{type: 'prose', text: 'acme'}],
|
|
571
|
+
},
|
|
572
|
+
],
|
|
573
|
+
});
|
|
574
|
+
expect(titles(merged)).toEqual([
|
|
575
|
+
['quick-start', 'Quick Start with Acme', 'acme'],
|
|
576
|
+
[null, 'Light/Dark Mode', 'base'],
|
|
577
|
+
]);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('replaces the section a title variant derives the same key as', () => {
|
|
581
|
+
const merged = mergeTopic(base, {
|
|
582
|
+
sections: [
|
|
583
|
+
{title: 'Light-Dark Mode', content: [{type: 'prose', text: 'acme'}]},
|
|
584
|
+
],
|
|
585
|
+
});
|
|
586
|
+
expect(titles(merged)).toEqual([
|
|
587
|
+
[null, 'Quick Start', 'base'],
|
|
588
|
+
[null, 'Light-Dark Mode', 'acme'],
|
|
589
|
+
]);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('prefers the key over a legacy title for an extension with an id', () => {
|
|
593
|
+
const merged = mergeTopic(base, {
|
|
594
|
+
sections: [
|
|
595
|
+
{
|
|
596
|
+
id: 'light-dark-mode',
|
|
597
|
+
title: 'Quick Start',
|
|
598
|
+
content: [{type: 'prose', text: 'acme'}],
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
});
|
|
602
|
+
expect(titles(merged)).toEqual([
|
|
603
|
+
[null, 'Quick Start', 'base'],
|
|
604
|
+
['light-dark-mode', 'Quick Start', 'acme'],
|
|
605
|
+
]);
|
|
340
606
|
});
|
|
341
607
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
* A payload's size as `--json` prints it: the envelope data, two-space
|
|
6
|
+
* indented, in UTF-8 bytes.
|
|
7
|
+
* @param {unknown} payload
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
export function docPayloadBytes(payload: unknown): number;
|
|
11
|
+
/**
|
|
12
|
+
* @param {import('../../api/docs/docs.type.mjs').DocsIndex} index
|
|
13
|
+
* @returns {number}
|
|
14
|
+
*/
|
|
15
|
+
export function docsIndexBytes(index: import("../../api/docs/docs.type.mjs").DocsIndex): number;
|
|
16
|
+
/**
|
|
17
|
+
* The sections a single read would return more than `budget` bytes for.
|
|
18
|
+
* @param {any[]} sections
|
|
19
|
+
* @param {number} [budget]
|
|
20
|
+
* @returns {{key: string, title: string, bytes: number}[]}
|
|
21
|
+
*/
|
|
22
|
+
export function oversizedDocSections(sections: any[], budget?: number): {
|
|
23
|
+
key: string;
|
|
24
|
+
title: string;
|
|
25
|
+
bytes: number;
|
|
26
|
+
}[];
|
|
27
|
+
/** The most one index or one section read may return, in bytes. */
|
|
28
|
+
export const DOC_OUTPUT_BUDGET_BYTES: number;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The most one progressive docs read may return.
|
|
5
|
+
*
|
|
6
|
+
* @input Docs payloads: one topic's section index, or one section.
|
|
7
|
+
* @output Their size in bytes as `--json` prints them, and the sections over
|
|
8
|
+
* the budget.
|
|
9
|
+
* @position Shared by Doctor's docs checks and the authoring self-doc audit, so
|
|
10
|
+
* both hold every read to the same limit.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {sectionKey} from './docs-section-key.mjs';
|
|
14
|
+
|
|
15
|
+
/** The most one index or one section read may return, in bytes. */
|
|
16
|
+
export const DOC_OUTPUT_BUDGET_BYTES = 32 * 1024;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A payload's size as `--json` prints it: the envelope data, two-space
|
|
20
|
+
* indented, in UTF-8 bytes.
|
|
21
|
+
* @param {unknown} payload
|
|
22
|
+
* @returns {number}
|
|
23
|
+
*/
|
|
24
|
+
export function docPayloadBytes(payload) {
|
|
25
|
+
return Buffer.byteLength(JSON.stringify(payload, null, 2), 'utf8');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {import('../../api/docs/docs.type.mjs').DocsIndex} index
|
|
30
|
+
* @returns {number}
|
|
31
|
+
*/
|
|
32
|
+
export function docsIndexBytes(index) {
|
|
33
|
+
return docPayloadBytes({type: 'docs.index', data: index});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The sections a single read would return more than `budget` bytes for.
|
|
38
|
+
* @param {any[]} sections
|
|
39
|
+
* @param {number} [budget]
|
|
40
|
+
* @returns {{key: string, title: string, bytes: number}[]}
|
|
41
|
+
*/
|
|
42
|
+
export function oversizedDocSections(sections, budget = DOC_OUTPUT_BUDGET_BYTES) {
|
|
43
|
+
return sections
|
|
44
|
+
.map(section => ({
|
|
45
|
+
key: sectionKey(section),
|
|
46
|
+
title: section.title,
|
|
47
|
+
bytes: docPayloadBytes({type: 'docs.detail.section', data: section}),
|
|
48
|
+
}))
|
|
49
|
+
.filter(entry => entry.bytes > budget);
|
|
50
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
* Record the authored title of a section whose visible title a translation
|
|
6
|
+
* overlay replaces.
|
|
7
|
+
* @template {object} T
|
|
8
|
+
* @param {T} section
|
|
9
|
+
* @param {string} title
|
|
10
|
+
* @returns {T}
|
|
11
|
+
*/
|
|
12
|
+
export function withSourceTitle<T extends object>(section: T, title: string): T;
|
|
13
|
+
/**
|
|
14
|
+
* @param {any} section
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function sourceTitle(section: any): string;
|
|
18
|
+
/**
|
|
19
|
+
* The key a title derives: accents folded, `&` spelled out, and every other
|
|
20
|
+
* run of non-alphanumerics collapsed to one hyphen. Empty when the title has
|
|
21
|
+
* no Latin letters or digits to derive from.
|
|
22
|
+
* @param {unknown} title
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function sectionTitleKey(title: unknown): string;
|
|
26
|
+
/**
|
|
27
|
+
* The key a section is addressed by: its authored `id`, else the key its
|
|
28
|
+
* authored title derives.
|
|
29
|
+
* @param {any} section
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function sectionKey(section: any): string;
|
|
33
|
+
/**
|
|
34
|
+
* Problems with the keys of a topic's sections: an authored id that is not a
|
|
35
|
+
* stable key, a title no key derives from, and two sections sharing a key.
|
|
36
|
+
* Keys are never suffixed to make them unique, because readers link to them.
|
|
37
|
+
* @param {any[]} sections
|
|
38
|
+
* @returns {string[]}
|
|
39
|
+
*/
|
|
40
|
+
export function sectionKeyProblems(sections: any[]): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Stamp every section with the key it is addressed by. Runs only after
|
|
43
|
+
* extensions merge: a derived key must never take part in merge matching.
|
|
44
|
+
* @template {{sections: any[]}} T
|
|
45
|
+
* @param {T} doc
|
|
46
|
+
* @returns {T}
|
|
47
|
+
*/
|
|
48
|
+
export function withSectionKeys<T extends {
|
|
49
|
+
sections: any[];
|
|
50
|
+
}>(doc: T): T;
|
|
51
|
+
/**
|
|
52
|
+
* Find the section a reader asked for: by key, then by exact title (or the
|
|
53
|
+
* key the query derives), then by a title that contains the query. More than
|
|
54
|
+
* one match is refused rather than guessed.
|
|
55
|
+
* @param {any[]} sections
|
|
56
|
+
* @param {string} query
|
|
57
|
+
* @returns {{section: any | null, candidates: any[]}} `candidates` lists the
|
|
58
|
+
* matches when the query is ambiguous, and is empty when nothing matches
|
|
59
|
+
*/
|
|
60
|
+
export function findDocSection(sections: any[], query: string): {
|
|
61
|
+
section: any | null;
|
|
62
|
+
candidates: any[];
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* One line that says what a section holds: its first prose or list text,
|
|
66
|
+
* whitespace collapsed, cut at a word boundary.
|
|
67
|
+
* @param {any} section
|
|
68
|
+
* @param {number} [max]
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
export function sectionSummary(section: any, max?: number): string;
|
|
72
|
+
/**
|
|
73
|
+
* The index a topic-only read returns: what the topic is, and one entry per
|
|
74
|
+
* section with the key to read it by.
|
|
75
|
+
* @param {{name: string, title: string, description: string, sections: any[]}} doc
|
|
76
|
+
* @returns {import('../../api/docs/docs.type.mjs').DocsIndex}
|
|
77
|
+
*/
|
|
78
|
+
export function buildDocsIndexData(doc: {
|
|
79
|
+
name: string;
|
|
80
|
+
title: string;
|
|
81
|
+
description: string;
|
|
82
|
+
sections: any[];
|
|
83
|
+
}): import("../../api/docs/docs.type.mjs").DocsIndex;
|
|
84
|
+
/**
|
|
85
|
+
* @file Stable section keys, section lookup, and the topic index.
|
|
86
|
+
*
|
|
87
|
+
* @input Reference-doc sections, each with an optional authored `id`.
|
|
88
|
+
* @output The key a section is addressed by (its `id`, else a kebab-case key
|
|
89
|
+
* derived from its authored title), lookup by key or title, and the compact
|
|
90
|
+
* index a topic-only docs read returns.
|
|
91
|
+
* @position Shared by docs discovery (which rejects colliding keys before a
|
|
92
|
+
* reader sees them), the docs leaves (index, section, detail), and Doctor
|
|
93
|
+
* (output budgets). Imports nothing from discovery, so both can use it.
|
|
94
|
+
*/
|
|
95
|
+
/** A stable key: lowercase letters and digits, joined by single hyphens. */
|
|
96
|
+
export const SECTION_KEY_RE: RegExp;
|
|
97
|
+
/** The longest summary an index entry carries, in characters. */
|
|
98
|
+
export const SECTION_SUMMARY_MAX: 240;
|