@astryxdesign/cli 0.6.3-canary.d774400 → 0.6.3-canary.d7eed85
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/api/docs/_adapter.d.mts +49 -7
- package/api/docs/_adapter.mjs +284 -33
- package/api/docs/docs.doc.mjs +4 -3
- package/api/docs/docs.type.d.mts +21 -5
- package/api/docs/docs.type.mjs +21 -5
- package/api/docs/integration-tree.test.mjs +9 -1
- package/api/docs/reference-blocks.test.mjs +406 -0
- package/api/doctor/doctor.mjs +5 -1
- package/api/integration/authoring-checks.mjs +14 -1
- package/assets/docs/styling-libraries.doc.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
- package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
- package/authoring/doctypes/_schema.d.mts +72 -0
- package/authoring/doctypes/_schema.mjs +25 -1
- package/authoring/doctypes/load-contract.test.mjs +25 -0
- package/authoring/doctypes/reference/reference.doc.mjs +31 -4
- package/authoring/doctypes/reference/type.ts +20 -5
- package/clients/cli/commands/docs.mjs +2 -2
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
- package/foundation/discovery/authoring-self-docs.d.mts +12 -0
- package/foundation/discovery/authoring-self-docs.mjs +20 -8
- package/foundation/discovery/authoring-self-docs.test.mjs +7 -2
- package/foundation/discovery/docs-discovery.d.mts +4 -1
- package/foundation/discovery/docs-discovery.mjs +40 -6
- package/foundation/discovery/docs-discovery.test.mjs +40 -0
- package/foundation/doc-compiler/lenses.d.mts +5 -3
- package/foundation/doc-compiler/lenses.mjs +48 -3
- package/foundation/doc-compiler/links.d.mts +30 -3
- package/foundation/doc-compiler/links.mjs +46 -5
- package/foundation/doc-compiler/links.test.mjs +53 -0
- package/foundation/integrations/cli-requirement.test.mjs +17 -42
- package/package.json +9 -9
package/api/docs/_adapter.d.mts
CHANGED
|
@@ -23,8 +23,9 @@ export function builtinCatalog(): DocsCatalog;
|
|
|
23
23
|
/**
|
|
24
24
|
* One topic, lowered for `lang` with every link between docs resolved
|
|
25
25
|
* (spec:AST-047 FR9): an inline `{@link <target>}` reads as the command that
|
|
26
|
-
* opens its doc, and a `reference`
|
|
27
|
-
*
|
|
26
|
+
* opens its doc, and a `reference` block carries the doc it names and the
|
|
27
|
+
* `content` it includes of it. Memoized per catalog and frozen, like the
|
|
28
|
+
* lowered node.
|
|
28
29
|
* @param {DocsCatalog} catalog
|
|
29
30
|
* @param {DocsTopicEntry} entry
|
|
30
31
|
* @param {string | null} [lang]
|
|
@@ -51,9 +52,8 @@ export function projectTree(catalog: DocsCatalog, { fresh }?: {
|
|
|
51
52
|
fresh?: boolean;
|
|
52
53
|
}): Promise<DocsTree>;
|
|
53
54
|
/**
|
|
54
|
-
* How a doc's links find their targets (spec:AST-047 FR9)
|
|
55
|
-
*
|
|
56
|
-
* name. A target that matches neither is a problem, never a guess.
|
|
55
|
+
* How a doc's links find their targets (spec:AST-047 FR9), as
|
|
56
|
+
* {@link docFinder} finds them: each resolves to the link a read shows.
|
|
57
57
|
* @param {DocsCatalog} catalog
|
|
58
58
|
* @param {string} fromProvider the provider id of the doc the links sit in
|
|
59
59
|
* @returns {Promise<LinkResolver>}
|
|
@@ -64,11 +64,15 @@ export function linkResolver(catalog: DocsCatalog, fromProvider: string): Promis
|
|
|
64
64
|
* guide the tree places, and each typed doc.
|
|
65
65
|
* @param {DocsCatalog} catalog
|
|
66
66
|
* @param {DocsTree} tree
|
|
67
|
-
* @param {{owner?: string}} [options] `owner`: only the
|
|
67
|
+
* @param {{owner?: string, references?: boolean}} [options] `owner`: only the
|
|
68
|
+
* docs this package owns. `references`: instead of the links, each reference
|
|
69
|
+
* block that cannot include what it names; a reader loses that content,
|
|
70
|
+
* where a link that names no doc still prints as written
|
|
68
71
|
* @returns {Promise<string[]>}
|
|
69
72
|
*/
|
|
70
|
-
export function docsLinkProblems(catalog: DocsCatalog, tree: DocsTree, { owner }?: {
|
|
73
|
+
export function docsLinkProblems(catalog: DocsCatalog, tree: DocsTree, { owner, references }?: {
|
|
71
74
|
owner?: string;
|
|
75
|
+
references?: boolean;
|
|
72
76
|
}): Promise<string[]>;
|
|
73
77
|
/**
|
|
74
78
|
* What \`astryx doctor integration docs\` checks in one integration's docs: the
|
|
@@ -93,6 +97,22 @@ export function packageDocsProblems(integration: {
|
|
|
93
97
|
* @returns {(topic: string) => Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode | null>}
|
|
94
98
|
*/
|
|
95
99
|
export function referenceTargets(catalog: DocsCatalog, lang: string | null): (topic: string) => Promise<import("../../foundation/doc-compiler/compile.mjs").CompiledReferenceNode | null>;
|
|
100
|
+
/**
|
|
101
|
+
* Each reference block in one integration's docs that cannot include what it
|
|
102
|
+
* names (spec:AST-047 FR9): a target that names no doc, a field its schema
|
|
103
|
+
* does not have, or a projection its doc cannot take. A reader would lose
|
|
104
|
+
* that content, so `astryx doctor integration docs` fails on each.
|
|
105
|
+
* @param {{name: string}} integration
|
|
106
|
+
* @param {{records: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicRecord[], namespaces: import('../../foundation/doc-compiler/tree.mjs').TreeNamespaceInput[], guides: import('../../foundation/doc-compiler/tree.mjs').TreeDocInput[]}} discovered
|
|
107
|
+
* @returns {Promise<string[]>}
|
|
108
|
+
*/
|
|
109
|
+
export function packageReferenceProblems(integration: {
|
|
110
|
+
name: string;
|
|
111
|
+
}, discovered: {
|
|
112
|
+
records: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicRecord[];
|
|
113
|
+
namespaces: import("../../foundation/doc-compiler/tree.mjs").TreeNamespaceInput[];
|
|
114
|
+
guides: import("../../foundation/doc-compiler/tree.mjs").TreeDocInput[];
|
|
115
|
+
}): Promise<string[]>;
|
|
96
116
|
/**
|
|
97
117
|
* One topic, compiled for `lang`: lowered, then every token reference linked.
|
|
98
118
|
* @param {DocsCatalog} catalog
|
|
@@ -245,10 +265,32 @@ export function resolveTopicDocs(topic: unknown, options?: {
|
|
|
245
265
|
lang: string | null;
|
|
246
266
|
entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
|
|
247
267
|
}>;
|
|
268
|
+
/**
|
|
269
|
+
* A doc a link found: what a read shows of the link, and the typed doc behind
|
|
270
|
+
* it when a reference block can include it.
|
|
271
|
+
*/
|
|
272
|
+
export type FoundDoc = {
|
|
273
|
+
link: import("../../foundation/doc-compiler/links.mjs").DocLink;
|
|
274
|
+
/**
|
|
275
|
+
* the doc's kind
|
|
276
|
+
*/
|
|
277
|
+
kind: string;
|
|
278
|
+
/**
|
|
279
|
+
* a
|
|
280
|
+
* schema, command, function, or enum doc: a leaf of the docs tree (`tree`),
|
|
281
|
+
* or a section of `astryx docs authoring`; null for any other kind
|
|
282
|
+
*/
|
|
283
|
+
typed: {
|
|
284
|
+
doc: any;
|
|
285
|
+
providerId: string;
|
|
286
|
+
tree: boolean;
|
|
287
|
+
} | null;
|
|
288
|
+
};
|
|
248
289
|
export type DocsTree = import("../../foundation/doc-compiler/tree.mjs").DocsTree;
|
|
249
290
|
export type TreeNode = import("../../foundation/doc-compiler/tree.mjs").TreeNode;
|
|
250
291
|
export type LinkProblem = import("../../foundation/doc-compiler/links.mjs").LinkProblem;
|
|
251
292
|
export type LinkResolver = import("../../foundation/doc-compiler/links.mjs").LinkResolver;
|
|
293
|
+
export type DocIncluder = import("../../foundation/doc-compiler/links.mjs").DocIncluder;
|
|
252
294
|
export type DocsTopicEntry = import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
|
|
253
295
|
import { DocsCatalog } from '../../foundation/discovery/docs-discovery.mjs';
|
|
254
296
|
import { AstryxError } from '../error.mjs';
|
package/api/docs/_adapter.mjs
CHANGED
|
@@ -47,6 +47,12 @@ import {
|
|
|
47
47
|
cliDocIndex,
|
|
48
48
|
cliDocSection,
|
|
49
49
|
} from '../../foundation/discovery/cli-self-docs.mjs';
|
|
50
|
+
import {
|
|
51
|
+
loadAuthoringSelfDocs,
|
|
52
|
+
schemaFieldTable,
|
|
53
|
+
selfDocSection,
|
|
54
|
+
} from '../../foundation/discovery/authoring-self-docs.mjs';
|
|
55
|
+
import {CLI_PROVIDER_ID} from '../../foundation/identity/providers.mjs';
|
|
50
56
|
import {AstryxError} from '../error.mjs';
|
|
51
57
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
52
58
|
|
|
@@ -119,6 +125,7 @@ function lowerRawTopic(catalog, entry, lang = null) {
|
|
|
119
125
|
* @typedef {import('../../foundation/doc-compiler/tree.mjs').TreeNode} TreeNode
|
|
120
126
|
* @typedef {import('../../foundation/doc-compiler/links.mjs').LinkProblem} LinkProblem
|
|
121
127
|
* @typedef {import('../../foundation/doc-compiler/links.mjs').LinkResolver} LinkResolver
|
|
128
|
+
* @typedef {import('../../foundation/doc-compiler/links.mjs').DocIncluder} DocIncluder
|
|
122
129
|
* @typedef {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} DocsTopicEntry
|
|
123
130
|
*/
|
|
124
131
|
|
|
@@ -146,8 +153,9 @@ function providerOf(entry) {
|
|
|
146
153
|
/**
|
|
147
154
|
* One topic, lowered for `lang` with every link between docs resolved
|
|
148
155
|
* (spec:AST-047 FR9): an inline `{@link <target>}` reads as the command that
|
|
149
|
-
* opens its doc, and a `reference`
|
|
150
|
-
*
|
|
156
|
+
* opens its doc, and a `reference` block carries the doc it names and the
|
|
157
|
+
* `content` it includes of it. Memoized per catalog and frozen, like the
|
|
158
|
+
* lowered node.
|
|
151
159
|
* @param {DocsCatalog} catalog
|
|
152
160
|
* @param {DocsTopicEntry} entry
|
|
153
161
|
* @param {string | null} [lang]
|
|
@@ -183,16 +191,19 @@ function linkTopic(catalog, entry, lang) {
|
|
|
183
191
|
if (!linked) {
|
|
184
192
|
linked = (async () => {
|
|
185
193
|
const raw = await lowerRawTopic(catalog, entry, lang);
|
|
186
|
-
/** @type {Map<string, LinkResolver>} */
|
|
187
|
-
const
|
|
194
|
+
/** @type {Map<string, {resolve: LinkResolver, include: DocIncluder}>} */
|
|
195
|
+
const linkers = new Map();
|
|
188
196
|
/** @param {string} provider */
|
|
189
|
-
const
|
|
190
|
-
let
|
|
191
|
-
if (!
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
const linkerFor = async provider => {
|
|
198
|
+
let linker = linkers.get(provider);
|
|
199
|
+
if (!linker) {
|
|
200
|
+
linker = {
|
|
201
|
+
resolve: await linkResolver(catalog, provider),
|
|
202
|
+
include: await docIncluder(catalog, provider),
|
|
203
|
+
};
|
|
204
|
+
linkers.set(provider, linker);
|
|
194
205
|
}
|
|
195
|
-
return
|
|
206
|
+
return linker;
|
|
196
207
|
};
|
|
197
208
|
/** @type {LinkProblem[]} */
|
|
198
209
|
const problems = [];
|
|
@@ -202,12 +213,14 @@ function linkTopic(catalog, entry, lang) {
|
|
|
202
213
|
// base topic's.
|
|
203
214
|
for (const section of raw.doc.sections) {
|
|
204
215
|
const provider = raw.sectionProviders?.[section.id];
|
|
216
|
+
const {resolve, include} = await linkerFor(
|
|
217
|
+
provider == null ? providerOf(entry) : normalizeProviderId(provider),
|
|
218
|
+
);
|
|
205
219
|
const linked = await linkBlocks(
|
|
206
220
|
section.content,
|
|
207
|
-
|
|
208
|
-
provider == null ? providerOf(entry) : normalizeProviderId(provider),
|
|
209
|
-
),
|
|
221
|
+
resolve,
|
|
210
222
|
{section: section.id ?? section.title},
|
|
223
|
+
include,
|
|
211
224
|
);
|
|
212
225
|
problems.push(...linked.problems);
|
|
213
226
|
sections.push({...section, content: linked.content});
|
|
@@ -332,15 +345,52 @@ function identitiesOf(tree) {
|
|
|
332
345
|
return index;
|
|
333
346
|
}
|
|
334
347
|
|
|
348
|
+
/**
|
|
349
|
+
* The CLI's authoring docs, by kind and name. `astryx docs authoring` reads
|
|
350
|
+
* each as one section keyed by its name, so a link to one opens that section.
|
|
351
|
+
* Loaded once per process, like the CLI's tree files.
|
|
352
|
+
* @type {Promise<Map<string, any>> | undefined}
|
|
353
|
+
*/
|
|
354
|
+
let authoringDocs;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The CLI authoring doc a link names, while `astryx docs authoring` is the
|
|
358
|
+
* CLI's own topic.
|
|
359
|
+
* @param {DocsCatalog} catalog
|
|
360
|
+
* @param {string} kind
|
|
361
|
+
* @param {string} name
|
|
362
|
+
* @returns {Promise<any | null>}
|
|
363
|
+
*/
|
|
364
|
+
async function authoringDoc(catalog, kind, name) {
|
|
365
|
+
if (catalog.resolve('authoring')?.package !== CLI_PROVIDER_ID) return null;
|
|
366
|
+
authoringDocs ??= loadAuthoringSelfDocs().then(
|
|
367
|
+
({loaded}) =>
|
|
368
|
+
new Map(loaded.map(({doc}) => [`${doc.type}\u0000${doc.name}`, doc])),
|
|
369
|
+
);
|
|
370
|
+
return (await authoringDocs).get(`${kind}\u0000${name}`) ?? null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* A doc a link found: what a read shows of the link, and the typed doc behind
|
|
375
|
+
* it when a reference block can include it.
|
|
376
|
+
* @typedef {object} FoundDoc
|
|
377
|
+
* @property {import('../../foundation/doc-compiler/links.mjs').DocLink} link
|
|
378
|
+
* @property {string} kind the doc's kind
|
|
379
|
+
* @property {{doc: any, providerId: string, tree: boolean} | null} typed a
|
|
380
|
+
* schema, command, function, or enum doc: a leaf of the docs tree (`tree`),
|
|
381
|
+
* or a section of `astryx docs authoring`; null for any other kind
|
|
382
|
+
*/
|
|
383
|
+
|
|
335
384
|
/**
|
|
336
385
|
* How a doc's links find their targets (spec:AST-047 FR9): a doc in the
|
|
337
|
-
* project's docs tree by its identity,
|
|
338
|
-
*
|
|
386
|
+
* project's docs tree by its identity, a CLI authoring doc (a section of
|
|
387
|
+
* `astryx docs authoring`), or a flat topic by its provider and name. A
|
|
388
|
+
* target that matches none is a problem, never a guess.
|
|
339
389
|
* @param {DocsCatalog} catalog
|
|
340
390
|
* @param {string} fromProvider the provider id of the doc the links sit in
|
|
341
|
-
* @returns {Promise<
|
|
391
|
+
* @returns {Promise<(target: string) => Promise<FoundDoc | {problem: string}>>}
|
|
342
392
|
*/
|
|
343
|
-
|
|
393
|
+
async function docFinder(catalog, fromProvider) {
|
|
344
394
|
const identities = identitiesOf(await projectTree(catalog));
|
|
345
395
|
return async target => {
|
|
346
396
|
const parsed = parseLinkTarget(target);
|
|
@@ -356,12 +406,36 @@ export async function linkResolver(catalog, fromProvider) {
|
|
|
356
406
|
const node = identities.get(identityKey(provider, parsed.kind, parsed.name));
|
|
357
407
|
if (node) {
|
|
358
408
|
return {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
409
|
+
link: {
|
|
410
|
+
target,
|
|
411
|
+
id: /** @type {string} */ (node.id),
|
|
412
|
+
route: node.route,
|
|
413
|
+
title: node.title,
|
|
414
|
+
summary: node.summary,
|
|
415
|
+
command: `astryx docs ${node.route}`,
|
|
416
|
+
},
|
|
417
|
+
kind: node.kind,
|
|
418
|
+
typed: node.ref?.selfDoc
|
|
419
|
+
? {doc: node.ref.selfDoc, providerId: node.providerId, tree: true}
|
|
420
|
+
: null,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const authored =
|
|
424
|
+
provider === CLI_PROVIDER_ID
|
|
425
|
+
? await authoringDoc(catalog, parsed.kind, parsed.name)
|
|
426
|
+
: null;
|
|
427
|
+
if (authored) {
|
|
428
|
+
return {
|
|
429
|
+
link: {
|
|
430
|
+
target,
|
|
431
|
+
id: createDocId(provider, authored.type, authored.name),
|
|
432
|
+
route: 'authoring',
|
|
433
|
+
title: authored.displayName ?? authored.name,
|
|
434
|
+
summary: authored.description ?? '',
|
|
435
|
+
command: `astryx docs authoring ${authored.name}`,
|
|
436
|
+
},
|
|
437
|
+
kind: parsed.kind,
|
|
438
|
+
typed: {doc: authored, providerId: CLI_PROVIDER_ID, tree: false},
|
|
365
439
|
};
|
|
366
440
|
}
|
|
367
441
|
if (parsed.kind === 'generic') {
|
|
@@ -383,12 +457,16 @@ export async function linkResolver(catalog, fromProvider) {
|
|
|
383
457
|
// link still opens it.
|
|
384
458
|
}
|
|
385
459
|
return {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
460
|
+
link: {
|
|
461
|
+
target,
|
|
462
|
+
id: createDocId(provider, 'generic', parsed.name),
|
|
463
|
+
route: entry.name,
|
|
464
|
+
title,
|
|
465
|
+
summary,
|
|
466
|
+
command: `astryx docs ${entry.name}`,
|
|
467
|
+
},
|
|
468
|
+
kind: 'generic',
|
|
469
|
+
typed: null,
|
|
392
470
|
};
|
|
393
471
|
}
|
|
394
472
|
}
|
|
@@ -398,20 +476,158 @@ export async function linkResolver(catalog, fromProvider) {
|
|
|
398
476
|
};
|
|
399
477
|
}
|
|
400
478
|
|
|
479
|
+
/**
|
|
480
|
+
* How a doc's links find their targets (spec:AST-047 FR9), as
|
|
481
|
+
* {@link docFinder} finds them: each resolves to the link a read shows.
|
|
482
|
+
* @param {DocsCatalog} catalog
|
|
483
|
+
* @param {string} fromProvider the provider id of the doc the links sit in
|
|
484
|
+
* @returns {Promise<LinkResolver>}
|
|
485
|
+
*/
|
|
486
|
+
export async function linkResolver(catalog, fromProvider) {
|
|
487
|
+
const find = await docFinder(catalog, fromProvider);
|
|
488
|
+
return async target => {
|
|
489
|
+
const found = await find(target);
|
|
490
|
+
return 'problem' in found ? found : found.link;
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** How a problem names a doc kind that a reference block cannot include. */
|
|
495
|
+
const KIND_NAMES = /** @type {Record<string, string>} */ ({
|
|
496
|
+
generic: 'topic',
|
|
497
|
+
namespace: 'namespace',
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* How a topic's reference blocks include the docs they name (spec:AST-047
|
|
502
|
+
* FR9): a schema, command, function, or enum doc as `astryx docs` prints
|
|
503
|
+
* it, narrowed by the block's projection and presentation, with the included
|
|
504
|
+
* doc's own links resolved against its own provider. Any other doc shows its
|
|
505
|
+
* title and summary. Each part a block names that it cannot include is a
|
|
506
|
+
* problem, and a read marks where it is missing; a target that names no doc
|
|
507
|
+
* is the resolver's problem.
|
|
508
|
+
* @param {DocsCatalog} catalog
|
|
509
|
+
* @param {string} fromProvider the provider id of the doc the blocks sit in
|
|
510
|
+
* @returns {Promise<DocIncluder>}
|
|
511
|
+
*/
|
|
512
|
+
async function docIncluder(catalog, fromProvider) {
|
|
513
|
+
const find = await docFinder(catalog, fromProvider);
|
|
514
|
+
const tree = await projectTree(catalog);
|
|
515
|
+
return async block => {
|
|
516
|
+
const found = await find(block.target);
|
|
517
|
+
if ('problem' in found) return {content: [], problems: []};
|
|
518
|
+
return includedContent(catalog, tree, block, found);
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* What one reference block includes of the doc it found.
|
|
524
|
+
* @param {DocsCatalog} catalog
|
|
525
|
+
* @param {DocsTree} tree
|
|
526
|
+
* @param {any} block
|
|
527
|
+
* @param {FoundDoc} found
|
|
528
|
+
* @returns {Promise<{content: any[], problems: string[]}>}
|
|
529
|
+
*/
|
|
530
|
+
async function includedContent(catalog, tree, block, found) {
|
|
531
|
+
/** @type {string[]} */
|
|
532
|
+
const problems = [];
|
|
533
|
+
const {fields, sections} = block.projection ?? {};
|
|
534
|
+
const presentation = block.presentation ?? 'full';
|
|
535
|
+
if (sections != null) {
|
|
536
|
+
problems.push(
|
|
537
|
+
'projection.sections: a reference block does not include topic sections; reference a schema, command, function, or enum doc, and name the fields of a schema with projection.fields',
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
const typed = found.typed;
|
|
541
|
+
if (typed == null) {
|
|
542
|
+
// A reference shows any other doc only by its title and summary.
|
|
543
|
+
if (fields != null || (block.presentation ?? 'summary') !== 'summary') {
|
|
544
|
+
problems.push(
|
|
545
|
+
`"${block.target}" is a ${KIND_NAMES[found.kind] ?? `${found.kind} doc`}, which a reference block shows only by its title and summary; remove the projection and the presentation, or reference a schema, command, function, or enum doc`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return {content: [], problems};
|
|
549
|
+
}
|
|
550
|
+
if (presentation === 'summary') {
|
|
551
|
+
if (fields != null) {
|
|
552
|
+
problems.push(
|
|
553
|
+
"projection.fields: a summary includes no fields; remove projection.fields or presentation: 'summary'",
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return {content: [], problems};
|
|
557
|
+
}
|
|
558
|
+
/** @type {any[]} */
|
|
559
|
+
let content;
|
|
560
|
+
if (fields != null && typed.doc.type === 'schema') {
|
|
561
|
+
/** @type {Map<string, any>} */
|
|
562
|
+
const byName = new Map(
|
|
563
|
+
(typed.doc.fields ?? []).map((/** @type {any} */ field) => [
|
|
564
|
+
field.name,
|
|
565
|
+
field,
|
|
566
|
+
]),
|
|
567
|
+
);
|
|
568
|
+
const selected = [];
|
|
569
|
+
/** @type {any[]} */
|
|
570
|
+
const missing = [];
|
|
571
|
+
for (const name of fields) {
|
|
572
|
+
const field = byName.get(name);
|
|
573
|
+
if (field) {
|
|
574
|
+
selected.push(field);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
problems.push(
|
|
578
|
+
`projection.fields: "${name}" is not a field of ${found.link.title} (${block.target}). Its fields: ${[...byName.keys()].join(', ')}`,
|
|
579
|
+
);
|
|
580
|
+
missing.push({
|
|
581
|
+
type: 'prose',
|
|
582
|
+
text: `[reference: field "${name}" not found in "${block.target}"]`,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
const table = schemaFieldTable(selected);
|
|
586
|
+
content = [...(table ? [table] : []), ...missing];
|
|
587
|
+
} else {
|
|
588
|
+
if (fields != null) {
|
|
589
|
+
problems.push(
|
|
590
|
+
`projection.fields: names the fields of a schema doc, and "${block.target}" is a ${typed.doc.type} doc; remove it to include the whole doc`,
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
content = typed.tree
|
|
594
|
+
? cliDocSection(typed.doc, typedDocIndex(tree)).content
|
|
595
|
+
: selfDocSection(typed.doc).content;
|
|
596
|
+
}
|
|
597
|
+
if (presentation === 'compact') {
|
|
598
|
+
content = content.filter(each => each?.type !== 'code');
|
|
599
|
+
}
|
|
600
|
+
// The included doc's own links resolve against its own provider. A link in
|
|
601
|
+
// it that names no doc is that doc's problem, reported where it is written.
|
|
602
|
+
const linked = await linkBlocks(
|
|
603
|
+
content,
|
|
604
|
+
await linkResolver(catalog, typed.providerId),
|
|
605
|
+
);
|
|
606
|
+
return {content: linked.content, problems};
|
|
607
|
+
}
|
|
608
|
+
|
|
401
609
|
/**
|
|
402
610
|
* Every link in the project's docs that names no doc: in each topic, each
|
|
403
611
|
* guide the tree places, and each typed doc.
|
|
404
612
|
* @param {DocsCatalog} catalog
|
|
405
613
|
* @param {DocsTree} tree
|
|
406
|
-
* @param {{owner?: string}} [options] `owner`: only the
|
|
614
|
+
* @param {{owner?: string, references?: boolean}} [options] `owner`: only the
|
|
615
|
+
* docs this package owns. `references`: instead of the links, each reference
|
|
616
|
+
* block that cannot include what it names; a reader loses that content,
|
|
617
|
+
* where a link that names no doc still prints as written
|
|
407
618
|
* @returns {Promise<string[]>}
|
|
408
619
|
*/
|
|
409
|
-
export async function docsLinkProblems(
|
|
620
|
+
export async function docsLinkProblems(
|
|
621
|
+
catalog,
|
|
622
|
+
tree,
|
|
623
|
+
{owner, references = false} = {},
|
|
624
|
+
) {
|
|
410
625
|
/** @type {string[]} */
|
|
411
626
|
const problems = [];
|
|
412
627
|
/** @param {string} where @param {LinkProblem[]} found */
|
|
413
628
|
const note = (where, found) => {
|
|
414
629
|
for (const problem of found) {
|
|
630
|
+
if ((problem.include === true) !== references) continue;
|
|
415
631
|
problems.push(
|
|
416
632
|
`${where}${problem.section ? ` \u00a7 ${problem.section}` : ''}: ${problem.message}`,
|
|
417
633
|
);
|
|
@@ -487,6 +703,28 @@ export function referenceTargets(catalog, lang) {
|
|
|
487
703
|
};
|
|
488
704
|
}
|
|
489
705
|
|
|
706
|
+
/**
|
|
707
|
+
* Each reference block in one integration's docs that cannot include what it
|
|
708
|
+
* names (spec:AST-047 FR9): a target that names no doc, a field its schema
|
|
709
|
+
* does not have, or a projection its doc cannot take. A reader would lose
|
|
710
|
+
* that content, so `astryx doctor integration docs` fails on each.
|
|
711
|
+
* @param {{name: string}} integration
|
|
712
|
+
* @param {{records: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicRecord[], namespaces: import('../../foundation/doc-compiler/tree.mjs').TreeNamespaceInput[], guides: import('../../foundation/doc-compiler/tree.mjs').TreeDocInput[]}} discovered
|
|
713
|
+
* @returns {Promise<string[]>}
|
|
714
|
+
*/
|
|
715
|
+
export async function packageReferenceProblems(integration, discovered) {
|
|
716
|
+
const catalog = DocsCatalog.fromBuiltins();
|
|
717
|
+
for (const record of discovered.records) catalog.add(record);
|
|
718
|
+
catalog.addTreeInputs({
|
|
719
|
+
namespaces: discovered.namespaces.map(input => ({...input, rank: 1})),
|
|
720
|
+
guides: discovered.guides.map(input => ({...input, rank: 1})),
|
|
721
|
+
});
|
|
722
|
+
return docsLinkProblems(catalog, await projectTree(catalog), {
|
|
723
|
+
owner: integration.name,
|
|
724
|
+
references: true,
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
490
728
|
/**
|
|
491
729
|
* One topic, compiled for `lang`: lowered, then every token reference linked.
|
|
492
730
|
* @param {DocsCatalog} catalog
|
|
@@ -535,6 +773,20 @@ const typedDocIndexes = new WeakMap();
|
|
|
535
773
|
*/
|
|
536
774
|
export async function nodeContent(catalog, tree, node) {
|
|
537
775
|
if (!node.ref?.selfDoc) return {content: [], problems: []};
|
|
776
|
+
const resolve = await linkResolver(catalog, node.providerId);
|
|
777
|
+
return linkBlocks(
|
|
778
|
+
cliDocSection(node.ref.selfDoc, typedDocIndex(tree)).content,
|
|
779
|
+
resolve,
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* The index a typed doc's content reads its cross-links from: every typed doc
|
|
785
|
+
* in the tree. Built once per tree.
|
|
786
|
+
* @param {DocsTree} tree
|
|
787
|
+
* @returns {ReturnType<typeof cliDocIndex>}
|
|
788
|
+
*/
|
|
789
|
+
function typedDocIndex(tree) {
|
|
538
790
|
let index = typedDocIndexes.get(tree);
|
|
539
791
|
if (!index) {
|
|
540
792
|
index = cliDocIndex(
|
|
@@ -544,8 +796,7 @@ export async function nodeContent(catalog, tree, node) {
|
|
|
544
796
|
);
|
|
545
797
|
typedDocIndexes.set(tree, index);
|
|
546
798
|
}
|
|
547
|
-
|
|
548
|
-
return linkBlocks(cliDocSection(node.ref.selfDoc, index).content, resolve);
|
|
799
|
+
return index;
|
|
549
800
|
}
|
|
550
801
|
|
|
551
802
|
/**
|
package/api/docs/docs.doc.mjs
CHANGED
|
@@ -20,7 +20,8 @@ export const doc = {
|
|
|
20
20
|
'ReferenceDoc, and `index: true` returns its section index (each ' +
|
|
21
21
|
'section\'s key, title, and summary); a topic plus a section returns ' +
|
|
22
22
|
'that one section. Token-ref blocks are ' +
|
|
23
|
-
'inlined in every read
|
|
23
|
+
'inlined in every read, and so is a section\'s reference block: the doc it ' +
|
|
24
|
+
'includes, then the command that opens that doc. The topic set is the CLI\'s own docs plus the ' +
|
|
24
25
|
'ones the project\'s configured integrations contribute, including any ' +
|
|
25
26
|
'topic an integration replaces or extends, so it depends on the cwd. ' +
|
|
26
27
|
'A route opens a node of the docs tree instead: a namespace such as ' +
|
|
@@ -97,7 +98,7 @@ export const doc = {
|
|
|
97
98
|
{
|
|
98
99
|
type: 'docs.detail',
|
|
99
100
|
description:
|
|
100
|
-
"One topic's full ReferenceDoc, with token-ref blocks inlined, plus links.",
|
|
101
|
+
"One topic's full ReferenceDoc, with token-ref and reference blocks inlined, plus links.",
|
|
101
102
|
},
|
|
102
103
|
{
|
|
103
104
|
type: 'docs.index',
|
|
@@ -107,7 +108,7 @@ export const doc = {
|
|
|
107
108
|
{
|
|
108
109
|
type: 'docs.detail.section',
|
|
109
110
|
description:
|
|
110
|
-
'One ReferenceSection of the topic, found by key or title, with token-ref blocks inlined.',
|
|
111
|
+
'One ReferenceSection of the topic, found by key or title, with token-ref and reference blocks inlined.',
|
|
111
112
|
},
|
|
112
113
|
{
|
|
113
114
|
type: 'docs.node',
|
package/api/docs/docs.type.d.mts
CHANGED
|
@@ -67,15 +67,31 @@ export type DocsDetailResponse = {
|
|
|
67
67
|
/**
|
|
68
68
|
* the whole doc, and the moves from it
|
|
69
69
|
*/
|
|
70
|
-
data:
|
|
70
|
+
data: DocsReadDoc & {
|
|
71
71
|
links: DocsLinks;
|
|
72
72
|
};
|
|
73
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* A topic section as a read returns it. A read inlines each token reference
|
|
76
|
+
* and each `reference` block the author wrote, so its content holds only the
|
|
77
|
+
* stable ReferenceContentBlock kinds.
|
|
78
|
+
*/
|
|
79
|
+
export type DocsReadSection = Omit<import("@astryxdesign/cli/authoring").ReferenceSection, "content"> & {
|
|
80
|
+
content: import("@astryxdesign/cli/authoring").ReferenceContentBlock[];
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* A topic as a read returns it: every section a {@link DocsReadSection}.
|
|
84
|
+
*/
|
|
85
|
+
export type DocsReadDoc = Omit<import("@astryxdesign/cli/authoring").ReferenceDoc, "sections"> & {
|
|
86
|
+
sections: DocsReadSection[];
|
|
87
|
+
};
|
|
74
88
|
/**
|
|
75
89
|
* The doc a link opens (spec:AST-047 FR9). A docs read resolves every link:
|
|
76
|
-
* an inline `{@link <target>}` reads as `link.command`, a `reference` block
|
|
77
|
-
* carries `link` (null when the target names no doc), and each
|
|
78
|
-
* carries `links`, one per reference.
|
|
90
|
+
* an inline `{@link <target>}` reads as `link.command`, a `reference` block in
|
|
91
|
+
* a namespace doc carries `link` (null when the target names no doc), and each
|
|
92
|
+
* `workflow` step carries `links`, one per reference. A topic read inlines a
|
|
93
|
+
* section's `reference` block as the doc it includes, then a line naming where
|
|
94
|
+
* that comes from and the command that opens it.
|
|
79
95
|
*/
|
|
80
96
|
export type DocLink = import("../../foundation/doc-compiler/links.mjs").DocLink;
|
|
81
97
|
/**
|
|
@@ -146,7 +162,7 @@ export type DocsDetailSectionResponse = {
|
|
|
146
162
|
* the section, and the moves from it: up to its topic's index, and across to
|
|
147
163
|
* the sections before and after it
|
|
148
164
|
*/
|
|
149
|
-
data:
|
|
165
|
+
data: DocsReadSection & {
|
|
150
166
|
links: DocsLinks;
|
|
151
167
|
};
|
|
152
168
|
};
|
package/api/docs/docs.type.mjs
CHANGED
|
@@ -56,15 +56,31 @@
|
|
|
56
56
|
* astryx --json docs <topic>
|
|
57
57
|
* @typedef {object} DocsDetailResponse
|
|
58
58
|
* @property {'docs.detail'} type
|
|
59
|
-
* @property {
|
|
59
|
+
* @property {DocsReadDoc & {links: DocsLinks}} data
|
|
60
60
|
* the whole doc, and the moves from it
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* A topic section as a read returns it. A read inlines each token reference
|
|
65
|
+
* and each `reference` block the author wrote, so its content holds only the
|
|
66
|
+
* stable ReferenceContentBlock kinds.
|
|
67
|
+
* @typedef {Omit<import('@astryxdesign/cli/authoring').ReferenceSection, 'content'>
|
|
68
|
+
* & {content: import('@astryxdesign/cli/authoring').ReferenceContentBlock[]}} DocsReadSection
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A topic as a read returns it: every section a {@link DocsReadSection}.
|
|
73
|
+
* @typedef {Omit<import('@astryxdesign/cli/authoring').ReferenceDoc, 'sections'>
|
|
74
|
+
* & {sections: DocsReadSection[]}} DocsReadDoc
|
|
75
|
+
*/
|
|
76
|
+
|
|
63
77
|
/**
|
|
64
78
|
* The doc a link opens (spec:AST-047 FR9). A docs read resolves every link:
|
|
65
|
-
* an inline `{@link <target>}` reads as `link.command`, a `reference` block
|
|
66
|
-
* carries `link` (null when the target names no doc), and each
|
|
67
|
-
* carries `links`, one per reference.
|
|
79
|
+
* an inline `{@link <target>}` reads as `link.command`, a `reference` block in
|
|
80
|
+
* a namespace doc carries `link` (null when the target names no doc), and each
|
|
81
|
+
* `workflow` step carries `links`, one per reference. A topic read inlines a
|
|
82
|
+
* section's `reference` block as the doc it includes, then a line naming where
|
|
83
|
+
* that comes from and the command that opens it.
|
|
68
84
|
* @typedef {import('../../foundation/doc-compiler/links.mjs').DocLink} DocLink
|
|
69
85
|
*/
|
|
70
86
|
|
|
@@ -110,7 +126,7 @@
|
|
|
110
126
|
* astryx --json docs <topic> <section>
|
|
111
127
|
* @typedef {object} DocsDetailSectionResponse
|
|
112
128
|
* @property {'docs.detail.section'} type
|
|
113
|
-
* @property {
|
|
129
|
+
* @property {DocsReadSection & {links: DocsLinks}} data
|
|
114
130
|
* the section, and the moves from it: up to its topic's index, and across to
|
|
115
131
|
* the sections before and after it
|
|
116
132
|
*/
|
|
@@ -151,7 +151,15 @@ describe('integration docs in the docs tree', () => {
|
|
|
151
151
|
title: 'Broken',
|
|
152
152
|
description: 'A topic whose block no topic may hold.',
|
|
153
153
|
sections: [
|
|
154
|
-
{
|
|
154
|
+
{
|
|
155
|
+
title: 'Only',
|
|
156
|
+
content: [
|
|
157
|
+
{
|
|
158
|
+
type: 'workflow',
|
|
159
|
+
steps: [{title: 'Set up', references: ['generic:setup']}],
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
155
163
|
],
|
|
156
164
|
},
|
|
157
165
|
});
|