@astryxdesign/cli 0.6.3-canary.7ada42e → 0.6.3-canary.7ec7be0
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 +21 -19
- package/api/build/_adapter.d.mts +50 -0
- package/api/build/_adapter.mjs +60 -0
- package/api/build/build.doc.mjs +13 -7
- package/api/build/build.test.mjs +184 -6
- package/api/build/build.type.d.mts +57 -2
- package/api/build/build.type.mjs +30 -7
- package/api/build/help/help.d.mts +4 -0
- package/api/build/help/help.mjs +24 -11
- package/api/build/kit/kit.d.mts +4 -1
- package/api/build/kit/kit.mjs +165 -49
- package/api/build/kit/rank.d.mts +44 -0
- package/api/build/kit/rank.mjs +432 -0
- package/api/build/kit/rank.test.mjs +196 -0
- package/api/component/_adapter.d.mts +6 -12
- package/api/component/_adapter.mjs +20 -10
- package/api/component/component.mjs +60 -12
- package/api/component/list/list.mjs +3 -2
- package/api/docs/_adapter.d.mts +251 -28
- package/api/docs/_adapter.mjs +918 -35
- package/api/docs/detail/detail.mjs +11 -3
- package/api/docs/detail/section/section.mjs +24 -13
- package/api/docs/detail/section/section.test.mjs +15 -6
- package/api/docs/docs.d.mts +5 -3
- package/api/docs/docs.doc.mjs +38 -17
- package/api/docs/docs.mjs +44 -8
- package/api/docs/docs.test.mjs +158 -4
- package/api/docs/docs.type.d.mts +181 -2
- package/api/docs/docs.type.mjs +117 -3
- package/api/docs/index/index.mjs +11 -3
- package/api/docs/index/index.test.mjs +1 -1
- package/api/docs/integration-tree.test.mjs +555 -0
- package/api/docs/integrationDocs.test.mjs +14 -14
- package/api/docs/list/list.mjs +25 -5
- package/api/docs/node/node.d.mts +43 -0
- package/api/docs/node/node.mjs +192 -0
- package/api/docs/reference-blocks.test.mjs +406 -0
- package/api/doctor/doctor.d.mts +30 -2
- package/api/doctor/doctor.mjs +151 -8
- package/api/doctor/doctor.test.mjs +95 -7
- package/api/index.d.mts +1 -1
- package/api/index.mjs +1 -0
- package/api/init/init.doc.mjs +1 -1
- package/api/integration/add-contribution.d.mts +2 -1
- package/api/integration/add-contribution.mjs +107 -5
- package/api/integration/add-contribution.test.mjs +137 -0
- package/api/integration/authoring-checks.mjs +138 -28
- package/api/integration/authoring-checks.test.mjs +171 -3
- package/api/integration/authoring-checks.type.mjs +6 -1
- package/api/integration/integration-authoring.type.d.mts +2 -0
- package/api/integration/integration-authoring.type.mjs +2 -0
- package/api/integration/integrationAdd.doc.mjs +6 -0
- package/api/integration/integrationAddDoc.doc.mjs +7 -1
- package/api/integration/integrationDocConflicts.doc.mjs +1 -1
- package/api/integration/integrationPackCheck.doc.mjs +1 -1
- package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
- package/api/integration/integrationTemplateConflicts.doc.mjs +3 -0
- package/api/integration/pack-check.mjs +34 -0
- package/api/integration/pack-check.test.mjs +121 -0
- package/api/integration/pack-check.type.d.mts +26 -2
- package/api/integration/pack-check.type.mjs +14 -1
- package/api/integration/template-conflict-compatibility.test.mjs +73 -0
- package/api/integration/validate-integration.test.mjs +3 -3
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/json/index.ts +2 -0
- package/api/layout/_adapter.mjs +20 -5
- package/api/search/search.d.mts +59 -1
- package/api/search/search.doc.mjs +4 -3
- package/api/search/search.mjs +442 -71
- package/api/search/search.test.mjs +91 -2
- package/api/search/search.type.d.mts +13 -1
- package/api/search/search.type.mjs +4 -1
- package/api/template/list/list.mjs +1 -0
- package/api/template/table-floating-bulk-actions.test.mjs +66 -0
- package/api/template/template-integration.test.mjs +1072 -3
- package/api/template/template.doc.mjs +27 -7
- package/api/template/template.mjs +45 -8
- package/api/template/template.type.d.mts +6 -8
- package/api/template/template.type.mjs +3 -2
- package/api/theme/template/template.test.mjs +5 -0
- package/api/theme/themeTemplate.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +2 -2
- package/assets/docs/layout.doc.dense.mjs +2 -2
- package/assets/docs/layout.doc.mjs +1 -1
- package/assets/docs/principles.doc.mjs +6 -6
- package/assets/docs/styling-libraries.doc.mjs +3 -3
- package/assets/docs/styling.doc.mjs +4 -4
- package/assets/docs/theme.doc.mjs +4 -4
- package/assets/docs/tokens.doc.mjs +1 -1
- package/assets/docs/tree/api.doc.mjs +30 -0
- package/assets/docs/tree/cli.doc.mjs +23 -0
- package/assets/docs/tree/commands.doc.mjs +25 -0
- package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +37 -9
- package/assets/docs/tree/integrations.test.mjs +62 -0
- package/assets/docs/tree/writing-docs.doc.mjs +286 -0
- package/assets/docs/working-with-ai.doc.mjs +3 -3
- package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
- package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
- package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
- package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
- package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
- package/authoring/debug/parse.d.mts +3 -3
- package/authoring/doctypes/_schema.d.mts +119 -117
- package/authoring/doctypes/_schema.mjs +54 -3
- package/authoring/doctypes/base/graph-fields.doc.mjs +7 -5
- package/authoring/doctypes/base/type.ts +6 -5
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +2 -2
- package/authoring/doctypes/enum/enum.doc.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +1 -1
- package/authoring/doctypes/function/function.doc.mjs +3 -2
- package/authoring/doctypes/function/type.ts +3 -2
- package/authoring/doctypes/load-contract.test.mjs +26 -1
- package/authoring/doctypes/namespace/namespace.doc.mjs +6 -10
- package/authoring/doctypes/namespace/parse.test.mjs +23 -25
- package/authoring/doctypes/namespace/type.ts +5 -2
- package/authoring/doctypes/parse.mjs +3 -2
- package/authoring/doctypes/reference/reference.doc.mjs +34 -5
- package/authoring/doctypes/reference/type.ts +28 -11
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +1 -1
- package/authoring/doctypes/template/parse.d.mts +2 -0
- package/authoring/doctypes/template/parse.mjs +4 -0
- package/authoring/doctypes/template/parse.test.mjs +18 -0
- package/authoring/doctypes/template/template.doc.mjs +6 -0
- package/authoring/doctypes/template/type.ts +8 -0
- package/authoring/identity/identity.doc.mjs +2 -2
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/type.ts +1 -1
- package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
- package/clients/cli/commands/build-theme.mjs +1 -1
- package/clients/cli/commands/build.doc.mjs +11 -6
- package/clients/cli/commands/build.mjs +105 -67
- package/clients/cli/commands/build.text-fields.test.mjs +41 -0
- package/clients/cli/commands/component/index.mjs +1 -1
- package/clients/cli/commands/component-ownership.test.mjs +3 -3
- package/clients/cli/commands/discover.broken-integration.test.mjs +7 -8
- package/clients/cli/commands/discover.mjs +1 -1
- package/clients/cli/commands/docs.doc.mjs +20 -8
- package/clients/cli/commands/docs.mjs +183 -69
- package/clients/cli/commands/docs.test.mjs +120 -16
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +2 -2
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +14 -7
- package/clients/cli/commands/doctor-integration.test.mjs +75 -1
- package/clients/cli/commands/doctor.mjs +53 -16
- package/clients/cli/commands/integration-add.doc.mjs +10 -0
- package/clients/cli/commands/integration.mjs +1 -0
- package/clients/cli/commands/search.mjs +11 -2
- package/clients/cli/commands/setup-nudge.test.mjs +6 -0
- package/clients/cli/commands/template.doc.mjs +23 -6
- package/clients/cli/commands/template.mjs +4 -91
- package/clients/cli/index.mjs +8 -0
- package/clients/cli/lib/manifest.mjs +9 -2
- package/foundation/agent-docs/agent-docs.d.mts +3 -2
- package/foundation/agent-docs/agent-docs.mjs +13 -9
- package/foundation/agent-docs/agent-docs.test.mjs +19 -1
- package/foundation/config/project.d.mts +12 -11
- package/foundation/config/project.mjs +73 -59
- package/foundation/config/project.test.mjs +141 -5
- 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 +25 -9
- package/foundation/discovery/cli-self-docs.d.mts +40 -34
- package/foundation/discovery/cli-self-docs.mjs +91 -115
- package/foundation/discovery/cli-self-docs.test.mjs +104 -166
- package/foundation/discovery/component-discovery.d.mts +38 -0
- package/foundation/discovery/component-discovery.mjs +48 -0
- package/foundation/discovery/docs-discovery.d.mts +109 -9
- package/foundation/discovery/docs-discovery.mjs +223 -24
- package/foundation/discovery/docs-discovery.test.mjs +103 -27
- package/foundation/discovery/docs-output-budget.d.mts +2 -2
- package/foundation/discovery/docs-output-budget.mjs +1 -1
- package/foundation/discovery/docs-section-key.d.mts +28 -10
- package/foundation/discovery/docs-section-key.mjs +134 -33
- package/foundation/discovery/docs-section-key.test.mjs +41 -19
- package/foundation/discovery/template-adapter.d.mts +92 -6
- package/foundation/discovery/template-adapter.mjs +453 -17
- package/foundation/discovery/template-adapter.test.mjs +42 -0
- package/foundation/discovery/template-conflict-release.d.mts +13 -0
- package/foundation/discovery/template-conflict-release.mjs +40 -0
- package/foundation/discovery/template-conflict-release.test.mjs +40 -0
- package/foundation/doc-compiler/bundle.mjs +61 -1
- package/foundation/doc-compiler/bundle.test.mjs +21 -10
- package/foundation/doc-compiler/compile.d.mts +113 -132
- package/foundation/doc-compiler/compile.mjs +57 -10
- package/foundation/doc-compiler/diagnostics.mjs +28 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +2 -2
- package/foundation/doc-compiler/inputs.d.mts +2 -2
- package/foundation/doc-compiler/inputs.mjs +6 -1
- package/foundation/doc-compiler/ir.mjs +7 -0
- package/foundation/doc-compiler/lenses.d.mts +8 -5
- package/foundation/doc-compiler/lenses.mjs +50 -4
- package/foundation/doc-compiler/links.d.mts +162 -0
- package/foundation/doc-compiler/links.mjs +294 -0
- package/foundation/doc-compiler/links.test.mjs +192 -0
- package/foundation/doc-compiler/lower-doc.test.mjs +5 -2
- package/foundation/doc-compiler/read.d.mts +3 -2
- package/foundation/doc-compiler/read.mjs +6 -1
- package/foundation/doc-compiler/tree.d.mts +288 -0
- package/foundation/doc-compiler/tree.mjs +876 -0
- package/foundation/doc-compiler/tree.test.mjs +598 -0
- package/foundation/integrations/autolink.test.mjs +1 -1
- package/foundation/integrations/cli-requirement.d.mts +45 -0
- package/foundation/integrations/cli-requirement.mjs +154 -0
- package/foundation/integrations/cli-requirement.test.mjs +84 -0
- package/foundation/integrations/contribution-inventory.d.mts +2 -1
- package/foundation/integrations/contribution-inventory.mjs +10 -4
- package/foundation/integrations/contribution-inventory.test.mjs +19 -0
- package/foundation/integrations/integration-warnings.d.mts +9 -2
- package/foundation/integrations/integration-warnings.mjs +51 -26
- package/foundation/integrations/integration-warnings.test.mjs +74 -1
- package/foundation/integrations/validate-contributions.mjs +25 -27
- package/foundation/response/response-types.doc.d.mts +5 -1
- package/foundation/response/response-types.doc.mjs +19 -10
- package/foundation/text/string-utils.d.mts +8 -0
- package/foundation/text/string-utils.mjs +18 -0
- package/foundation/xle/expand.mjs +1 -1
- package/foundation/xle/xle.test.mjs +13 -0
- package/package.json +9 -9
- package/assets/docs/cli.doc.mjs +0 -15
package/api/docs/list/list.mjs
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
* English `description` is read from its file; a contributed topic already
|
|
9
9
|
* carries the one discovery read. The listing never applies --dense/--zh
|
|
10
10
|
* overlays.
|
|
11
|
-
* @output { type: 'docs.list', data: DocsListEntry[] } — one entry per topic
|
|
12
|
-
* read order,
|
|
13
|
-
* `astryx --json docs`.
|
|
11
|
+
* @output { type: 'docs.list', data: DocsListEntry[] } — one entry per topic
|
|
12
|
+
* in read order, then one per top-level docs-tree namespace, each naming the
|
|
13
|
+
* package that owns it, matching `astryx --json docs`.
|
|
14
14
|
* @position Leaf under api/docs. Sibling of detail; both share _adapter.mjs.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import {loadTopicFile} from '../../../foundation/doc-compiler/read.mjs';
|
|
18
|
-
import {loadDocsCatalog} from '../_adapter.mjs';
|
|
18
|
+
import {holdsOwnName, loadDocsCatalog, projectTree} from '../_adapter.mjs';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @param {object} [options]
|
|
@@ -24,9 +24,13 @@ import {loadDocsCatalog} from '../_adapter.mjs';
|
|
|
24
24
|
*/
|
|
25
25
|
export async function list({cwd} = {}) {
|
|
26
26
|
const catalog = await loadDocsCatalog(cwd);
|
|
27
|
+
const tree = await projectTree(catalog);
|
|
27
28
|
/** @type {Array<import('../docs.type.mjs').DocsListEntry>} */
|
|
28
29
|
const entries = [];
|
|
29
30
|
for (const entry of catalog.entries()) {
|
|
31
|
+
// A topic whose name opens another doc is not listed (spec:AST-046
|
|
32
|
+
// FR11); `astryx doctor` names the clash.
|
|
33
|
+
if (!holdsOwnName(tree, catalog, entry)) continue;
|
|
30
34
|
let description = entry.description ?? '';
|
|
31
35
|
if (entry.description == null) {
|
|
32
36
|
const file = await loadTopicFile(entry.path, null);
|
|
@@ -41,5 +45,21 @@ export async function list({cwd} = {}) {
|
|
|
41
45
|
if (entry.replaces != null) listed.replaces = entry.replaces;
|
|
42
46
|
entries.push(listed);
|
|
43
47
|
}
|
|
44
|
-
|
|
48
|
+
// The docs tree's top-level namespaces, the CLI's and each integration's,
|
|
49
|
+
// each the way into a whole branch (spec:AST-046 FR7). They go in `meta`,
|
|
50
|
+
// so `data` stays the topic list 0.6 returned: every entry reads as a topic.
|
|
51
|
+
const namespaces = tree.roots().map(root => ({
|
|
52
|
+
topic: root.route,
|
|
53
|
+
description: root.summary,
|
|
54
|
+
package: root.provider,
|
|
55
|
+
}));
|
|
56
|
+
/** @type {NonNullable<import('../docs.type.mjs').DocsListResponse['meta']>} */
|
|
57
|
+
const meta = {};
|
|
58
|
+
if (namespaces.length > 0) meta.namespaces = namespaces;
|
|
59
|
+
// A package whose docs did not load is named, so its author knows why its
|
|
60
|
+
// topics are missing.
|
|
61
|
+
if (catalog.issues.length > 0) meta.notLoaded = [...catalog.issues];
|
|
62
|
+
return Object.keys(meta).length === 0
|
|
63
|
+
? {type: 'docs.list', data: entries}
|
|
64
|
+
: {type: 'docs.list', data: entries, meta};
|
|
45
65
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../../../foundation/doc-compiler/tree.mjs').DocsTree} DocsTree
|
|
6
|
+
* @typedef {import('../../../foundation/doc-compiler/tree.mjs').TreeNode} TreeNode
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The docs.node view of one tree node.
|
|
10
|
+
* @param {import('../../../foundation/discovery/docs-discovery.mjs').DocsCatalog} catalog
|
|
11
|
+
* @param {DocsTree} tree
|
|
12
|
+
* @param {TreeNode} node
|
|
13
|
+
* @returns {Promise<import('../docs.type.mjs').DocsNode>}
|
|
14
|
+
*/
|
|
15
|
+
export function nodeView(catalog: import("../../../foundation/discovery/docs-discovery.mjs").DocsCatalog, tree: DocsTree, node: TreeNode): Promise<import("../docs.type.mjs").DocsNode>;
|
|
16
|
+
/**
|
|
17
|
+
* The typed edges a doc declares (spec:AST-047 FR5), resolved to routes: a
|
|
18
|
+
* function doc's `command` and `related`, and a command doc's `fn` and
|
|
19
|
+
* `related`. A bare name resolves among the kinds its field allows; a name
|
|
20
|
+
* that matches docs of more than one allowed kind is an error, never a silent
|
|
21
|
+
* pick. A `command` resolves to the command doc its leading words name, so
|
|
22
|
+
* `integration add theme` (the `integration add` command with its kind
|
|
23
|
+
* argument) opens `integration add`. Every name that resolves to no doc, or to
|
|
24
|
+
* more than one, is returned in `unresolved`; the graph walk test fails on it,
|
|
25
|
+
* so no edge a doc declares can go stale.
|
|
26
|
+
* @param {DocsTree} tree
|
|
27
|
+
* @param {TreeNode} node
|
|
28
|
+
* @returns {{routes: string[], unresolved: string[]}}
|
|
29
|
+
*/
|
|
30
|
+
export function typedEdges(tree: DocsTree, node: TreeNode): {
|
|
31
|
+
routes: string[];
|
|
32
|
+
unresolved: string[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} route
|
|
36
|
+
* @param {{cwd?: string}} [options]
|
|
37
|
+
* @returns {Promise<import('../docs.type.mjs').DocsNodeResponse>}
|
|
38
|
+
*/
|
|
39
|
+
export function node(route: string, options?: {
|
|
40
|
+
cwd?: string;
|
|
41
|
+
}): Promise<import("../docs.type.mjs").DocsNodeResponse>;
|
|
42
|
+
export type DocsTree = import("../../../foundation/doc-compiler/tree.mjs").DocsTree;
|
|
43
|
+
export type TreeNode = import("../../../foundation/doc-compiler/tree.mjs").TreeNode;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file docs.node leaf — one namespace or typed doc in the docs tree.
|
|
5
|
+
*
|
|
6
|
+
* @input A route the docs tree resolves: a namespace such as `cli/api`, or a
|
|
7
|
+
* typed doc such as `cli/api/functions/search`, plus {cwd}.
|
|
8
|
+
* @output { type: 'docs.node', data: DocsNode } — the node's identity, title
|
|
9
|
+
* and summary, the namespaces above it, and either its slots with their
|
|
10
|
+
* children (a namespace) or its content (a typed doc). A namespace never
|
|
11
|
+
* lists its grandchildren, so a reader goes down one level at a time.
|
|
12
|
+
* Matches `astryx --json docs <route>`.
|
|
13
|
+
* @position Leaf under api/docs, beside the topic leaves. A guide the tree
|
|
14
|
+
* places is a topic: the detail leaf reads it by its route.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
nodeContent,
|
|
19
|
+
placeLinks,
|
|
20
|
+
resolveDocsArgument,
|
|
21
|
+
unknownTopicError,
|
|
22
|
+
} from '../_adapter.mjs';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {import('../../../foundation/doc-compiler/tree.mjs').DocsTree} DocsTree
|
|
26
|
+
* @typedef {import('../../../foundation/doc-compiler/tree.mjs').TreeNode} TreeNode
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The docs.node view of one tree node.
|
|
31
|
+
* @param {import('../../../foundation/discovery/docs-discovery.mjs').DocsCatalog} catalog
|
|
32
|
+
* @param {DocsTree} tree
|
|
33
|
+
* @param {TreeNode} node
|
|
34
|
+
* @returns {Promise<import('../docs.type.mjs').DocsNode>}
|
|
35
|
+
*/
|
|
36
|
+
export async function nodeView(catalog, tree, node) {
|
|
37
|
+
const {content} = await nodeContent(catalog, tree, node);
|
|
38
|
+
return {
|
|
39
|
+
id: node.id,
|
|
40
|
+
route: node.route,
|
|
41
|
+
kind: node.kind,
|
|
42
|
+
package: node.provider,
|
|
43
|
+
title: node.title,
|
|
44
|
+
summary: node.summary,
|
|
45
|
+
breadcrumb: tree
|
|
46
|
+
.ancestors(node)
|
|
47
|
+
.map(ancestor => ({route: ancestor.route, title: ancestor.title})),
|
|
48
|
+
slots: node.slots
|
|
49
|
+
.filter(slot => slot.children.length > 0)
|
|
50
|
+
.map(slot => ({
|
|
51
|
+
name: slot.name,
|
|
52
|
+
title: slot.title,
|
|
53
|
+
children: slot.children.map(route => {
|
|
54
|
+
const child = /** @type {TreeNode} */ (tree.get(route));
|
|
55
|
+
return {
|
|
56
|
+
route: child.route,
|
|
57
|
+
name: child.route.slice(child.route.lastIndexOf('/') + 1),
|
|
58
|
+
kind: child.kind,
|
|
59
|
+
title: child.title,
|
|
60
|
+
summary: child.summary,
|
|
61
|
+
};
|
|
62
|
+
}),
|
|
63
|
+
})),
|
|
64
|
+
content,
|
|
65
|
+
links: nodeLinks(tree, node),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The moves a node offers (spec:AST-047): up to the level it sits in (the
|
|
71
|
+
* topic list, for a top-level namespace), and across to the nodes before and
|
|
72
|
+
* after it in its parent's slot.
|
|
73
|
+
* @param {DocsTree} tree
|
|
74
|
+
* @param {TreeNode} node
|
|
75
|
+
* @returns {import('../docs.type.mjs').DocsLinks}
|
|
76
|
+
*/
|
|
77
|
+
function nodeLinks(tree, node) {
|
|
78
|
+
const links = placeLinks(tree, node);
|
|
79
|
+
const related = typedEdges(tree, node).routes;
|
|
80
|
+
if (related.length > 0) {
|
|
81
|
+
links.related = related.map(
|
|
82
|
+
route =>
|
|
83
|
+
/** @type {import('../docs.type.mjs').DocsCommand} */ (
|
|
84
|
+
`astryx docs ${route}`
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return links;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @type {WeakMap<DocsTree, Map<string, string>>} */
|
|
92
|
+
const routeIndexes = new WeakMap();
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The route of each node, by its kind and name.
|
|
96
|
+
* @param {DocsTree} tree
|
|
97
|
+
* @returns {Map<string, string>}
|
|
98
|
+
*/
|
|
99
|
+
function routeIndex(tree) {
|
|
100
|
+
let routes = routeIndexes.get(tree);
|
|
101
|
+
if (!routes) {
|
|
102
|
+
routes = new Map();
|
|
103
|
+
for (const each of tree.nodes.values()) {
|
|
104
|
+
routes.set(`${each.kind}\u0000${each.name}`, each.route);
|
|
105
|
+
}
|
|
106
|
+
routeIndexes.set(tree, routes);
|
|
107
|
+
}
|
|
108
|
+
return routes;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The kinds of doc each typed field may name. A bare name in one of these
|
|
113
|
+
* fields names a doc of the same provider and one of these kinds, so it is a
|
|
114
|
+
* doc identity (spec:AST-047 FR9).
|
|
115
|
+
*/
|
|
116
|
+
const EDGE_KINDS = {
|
|
117
|
+
function: {command: ['command'], related: ['function']},
|
|
118
|
+
command: {fn: ['function'], related: ['command']},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The typed edges a doc declares (spec:AST-047 FR5), resolved to routes: a
|
|
123
|
+
* function doc's `command` and `related`, and a command doc's `fn` and
|
|
124
|
+
* `related`. A bare name resolves among the kinds its field allows; a name
|
|
125
|
+
* that matches docs of more than one allowed kind is an error, never a silent
|
|
126
|
+
* pick. A `command` resolves to the command doc its leading words name, so
|
|
127
|
+
* `integration add theme` (the `integration add` command with its kind
|
|
128
|
+
* argument) opens `integration add`. Every name that resolves to no doc, or to
|
|
129
|
+
* more than one, is returned in `unresolved`; the graph walk test fails on it,
|
|
130
|
+
* so no edge a doc declares can go stale.
|
|
131
|
+
* @param {DocsTree} tree
|
|
132
|
+
* @param {TreeNode} node
|
|
133
|
+
* @returns {{routes: string[], unresolved: string[]}}
|
|
134
|
+
*/
|
|
135
|
+
export function typedEdges(tree, node) {
|
|
136
|
+
const doc = /** @type {any} */ (node.ref)?.selfDoc;
|
|
137
|
+
/** @type {string[]} */
|
|
138
|
+
const routes = [];
|
|
139
|
+
/** @type {string[]} */
|
|
140
|
+
const unresolved = [];
|
|
141
|
+
const fields = /** @type {Record<string, string[]> | undefined} */ (
|
|
142
|
+
/** @type {any} */ (EDGE_KINDS)[node.kind]
|
|
143
|
+
);
|
|
144
|
+
if (!doc || !fields) return {routes, unresolved};
|
|
145
|
+
const index = routeIndex(tree);
|
|
146
|
+
/** @param {string[]} kinds @param {string} name */
|
|
147
|
+
const among = (kinds, name) =>
|
|
148
|
+
kinds
|
|
149
|
+
.map(kind => index.get(`${kind}\u0000${name}`))
|
|
150
|
+
.filter(route => typeof route === 'string');
|
|
151
|
+
for (const [field, kinds] of Object.entries(fields)) {
|
|
152
|
+
const value = doc[field];
|
|
153
|
+
const names = Array.isArray(value) ? value : value == null ? [] : [value];
|
|
154
|
+
for (const name of names) {
|
|
155
|
+
if (typeof name !== 'string' || name.trim() === '') continue;
|
|
156
|
+
let found = among(kinds, name);
|
|
157
|
+
if (found.length === 0 && field === 'command') {
|
|
158
|
+
const words = name.trim().split(/\s+/);
|
|
159
|
+
for (let n = words.length - 1; n > 0 && found.length === 0; n--) {
|
|
160
|
+
found = among(kinds, words.slice(0, n).join(' '));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (found.length === 1) routes.push(/** @type {string} */ (found[0]));
|
|
164
|
+
else if (found.length === 0) {
|
|
165
|
+
unresolved.push(`${field} "${name}" names no ${kinds.join(' or ')} doc`);
|
|
166
|
+
} else {
|
|
167
|
+
unresolved.push(
|
|
168
|
+
`${field} "${name}" names more than one doc (${found.join(', ')})`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
routes: [...new Set(routes)].filter(route => route !== node.route),
|
|
175
|
+
unresolved,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {string} route
|
|
181
|
+
* @param {{cwd?: string}} [options]
|
|
182
|
+
* @returns {Promise<import('../docs.type.mjs').DocsNodeResponse>}
|
|
183
|
+
*/
|
|
184
|
+
export async function node(route, options = {}) {
|
|
185
|
+
const found = await resolveDocsArgument(route, options);
|
|
186
|
+
if (found.kind !== 'node')
|
|
187
|
+
throw await unknownTopicError(route, found.catalog);
|
|
188
|
+
return {
|
|
189
|
+
type: 'docs.node',
|
|
190
|
+
data: await nodeView(found.catalog, found.tree, found.node),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file A topic section's `reference` block (spec:AST-047 FR9). A read
|
|
5
|
+
* includes the doc it names from that doc's own source, narrowed by its
|
|
6
|
+
* projection, and still returns only the stable block kinds. A block that
|
|
7
|
+
* cannot include what it names fails the authoring check.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
13
|
+
import {docs} from './docs.mjs';
|
|
14
|
+
import {
|
|
15
|
+
builtinCatalog,
|
|
16
|
+
docsLinkProblems,
|
|
17
|
+
linkResolver,
|
|
18
|
+
loadDocsCatalog,
|
|
19
|
+
projectTree,
|
|
20
|
+
} from './_adapter.mjs';
|
|
21
|
+
import {integrationDocConflicts} from '../integration/authoring-checks.mjs';
|
|
22
|
+
import {checkDocsTree} from '../doctor/doctor.mjs';
|
|
23
|
+
import {createDocId} from '../../foundation/identity/provider-identity.mjs';
|
|
24
|
+
import {CLI_PROVIDER_ID} from '../../foundation/identity/providers.mjs';
|
|
25
|
+
import {runCli} from '../../test-utils/run-cli.mjs';
|
|
26
|
+
|
|
27
|
+
const SLOW = 60_000;
|
|
28
|
+
|
|
29
|
+
/** The block kinds a read returns: a token reference and a reference block are inlined. */
|
|
30
|
+
const READ_KINDS = new Set(['prose', 'heading', 'code', 'table', 'list']);
|
|
31
|
+
|
|
32
|
+
/** The primary fixture: two fields of the CLI's integration manifest schema. */
|
|
33
|
+
const MANIFEST_FIELDS = {
|
|
34
|
+
type: 'reference',
|
|
35
|
+
target: '@astryxdesign/cli:schema:integration',
|
|
36
|
+
projection: {fields: ['components', 'docs']},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** A topic whose sections include canonical CLI docs. */
|
|
40
|
+
const SETUP = {
|
|
41
|
+
type: 'generic',
|
|
42
|
+
name: 'acme-setup',
|
|
43
|
+
title: 'Acme setup',
|
|
44
|
+
description: 'Set up the Acme kit.',
|
|
45
|
+
sections: [
|
|
46
|
+
{
|
|
47
|
+
id: 'manifest',
|
|
48
|
+
title: 'Point the manifest at your folders',
|
|
49
|
+
content: [
|
|
50
|
+
{type: 'prose', text: 'Add these fields to astryx.integration.mjs.'},
|
|
51
|
+
MANIFEST_FIELDS,
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'check',
|
|
56
|
+
title: 'Check the docs',
|
|
57
|
+
content: [
|
|
58
|
+
{type: 'prose', text: 'Run the check before you publish.'},
|
|
59
|
+
{
|
|
60
|
+
type: 'reference',
|
|
61
|
+
target: '@astryxdesign/cli:command:doctor integration docs',
|
|
62
|
+
presentation: 'compact',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'more',
|
|
68
|
+
title: 'Read more',
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: 'reference',
|
|
72
|
+
target: '@astryxdesign/cli:function:docs',
|
|
73
|
+
presentation: 'summary',
|
|
74
|
+
},
|
|
75
|
+
{type: 'reference', target: '@astryxdesign/cli:generic:theme'},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** A topic whose reference blocks each name something they cannot include. */
|
|
82
|
+
const BROKEN = {
|
|
83
|
+
type: 'generic',
|
|
84
|
+
name: 'acme-broken',
|
|
85
|
+
title: 'Acme broken',
|
|
86
|
+
description: 'References that include nothing.',
|
|
87
|
+
sections: [
|
|
88
|
+
{
|
|
89
|
+
id: 'gaps',
|
|
90
|
+
title: 'Gaps',
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: 'reference',
|
|
94
|
+
target: '@astryxdesign/cli:schema:integration',
|
|
95
|
+
projection: {fields: ['components', 'nope']},
|
|
96
|
+
},
|
|
97
|
+
{type: 'reference', target: '@astryxdesign/cli:schema:nothing'},
|
|
98
|
+
{
|
|
99
|
+
type: 'reference',
|
|
100
|
+
target: '@astryxdesign/cli:command:doctor',
|
|
101
|
+
projection: {fields: ['--json']},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
type: 'reference',
|
|
105
|
+
target: '@astryxdesign/cli:schema:config',
|
|
106
|
+
projection: {sections: ['fields']},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: 'reference',
|
|
110
|
+
target: '@astryxdesign/cli:generic:theme',
|
|
111
|
+
presentation: 'full',
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** @type {string} */
|
|
119
|
+
let tmpDir;
|
|
120
|
+
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-reference-test-'));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
afterEach(() => {
|
|
126
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A consumer project that configures `@acme/kit`, which ships these docs.
|
|
131
|
+
* @param {Record<string, object>} files doc file name -> doc
|
|
132
|
+
*/
|
|
133
|
+
function scaffold(files) {
|
|
134
|
+
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name": "consumer"}\n');
|
|
135
|
+
fs.writeFileSync(
|
|
136
|
+
path.join(tmpDir, 'astryx.config.mjs'),
|
|
137
|
+
"export default {integrations: ['@acme/kit']};\n",
|
|
138
|
+
);
|
|
139
|
+
const kit = path.join(tmpDir, 'node_modules', '@acme', 'kit');
|
|
140
|
+
fs.mkdirSync(path.join(kit, 'docs'), {recursive: true});
|
|
141
|
+
fs.writeFileSync(
|
|
142
|
+
path.join(kit, 'package.json'),
|
|
143
|
+
JSON.stringify({name: '@acme/kit', version: '1.0.0'}),
|
|
144
|
+
);
|
|
145
|
+
fs.writeFileSync(
|
|
146
|
+
path.join(kit, 'astryx.integration.mjs'),
|
|
147
|
+
"export default {docs: './docs'};\n",
|
|
148
|
+
);
|
|
149
|
+
for (const [file, doc] of Object.entries(files)) {
|
|
150
|
+
fs.writeFileSync(
|
|
151
|
+
path.join(kit, 'docs', file),
|
|
152
|
+
`export const docs = ${JSON.stringify(doc, null, 2)};\n`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {string} topic
|
|
159
|
+
* @param {string} [section]
|
|
160
|
+
*/
|
|
161
|
+
async function read(topic, section) {
|
|
162
|
+
return (await docs(topic, section, {cwd: tmpDir})).data;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
describe('a reference block in a topic section', () => {
|
|
166
|
+
it(
|
|
167
|
+
'includes the fields a schema reference names, then where they come from',
|
|
168
|
+
async () => {
|
|
169
|
+
scaffold({'acme-setup.doc.mjs': SETUP});
|
|
170
|
+
const section = await read('acme-setup', 'manifest');
|
|
171
|
+
expect(section.content.map(block => block.type)).toEqual([
|
|
172
|
+
'prose',
|
|
173
|
+
'table',
|
|
174
|
+
'prose',
|
|
175
|
+
]);
|
|
176
|
+
const table = section.content[1];
|
|
177
|
+
expect(table.headers).toEqual([
|
|
178
|
+
'Field',
|
|
179
|
+
'Type',
|
|
180
|
+
'Required',
|
|
181
|
+
'Description',
|
|
182
|
+
]);
|
|
183
|
+
// The fields the block names, in the order it names them.
|
|
184
|
+
expect(table.rows.map(row => row[0])).toEqual(['components', 'docs']);
|
|
185
|
+
expect(section.content[2]).toEqual({
|
|
186
|
+
type: 'prose',
|
|
187
|
+
text: 'From Astryx Integration: `astryx docs authoring integration`',
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
SLOW,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
it(
|
|
194
|
+
"reads the included fields from the schema's own doc, not a copy",
|
|
195
|
+
async () => {
|
|
196
|
+
scaffold({'acme-setup.doc.mjs': SETUP});
|
|
197
|
+
const included = (await read('acme-setup', 'manifest')).content[1].rows;
|
|
198
|
+
const schema = (await docs('authoring', 'integration')).data.content.find(
|
|
199
|
+
block => block.type === 'table',
|
|
200
|
+
);
|
|
201
|
+
expect(included).toEqual(
|
|
202
|
+
MANIFEST_FIELDS.projection.fields.map(name =>
|
|
203
|
+
schema.rows.find(row => row[0] === name),
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
SLOW,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
it(
|
|
211
|
+
'returns only the stable block kinds in every read, in JSON and in each language',
|
|
212
|
+
async () => {
|
|
213
|
+
scaffold({'acme-setup.doc.mjs': SETUP});
|
|
214
|
+
for (const lang of [null, 'zh']) {
|
|
215
|
+
const whole = (await docs('acme-setup', undefined, {cwd: tmpDir, lang}))
|
|
216
|
+
.data;
|
|
217
|
+
const kinds = new Set(
|
|
218
|
+
whole.sections.flatMap(section => section.content.map(b => b.type)),
|
|
219
|
+
);
|
|
220
|
+
expect([...kinds].filter(kind => !READ_KINDS.has(kind))).toEqual([]);
|
|
221
|
+
for (const {id} of whole.sections) {
|
|
222
|
+
const one = (await docs('acme-setup', id, {cwd: tmpDir, lang})).data;
|
|
223
|
+
expect(one.content.every(block => READ_KINDS.has(block.type))).toBe(
|
|
224
|
+
true,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
SLOW,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
it(
|
|
233
|
+
'includes a whole command doc, without code when compact; a summary and a topic show as one line',
|
|
234
|
+
async () => {
|
|
235
|
+
scaffold({'acme-setup.doc.mjs': SETUP});
|
|
236
|
+
const check = (await read('acme-setup', 'check')).content;
|
|
237
|
+
expect(check.some(block => block.type === 'code')).toBe(false);
|
|
238
|
+
expect(check.find(block => block.type === 'table')?.headers).toEqual([
|
|
239
|
+
'Argument',
|
|
240
|
+
'Description',
|
|
241
|
+
]);
|
|
242
|
+
// The included doc's own links resolve against its own provider.
|
|
243
|
+
expect(
|
|
244
|
+
check.some(
|
|
245
|
+
block =>
|
|
246
|
+
block.type === 'prose' &&
|
|
247
|
+
block.text.includes(
|
|
248
|
+
'Read it with `astryx docs cli/api/functions/integration-doc-conflicts`.',
|
|
249
|
+
),
|
|
250
|
+
),
|
|
251
|
+
).toBe(true);
|
|
252
|
+
expect(check.at(-1)).toEqual({
|
|
253
|
+
type: 'prose',
|
|
254
|
+
text: 'From astryx doctor integration docs: `astryx docs cli/commands/doctor-integration-docs`',
|
|
255
|
+
});
|
|
256
|
+
const more = (await read('acme-setup', 'more')).content;
|
|
257
|
+
expect(more).toHaveLength(2);
|
|
258
|
+
expect(more[0].text).toMatch(
|
|
259
|
+
/^docs\(\): Read the reference docs: .* Read it with `astryx docs cli\/api\/functions\/docs`\.$/,
|
|
260
|
+
);
|
|
261
|
+
expect(more[1].text).toMatch(
|
|
262
|
+
/^Theme System: .* Read it with `astryx docs theme`\.$/,
|
|
263
|
+
);
|
|
264
|
+
},
|
|
265
|
+
SLOW,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
it(
|
|
269
|
+
'prints the included content in text',
|
|
270
|
+
async () => {
|
|
271
|
+
scaffold({'acme-setup.doc.mjs': SETUP});
|
|
272
|
+
const {status, stdout} = await runCli(
|
|
273
|
+
['docs', 'acme-setup', 'manifest'],
|
|
274
|
+
tmpDir,
|
|
275
|
+
);
|
|
276
|
+
expect(status).toBe(0);
|
|
277
|
+
expect(stdout).toMatch(/^Field +\| Type +\| Required +\| Description/m);
|
|
278
|
+
expect(stdout).toMatch(/^components +\| string +\| no +\| /m);
|
|
279
|
+
expect(stdout).toContain(
|
|
280
|
+
'From Astryx Integration: `astryx docs authoring integration`',
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
SLOW,
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('a reference block that includes what it names', () => {
|
|
288
|
+
it(
|
|
289
|
+
'passes the authoring check',
|
|
290
|
+
async () => {
|
|
291
|
+
scaffold({'acme-setup.doc.mjs': SETUP});
|
|
292
|
+
const {data} = await integrationDocConflicts('@acme/kit', {cwd: tmpDir});
|
|
293
|
+
expect(data.issues).toEqual([]);
|
|
294
|
+
const cli = await runCli(
|
|
295
|
+
['doctor', 'integration', 'docs', '@acme/kit'],
|
|
296
|
+
tmpDir,
|
|
297
|
+
);
|
|
298
|
+
expect(cli.status).toBe(0);
|
|
299
|
+
},
|
|
300
|
+
SLOW,
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('what a reference block names', () => {
|
|
305
|
+
it('resolves a CLI authoring schema by identity, to its section of `astryx docs authoring`', async () => {
|
|
306
|
+
const resolve = await linkResolver(builtinCatalog(), CLI_PROVIDER_ID);
|
|
307
|
+
expect(await resolve('schema:integration')).toMatchObject({
|
|
308
|
+
id: createDocId(CLI_PROVIDER_ID, 'schema', 'integration'),
|
|
309
|
+
route: 'authoring',
|
|
310
|
+
title: 'Astryx Integration',
|
|
311
|
+
command: 'astryx docs authoring integration',
|
|
312
|
+
});
|
|
313
|
+
const opened = await docs('authoring', 'integration');
|
|
314
|
+
expect(opened.type).toBe('docs.detail.section');
|
|
315
|
+
expect(opened.data.id).toBe('integration');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it(
|
|
319
|
+
"leaves no reference in the CLI's own docs that cannot include what it names",
|
|
320
|
+
async () => {
|
|
321
|
+
const catalog = builtinCatalog();
|
|
322
|
+
expect(
|
|
323
|
+
await docsLinkProblems(catalog, await projectTree(catalog), {
|
|
324
|
+
references: true,
|
|
325
|
+
}),
|
|
326
|
+
).toEqual([]);
|
|
327
|
+
},
|
|
328
|
+
SLOW,
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('a reference block that cannot include what it names', () => {
|
|
333
|
+
it(
|
|
334
|
+
'fails the authoring check, once for each part it cannot include',
|
|
335
|
+
async () => {
|
|
336
|
+
scaffold({'acme-broken.doc.mjs': BROKEN});
|
|
337
|
+
const {data} = await integrationDocConflicts('@acme/kit', {cwd: tmpDir});
|
|
338
|
+
const errors = data.issues.filter(
|
|
339
|
+
issue => issue.code === 'invalid_doc_reference',
|
|
340
|
+
);
|
|
341
|
+
expect(errors.every(issue => issue.severity === 'error')).toBe(true);
|
|
342
|
+
const messages = errors.map(issue => issue.message);
|
|
343
|
+
expect(messages).toHaveLength(5);
|
|
344
|
+
expect(messages[0]).toMatch(
|
|
345
|
+
/^acme-broken § gaps: projection\.fields: "nope" is not a field of Astryx Integration/,
|
|
346
|
+
);
|
|
347
|
+
expect(messages[1]).toMatch(
|
|
348
|
+
/"@astryxdesign\/cli:schema:nothing" names no doc/,
|
|
349
|
+
);
|
|
350
|
+
expect(messages[2]).toMatch(
|
|
351
|
+
/projection\.fields: names the fields of a schema doc/,
|
|
352
|
+
);
|
|
353
|
+
expect(messages[3]).toMatch(/projection\.sections: /);
|
|
354
|
+
expect(messages[4]).toMatch(
|
|
355
|
+
/"@astryxdesign\/cli:generic:theme" is a topic/,
|
|
356
|
+
);
|
|
357
|
+
// They are not also reported as links that print as written.
|
|
358
|
+
expect(
|
|
359
|
+
data.issues.filter(issue => issue.code === 'invalid_doc_graph'),
|
|
360
|
+
).toEqual([]);
|
|
361
|
+
const cli = await runCli(
|
|
362
|
+
['doctor', 'integration', 'docs', '@acme/kit'],
|
|
363
|
+
tmpDir,
|
|
364
|
+
);
|
|
365
|
+
expect(cli.status).toBe(1);
|
|
366
|
+
},
|
|
367
|
+
SLOW,
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
it(
|
|
371
|
+
'marks the missing content where a read would show it',
|
|
372
|
+
async () => {
|
|
373
|
+
scaffold({'acme-broken.doc.mjs': BROKEN});
|
|
374
|
+
const texts = (await read('acme-broken', 'gaps')).content
|
|
375
|
+
.filter(block => block.type === 'prose')
|
|
376
|
+
.map(block => block.text);
|
|
377
|
+
expect(texts).toContain(
|
|
378
|
+
'[reference: field "nope" not found in "@astryxdesign/cli:schema:integration"]',
|
|
379
|
+
);
|
|
380
|
+
expect(texts).toContain(
|
|
381
|
+
'[reference: "@astryxdesign/cli:schema:nothing" names no doc]',
|
|
382
|
+
);
|
|
383
|
+
// The field it could include is still there.
|
|
384
|
+
const table = (await read('acme-broken', 'gaps')).content.find(
|
|
385
|
+
block => block.type === 'table',
|
|
386
|
+
);
|
|
387
|
+
expect(table.rows.map(row => row[0])).toEqual(['components']);
|
|
388
|
+
},
|
|
389
|
+
SLOW,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
it(
|
|
393
|
+
"warns in a project's doctor, which an app cannot fix",
|
|
394
|
+
async () => {
|
|
395
|
+
scaffold({'acme-broken.doc.mjs': BROKEN});
|
|
396
|
+
const check = await checkDocsTree({
|
|
397
|
+
docsCatalog: await loadDocsCatalog(tmpDir),
|
|
398
|
+
});
|
|
399
|
+
expect(check.status).toBe('warn');
|
|
400
|
+
expect(check.message).toContain(
|
|
401
|
+
'"nope" is not a field of Astryx Integration',
|
|
402
|
+
);
|
|
403
|
+
},
|
|
404
|
+
SLOW,
|
|
405
|
+
);
|
|
406
|
+
});
|