@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
|
@@ -27,6 +27,7 @@ const NODE_FIELDS = new Set([
|
|
|
27
27
|
'lang',
|
|
28
28
|
'provenance',
|
|
29
29
|
'sourceTitles',
|
|
30
|
+
'sectionProviders',
|
|
30
31
|
'doc',
|
|
31
32
|
]);
|
|
32
33
|
const RESOLVED_FIELDS = new Set([
|
|
@@ -170,6 +171,12 @@ function structureProblems(node) {
|
|
|
170
171
|
'provenance: expected {provider, replaces, extensions} naming packages, not paths',
|
|
171
172
|
);
|
|
172
173
|
}
|
|
174
|
+
const providers = isRecord(node.sectionProviders)
|
|
175
|
+
? node.sectionProviders
|
|
176
|
+
: null;
|
|
177
|
+
if (!providers || !Object.values(providers).every(isText)) {
|
|
178
|
+
problems.push('sectionProviders: expected section key -> provider id');
|
|
179
|
+
}
|
|
173
180
|
const titles = isRecord(node.sourceTitles) ? node.sourceTitles : null;
|
|
174
181
|
if (!titles || !Object.values(titles).every(isText)) {
|
|
175
182
|
problems.push('sourceTitles: expected section key -> authored title');
|
|
@@ -18,14 +18,17 @@ export function readerSections(node: import("./compile.mjs").CompiledReferenceNo
|
|
|
18
18
|
export function detailView(node: import("./compile.mjs").CompiledReferenceNode): any;
|
|
19
19
|
/**
|
|
20
20
|
* `docs.index`: what the topic is, and each section's key, title and summary.
|
|
21
|
+
* The index leaf adds the moves.
|
|
21
22
|
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
22
|
-
* @returns {import('../../api/docs/docs.type.mjs').DocsIndex}
|
|
23
|
+
* @returns {Omit<import('../../api/docs/docs.type.mjs').DocsIndex, 'links'>}
|
|
23
24
|
*/
|
|
24
|
-
export function indexView(node: import("./compile.mjs").CompiledReferenceNode): import("../../api/docs/docs.type.mjs").DocsIndex
|
|
25
|
+
export function indexView(node: import("./compile.mjs").CompiledReferenceNode): Omit<import("../../api/docs/docs.type.mjs").DocsIndex, "links">;
|
|
25
26
|
/**
|
|
26
|
-
* `docs.detail.section`: one section with its token references
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* `docs.detail.section`: one section with its token references and reference
|
|
28
|
+
* blocks inlined, so a read returns only the stable block kinds. A referenced
|
|
29
|
+
* section's content takes the token reference's place; the section takes the
|
|
30
|
+
* preview type of the last reference that has one, unless it has its own. A
|
|
31
|
+
* reference block becomes the content it includes (see referenceView).
|
|
29
32
|
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
30
33
|
* @param {any} section a linked section of `node`
|
|
31
34
|
* @returns {any}
|
|
@@ -62,17 +62,20 @@ export function detailView(node) {
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* `docs.index`: what the topic is, and each section's key, title and summary.
|
|
65
|
+
* The index leaf adds the moves.
|
|
65
66
|
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
66
|
-
* @returns {import('../../api/docs/docs.type.mjs').DocsIndex}
|
|
67
|
+
* @returns {Omit<import('../../api/docs/docs.type.mjs').DocsIndex, 'links'>}
|
|
67
68
|
*/
|
|
68
69
|
export function indexView(node) {
|
|
69
70
|
return buildDocsIndexData(node.doc);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
|
-
* `docs.detail.section`: one section with its token references
|
|
74
|
-
*
|
|
75
|
-
*
|
|
74
|
+
* `docs.detail.section`: one section with its token references and reference
|
|
75
|
+
* blocks inlined, so a read returns only the stable block kinds. A referenced
|
|
76
|
+
* section's content takes the token reference's place; the section takes the
|
|
77
|
+
* preview type of the last reference that has one, unless it has its own. A
|
|
78
|
+
* reference block becomes the content it includes (see referenceView).
|
|
76
79
|
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
77
80
|
* @param {any} section a linked section of `node`
|
|
78
81
|
* @returns {any}
|
|
@@ -83,6 +86,10 @@ export function sectionView(node, section) {
|
|
|
83
86
|
/** @type {string | null} */
|
|
84
87
|
let previewType = null;
|
|
85
88
|
for (const block of section.content) {
|
|
89
|
+
if (block?.type === 'reference') {
|
|
90
|
+
content.push(...referenceView(node, block));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
86
93
|
if (block?.type !== 'token-ref') {
|
|
87
94
|
content.push(structuredClone(block));
|
|
88
95
|
continue;
|
|
@@ -125,3 +132,42 @@ export function sectionView(node, section) {
|
|
|
125
132
|
view.content = content;
|
|
126
133
|
return withSourceTitle(view, authoredTitle(node, section));
|
|
127
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A reference block as a read shows it (spec:AST-047 FR9): the content it
|
|
138
|
+
* includes, then where that content comes from and the command that opens it.
|
|
139
|
+
* A reference that includes nothing (a summary, or a doc that is not included
|
|
140
|
+
* whole) shows the doc's title and summary; one whose doc is missing says so
|
|
141
|
+
* where its content would be, as a token reference does.
|
|
142
|
+
* @param {import('./compile.mjs').CompiledReferenceNode} node
|
|
143
|
+
* @param {any} block a linked reference block
|
|
144
|
+
* @returns {any[]}
|
|
145
|
+
*/
|
|
146
|
+
function referenceView(node, block) {
|
|
147
|
+
if (!('link' in block)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`The reference to "${block.target}" in "${node.id}" was read before it was linked.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
const link = block.link;
|
|
153
|
+
if (link == null) {
|
|
154
|
+
return [
|
|
155
|
+
{type: 'prose', text: `[reference: "${block.target}" names no doc]`},
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
const included = (block.content ?? []).map((/** @type {any} */ each) =>
|
|
159
|
+
structuredClone(each),
|
|
160
|
+
);
|
|
161
|
+
if (included.length === 0) {
|
|
162
|
+
const summary = String(link.summary ?? '').trim();
|
|
163
|
+
const lead =
|
|
164
|
+
summary === ''
|
|
165
|
+
? link.title
|
|
166
|
+
: `${link.title}: ${summary}${/[.!?]$/.test(summary) ? '' : '.'}`;
|
|
167
|
+
return [{type: 'prose', text: `${lead} Read it with \`${link.command}\`.`}];
|
|
168
|
+
}
|
|
169
|
+
return [
|
|
170
|
+
...included,
|
|
171
|
+
{type: 'prose', text: `From ${link.title}: \`${link.command}\``},
|
|
172
|
+
];
|
|
173
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
* @typedef {object} LinkTarget
|
|
6
|
+
* @property {string | null} provider the provider id it names, or null for the
|
|
7
|
+
* linking doc's own provider
|
|
8
|
+
* @property {string} kind
|
|
9
|
+
* @property {string} name
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The doc a link opens, as a docs read returns it.
|
|
13
|
+
* @typedef {object} DocLink
|
|
14
|
+
* @property {string} target the target as written: `[<provider>:]<kind>:<name>`
|
|
15
|
+
* @property {string} id the doc's identity (a DocId)
|
|
16
|
+
* @property {string} route what `astryx docs <route>` opens
|
|
17
|
+
* @property {string} title
|
|
18
|
+
* @property {string} summary
|
|
19
|
+
* @property {string} command the command that opens it, `astryx docs <route>`
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Finds the doc a target names, or says why none matches.
|
|
23
|
+
* @typedef {(target: string) => Promise<DocLink | {problem: string}>} LinkResolver
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} LinkProblem
|
|
27
|
+
* @property {string} target the target as written
|
|
28
|
+
* @property {string} message
|
|
29
|
+
* @property {string} [section] the key of the section the link sits in
|
|
30
|
+
* @property {true} [include] the problem is in a `reference` block, which
|
|
31
|
+
* includes content rather than linking to it: a reader loses that content,
|
|
32
|
+
* so the authoring check fails on it
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* What a `reference` block in a topic section includes of the doc it names:
|
|
36
|
+
* stable content blocks, with anything it could not include named in
|
|
37
|
+
* `problems`.
|
|
38
|
+
* @typedef {(block: any) => Promise<{content: any[], problems: string[]}>} DocIncluder
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Parse `[<provider>:]<kind>:<name>`. A name may hold spaces
|
|
42
|
+
* (`command:theme build`); no part holds a colon.
|
|
43
|
+
* @param {unknown} target
|
|
44
|
+
* @returns {LinkTarget | {error: string}}
|
|
45
|
+
*/
|
|
46
|
+
export function parseLinkTarget(target: unknown): LinkTarget | {
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Text with each inline link replaced by its name, for indexes that must not
|
|
51
|
+
* read the link syntax as words.
|
|
52
|
+
* @param {string} text
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
export function unlinkText(text: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve every link in a list of content blocks, in order.
|
|
58
|
+
* @param {any[]} blocks
|
|
59
|
+
* @param {LinkResolver} resolve
|
|
60
|
+
* @param {{section?: string}} [at]
|
|
61
|
+
* @param {DocIncluder} [include] how a `reference` block includes the doc it
|
|
62
|
+
* names; without it, the block only carries its `link`
|
|
63
|
+
* @returns {Promise<{content: any[], problems: LinkProblem[]}>}
|
|
64
|
+
*/
|
|
65
|
+
export function linkBlocks(blocks: any[], resolve: LinkResolver, at?: {
|
|
66
|
+
section?: string;
|
|
67
|
+
}, include?: DocIncluder): Promise<{
|
|
68
|
+
content: any[];
|
|
69
|
+
problems: LinkProblem[];
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Resolve every link in a topic's sections.
|
|
73
|
+
* @param {any[]} sections
|
|
74
|
+
* @param {LinkResolver} resolve
|
|
75
|
+
* @returns {Promise<{sections: any[], problems: LinkProblem[]}>}
|
|
76
|
+
*/
|
|
77
|
+
export function linkSections(sections: any[], resolve: LinkResolver): Promise<{
|
|
78
|
+
sections: any[];
|
|
79
|
+
problems: LinkProblem[];
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* @file Links between docs, named by doc identity (spec:AST-047 FR9).
|
|
83
|
+
*
|
|
84
|
+
* @input A link target, `[<provider>:]<kind>:<name>`, written as a `reference`
|
|
85
|
+
* block, in a `workflow` step's `references`, or inline in text as
|
|
86
|
+
* `{@link <target>}`; and a resolver that finds the doc a target names.
|
|
87
|
+
* @output The same content with every link resolved: an inline link becomes
|
|
88
|
+
* the `astryx docs` command that opens its doc, and a block carries a `link`
|
|
89
|
+
* with the doc's identity, route, title, summary, and command. A reference
|
|
90
|
+
* block in a topic section also carries the `content` it includes. A link
|
|
91
|
+
* that names no doc is a problem, never a guess.
|
|
92
|
+
* @position Pure. The docs adapter owns the resolver (the project's docs tree
|
|
93
|
+
* and topic catalog) and applies this to every read and every doctor check.
|
|
94
|
+
*/
|
|
95
|
+
/** The kinds a link can name: every authored doc kind. */
|
|
96
|
+
export const LINK_KINDS: Set<string>;
|
|
97
|
+
/** An inline link in text: `{@link <target>}`. */
|
|
98
|
+
export const INLINE_LINK: RegExp;
|
|
99
|
+
export type LinkTarget = {
|
|
100
|
+
/**
|
|
101
|
+
* the provider id it names, or null for the
|
|
102
|
+
* linking doc's own provider
|
|
103
|
+
*/
|
|
104
|
+
provider: string | null;
|
|
105
|
+
kind: string;
|
|
106
|
+
name: string;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* The doc a link opens, as a docs read returns it.
|
|
110
|
+
*/
|
|
111
|
+
export type DocLink = {
|
|
112
|
+
/**
|
|
113
|
+
* the target as written: `[<provider>:]<kind>:<name>`
|
|
114
|
+
*/
|
|
115
|
+
target: string;
|
|
116
|
+
/**
|
|
117
|
+
* the doc's identity (a DocId)
|
|
118
|
+
*/
|
|
119
|
+
id: string;
|
|
120
|
+
/**
|
|
121
|
+
* what `astryx docs <route>` opens
|
|
122
|
+
*/
|
|
123
|
+
route: string;
|
|
124
|
+
title: string;
|
|
125
|
+
summary: string;
|
|
126
|
+
/**
|
|
127
|
+
* the command that opens it, `astryx docs <route>`
|
|
128
|
+
*/
|
|
129
|
+
command: string;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Finds the doc a target names, or says why none matches.
|
|
133
|
+
*/
|
|
134
|
+
export type LinkResolver = (target: string) => Promise<DocLink | {
|
|
135
|
+
problem: string;
|
|
136
|
+
}>;
|
|
137
|
+
export type LinkProblem = {
|
|
138
|
+
/**
|
|
139
|
+
* the target as written
|
|
140
|
+
*/
|
|
141
|
+
target: string;
|
|
142
|
+
message: string;
|
|
143
|
+
/**
|
|
144
|
+
* the key of the section the link sits in
|
|
145
|
+
*/
|
|
146
|
+
section?: string | undefined;
|
|
147
|
+
/**
|
|
148
|
+
* the problem is in a `reference` block, which
|
|
149
|
+
* includes content rather than linking to it: a reader loses that content,
|
|
150
|
+
* so the authoring check fails on it
|
|
151
|
+
*/
|
|
152
|
+
include?: true | undefined;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* What a `reference` block in a topic section includes of the doc it names:
|
|
156
|
+
* stable content blocks, with anything it could not include named in
|
|
157
|
+
* `problems`.
|
|
158
|
+
*/
|
|
159
|
+
export type DocIncluder = (block: any) => Promise<{
|
|
160
|
+
content: any[];
|
|
161
|
+
problems: string[];
|
|
162
|
+
}>;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Links between docs, named by doc identity (spec:AST-047 FR9).
|
|
5
|
+
*
|
|
6
|
+
* @input A link target, `[<provider>:]<kind>:<name>`, written as a `reference`
|
|
7
|
+
* block, in a `workflow` step's `references`, or inline in text as
|
|
8
|
+
* `{@link <target>}`; and a resolver that finds the doc a target names.
|
|
9
|
+
* @output The same content with every link resolved: an inline link becomes
|
|
10
|
+
* the `astryx docs` command that opens its doc, and a block carries a `link`
|
|
11
|
+
* with the doc's identity, route, title, summary, and command. A reference
|
|
12
|
+
* block in a topic section also carries the `content` it includes. A link
|
|
13
|
+
* that names no doc is a problem, never a guess.
|
|
14
|
+
* @position Pure. The docs adapter owns the resolver (the project's docs tree
|
|
15
|
+
* and topic catalog) and applies this to every read and every doctor check.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** The kinds a link can name: every authored doc kind. */
|
|
19
|
+
export const LINK_KINDS = new Set([
|
|
20
|
+
'generic',
|
|
21
|
+
'command',
|
|
22
|
+
'function',
|
|
23
|
+
'schema',
|
|
24
|
+
'enum',
|
|
25
|
+
'namespace',
|
|
26
|
+
'component',
|
|
27
|
+
'page',
|
|
28
|
+
'block',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/** An inline link in text: `{@link <target>}`. */
|
|
32
|
+
export const INLINE_LINK = /\{@link\s+([^{}]+?)\s*\}/g;
|
|
33
|
+
|
|
34
|
+
/** Code spans in text, which show link syntax as written. */
|
|
35
|
+
const CODE_SPAN = /(`[^`]*`)/;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} LinkTarget
|
|
39
|
+
* @property {string | null} provider the provider id it names, or null for the
|
|
40
|
+
* linking doc's own provider
|
|
41
|
+
* @property {string} kind
|
|
42
|
+
* @property {string} name
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The doc a link opens, as a docs read returns it.
|
|
47
|
+
* @typedef {object} DocLink
|
|
48
|
+
* @property {string} target the target as written: `[<provider>:]<kind>:<name>`
|
|
49
|
+
* @property {string} id the doc's identity (a DocId)
|
|
50
|
+
* @property {string} route what `astryx docs <route>` opens
|
|
51
|
+
* @property {string} title
|
|
52
|
+
* @property {string} summary
|
|
53
|
+
* @property {string} command the command that opens it, `astryx docs <route>`
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Finds the doc a target names, or says why none matches.
|
|
58
|
+
* @typedef {(target: string) => Promise<DocLink | {problem: string}>} LinkResolver
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {object} LinkProblem
|
|
63
|
+
* @property {string} target the target as written
|
|
64
|
+
* @property {string} message
|
|
65
|
+
* @property {string} [section] the key of the section the link sits in
|
|
66
|
+
* @property {true} [include] the problem is in a `reference` block, which
|
|
67
|
+
* includes content rather than linking to it: a reader loses that content,
|
|
68
|
+
* so the authoring check fails on it
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* What a `reference` block in a topic section includes of the doc it names:
|
|
73
|
+
* stable content blocks, with anything it could not include named in
|
|
74
|
+
* `problems`.
|
|
75
|
+
* @typedef {(block: any) => Promise<{content: any[], problems: string[]}>} DocIncluder
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse `[<provider>:]<kind>:<name>`. A name may hold spaces
|
|
80
|
+
* (`command:theme build`); no part holds a colon.
|
|
81
|
+
* @param {unknown} target
|
|
82
|
+
* @returns {LinkTarget | {error: string}}
|
|
83
|
+
*/
|
|
84
|
+
export function parseLinkTarget(target) {
|
|
85
|
+
if (typeof target !== 'string' || target.trim() === '') {
|
|
86
|
+
return {
|
|
87
|
+
error:
|
|
88
|
+
'a link target is a non-empty string: `[<provider>:]<kind>:<name>`',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const parts = target.trim().split(':');
|
|
92
|
+
if (parts.length < 2 || parts.length > 3 || parts.some(p => p.trim() === '')) {
|
|
93
|
+
return {error: `"${target}" is not \`[<provider>:]<kind>:<name>\``};
|
|
94
|
+
}
|
|
95
|
+
const provider = parts.length === 3 ? parts[0].trim() : null;
|
|
96
|
+
const kind = parts[parts.length - 2].trim();
|
|
97
|
+
const name = parts[parts.length - 1].trim();
|
|
98
|
+
if (!LINK_KINDS.has(kind)) {
|
|
99
|
+
return {
|
|
100
|
+
error: `"${target}" names the kind "${kind}"; a doc kind is one of ${[
|
|
101
|
+
...LINK_KINDS,
|
|
102
|
+
].join(', ')}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {provider, kind, name};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Text with each inline link replaced by its name, for indexes that must not
|
|
110
|
+
* read the link syntax as words.
|
|
111
|
+
* @param {string} text
|
|
112
|
+
* @returns {string}
|
|
113
|
+
*/
|
|
114
|
+
export function unlinkText(text) {
|
|
115
|
+
return text
|
|
116
|
+
.split(CODE_SPAN)
|
|
117
|
+
.map(part =>
|
|
118
|
+
part.startsWith('`')
|
|
119
|
+
? part
|
|
120
|
+
: part.replace(INLINE_LINK, (_, target) => {
|
|
121
|
+
const parsed = parseLinkTarget(target);
|
|
122
|
+
return 'error' in parsed ? target : parsed.name;
|
|
123
|
+
}),
|
|
124
|
+
)
|
|
125
|
+
.join('');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve every link in a list of content blocks, in order.
|
|
130
|
+
* @param {any[]} blocks
|
|
131
|
+
* @param {LinkResolver} resolve
|
|
132
|
+
* @param {{section?: string}} [at]
|
|
133
|
+
* @param {DocIncluder} [include] how a `reference` block includes the doc it
|
|
134
|
+
* names; without it, the block only carries its `link`
|
|
135
|
+
* @returns {Promise<{content: any[], problems: LinkProblem[]}>}
|
|
136
|
+
*/
|
|
137
|
+
export async function linkBlocks(blocks, resolve, at = {}, include) {
|
|
138
|
+
/** @type {LinkProblem[]} */
|
|
139
|
+
const problems = [];
|
|
140
|
+
const where = at.section ? {section: at.section} : {};
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} target
|
|
143
|
+
* @returns {Promise<DocLink | null>}
|
|
144
|
+
*/
|
|
145
|
+
const find = async target => {
|
|
146
|
+
const found = await resolve(target);
|
|
147
|
+
if ('problem' in found) {
|
|
148
|
+
problems.push({target, message: found.problem, ...where});
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return found;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* @param {unknown} text
|
|
155
|
+
* @returns {Promise<any>}
|
|
156
|
+
*/
|
|
157
|
+
const linkText = async text => {
|
|
158
|
+
if (typeof text !== 'string' || !text.includes('{@link')) return text;
|
|
159
|
+
let out = '';
|
|
160
|
+
// A link written inside code ticks is shown as written: it documents the
|
|
161
|
+
// syntax, it does not link.
|
|
162
|
+
for (const part of text.split(CODE_SPAN)) {
|
|
163
|
+
if (part.startsWith('`')) {
|
|
164
|
+
out += part;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
let last = 0;
|
|
168
|
+
for (const match of part.matchAll(INLINE_LINK)) {
|
|
169
|
+
const link = await find(match[1]);
|
|
170
|
+
const at = /** @type {number} */ (match.index);
|
|
171
|
+
out += part.slice(last, at);
|
|
172
|
+
// A link that names no doc prints as written: text that was never
|
|
173
|
+
// meant as a link reads the way it did, and doctor names the problem.
|
|
174
|
+
out += link ? `\`${link.command}\`` : match[0];
|
|
175
|
+
last = at + match[0].length;
|
|
176
|
+
}
|
|
177
|
+
out += part.slice(last);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* @param {unknown[]} values
|
|
183
|
+
* @returns {Promise<any[]>}
|
|
184
|
+
*/
|
|
185
|
+
const linkAll = async values => {
|
|
186
|
+
const out = [];
|
|
187
|
+
for (const value of values) out.push(await linkText(value));
|
|
188
|
+
return out;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const content = [];
|
|
192
|
+
for (const block of blocks ?? []) {
|
|
193
|
+
switch (block?.type) {
|
|
194
|
+
case 'prose':
|
|
195
|
+
case 'heading':
|
|
196
|
+
content.push({...block, text: await linkText(block.text)});
|
|
197
|
+
break;
|
|
198
|
+
case 'list':
|
|
199
|
+
content.push({...block, items: await linkAll(block.items ?? [])});
|
|
200
|
+
break;
|
|
201
|
+
case 'table': {
|
|
202
|
+
const rows = [];
|
|
203
|
+
for (const row of block.rows ?? []) rows.push(await linkAll(row));
|
|
204
|
+
content.push({
|
|
205
|
+
...block,
|
|
206
|
+
headers: await linkAll(block.headers ?? []),
|
|
207
|
+
rows,
|
|
208
|
+
});
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case 'reference': {
|
|
212
|
+
// A reference includes content, so what it cannot include is a
|
|
213
|
+
// problem the authoring check fails on, not one that prints as written.
|
|
214
|
+
const found = await resolve(block.target);
|
|
215
|
+
if ('problem' in found) {
|
|
216
|
+
problems.push({
|
|
217
|
+
target: block.target,
|
|
218
|
+
message: found.problem,
|
|
219
|
+
include: true,
|
|
220
|
+
...where,
|
|
221
|
+
});
|
|
222
|
+
content.push({...block, link: null});
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
/** @type {any} */
|
|
226
|
+
const linked = {...block, link: found};
|
|
227
|
+
if (include) {
|
|
228
|
+
const included = await include(block);
|
|
229
|
+
for (const message of included.problems) {
|
|
230
|
+
problems.push({
|
|
231
|
+
target: block.target,
|
|
232
|
+
message,
|
|
233
|
+
include: true,
|
|
234
|
+
...where,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
linked.content = included.content;
|
|
238
|
+
}
|
|
239
|
+
content.push(linked);
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
case 'workflow': {
|
|
243
|
+
const steps = [];
|
|
244
|
+
for (const step of block.steps ?? []) {
|
|
245
|
+
/** @type {any} */
|
|
246
|
+
const linked = {...step};
|
|
247
|
+
if (step.description != null) {
|
|
248
|
+
linked.description = await linkText(step.description);
|
|
249
|
+
}
|
|
250
|
+
if (Array.isArray(step.references)) {
|
|
251
|
+
const links = [];
|
|
252
|
+
for (const target of step.references) links.push(await find(target));
|
|
253
|
+
linked.links = links;
|
|
254
|
+
}
|
|
255
|
+
steps.push(linked);
|
|
256
|
+
}
|
|
257
|
+
content.push({...block, steps});
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case 'collection':
|
|
261
|
+
problems.push({
|
|
262
|
+
target: `collection:${block.source?.slot ?? ''}`,
|
|
263
|
+
message:
|
|
264
|
+
'a collection block is not rendered yet; link each doc with a reference block instead',
|
|
265
|
+
...where,
|
|
266
|
+
});
|
|
267
|
+
content.push(block);
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
content.push(block);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {content, problems};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Resolve every link in a topic's sections.
|
|
278
|
+
* @param {any[]} sections
|
|
279
|
+
* @param {LinkResolver} resolve
|
|
280
|
+
* @returns {Promise<{sections: any[], problems: LinkProblem[]}>}
|
|
281
|
+
*/
|
|
282
|
+
export async function linkSections(sections, resolve) {
|
|
283
|
+
/** @type {LinkProblem[]} */
|
|
284
|
+
const problems = [];
|
|
285
|
+
const out = [];
|
|
286
|
+
for (const section of sections ?? []) {
|
|
287
|
+
const linked = await linkBlocks(section.content, resolve, {
|
|
288
|
+
section: section.id ?? section.title,
|
|
289
|
+
});
|
|
290
|
+
problems.push(...linked.problems);
|
|
291
|
+
out.push({...section, content: linked.content});
|
|
292
|
+
}
|
|
293
|
+
return {sections: out, problems};
|
|
294
|
+
}
|