@astryxdesign/cli 0.6.3-canary.b333c39 → 0.6.3-canary.b7cc8b8
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 +27 -30
- package/api/docs/_adapter.mjs +124 -154
- package/api/docs/detail/detail.d.mts +15 -0
- package/api/docs/detail/detail.mjs +78 -14
- package/api/docs/detail/section/section.mjs +18 -22
- package/api/docs/detail/section/section.test.mjs +3 -4
- package/api/docs/index/index.mjs +5 -6
- package/api/doctor/doctor.mjs +7 -3
- package/api/search/search.mjs +5 -5
- package/api/upgrade/upgrade.doc.mjs +3 -4
- package/api/upgrade/upgrade.type.d.mts +1 -1
- package/api/upgrade/upgrade.type.mjs +1 -1
- package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +5 -27
- package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +5 -20
- package/assets/docs/cli-integrations.doc.mjs +14 -72
- package/authoring/integration/type.ts +2 -15
- package/clients/cli/commands/theme-palette-generate.doc.mjs +4 -8
- package/clients/cli/commands/upgrade.doc.mjs +2 -2
- package/package.json +9 -9
- package/api/docs/compiled-topics.test.mjs +0 -78
- package/foundation/doc-compiler/compile.d.mts +0 -162
- package/foundation/doc-compiler/compile.mjs +0 -262
- package/foundation/doc-compiler/doc-compiler.test.mjs +0 -687
- package/foundation/doc-compiler/ir.d.mts +0 -9
- package/foundation/doc-compiler/ir.mjs +0 -287
- package/foundation/doc-compiler/lenses.d.mts +0 -33
- package/foundation/doc-compiler/lenses.mjs +0 -127
package/api/docs/_adapter.d.mts
CHANGED
|
@@ -16,43 +16,40 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export function loadDocsCatalog(cwd?: string): Promise<DocsCatalog>;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
* @param {
|
|
21
|
-
* @returns {
|
|
19
|
+
* @param {string} docPath
|
|
20
|
+
* @param {{lang?: string|null}} [opts]
|
|
21
|
+
* @returns {Promise<import('./docs.type.mjs').DocsDetailResponse['data']>}
|
|
22
22
|
*/
|
|
23
|
-
export function
|
|
23
|
+
export function loadReferenceDocs(docPath: string, { lang }?: {
|
|
24
|
+
lang?: string | null;
|
|
25
|
+
}): Promise<import("./docs.type.mjs").DocsDetailResponse["data"]>;
|
|
24
26
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Load one catalog entry: its own doc, plus any extension an integration
|
|
28
|
+
* merged onto it, in configuration order.
|
|
29
|
+
*
|
|
30
|
+
* A localization overlay applies to each file before the extensions are
|
|
31
|
+
* merged, so an extension written in the base language stays readable under
|
|
32
|
+
* `--dense`/`--zh` (it replaces its own sections and leaves the rest
|
|
33
|
+
* translated) rather than being dropped.
|
|
34
|
+
*
|
|
30
35
|
* @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
|
|
31
|
-
* @param {string
|
|
32
|
-
* @returns {Promise<import('
|
|
36
|
+
* @param {{lang?: string|null}} [opts]
|
|
37
|
+
* @returns {Promise<import('./docs.type.mjs').DocsDetailResponse['data']>}
|
|
33
38
|
*/
|
|
34
|
-
export function
|
|
39
|
+
export function loadTopicDoc(entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry, { lang }?: {
|
|
40
|
+
lang?: string | null;
|
|
41
|
+
}): Promise<import("./docs.type.mjs").DocsDetailResponse["data"]>;
|
|
35
42
|
/**
|
|
36
|
-
*
|
|
37
|
-
* lowered for the same language.
|
|
38
|
-
* @param {DocsCatalog} catalog
|
|
39
|
-
* @param {string | null} lang
|
|
40
|
-
* @returns {(topic: string) => Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode | null>}
|
|
41
|
-
*/
|
|
42
|
-
export function referenceTargets(catalog: DocsCatalog, lang: string | null): (topic: string) => Promise<import("../../foundation/doc-compiler/compile.mjs").CompiledReferenceNode | null>;
|
|
43
|
-
/**
|
|
44
|
-
* One topic, compiled for `lang`: lowered, then every token reference linked.
|
|
45
|
-
* @param {DocsCatalog} catalog
|
|
43
|
+
* The overlay languages a topic ships for its own file or any extension.
|
|
46
44
|
* @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
|
|
47
|
-
* @
|
|
48
|
-
* @returns {Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>}
|
|
45
|
+
* @returns {string[]}
|
|
49
46
|
*/
|
|
50
|
-
export function
|
|
47
|
+
export function overlayLanguages(entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry): string[];
|
|
51
48
|
/**
|
|
52
49
|
* Resolve `topic` against the project's catalog (throwing `ERR_UNKNOWN_TOPIC`
|
|
53
|
-
* when unmatched) and
|
|
54
|
-
* integration extension applied. Shared by the leaves so
|
|
55
|
-
* and unknown-topic handling live in exactly one place.
|
|
50
|
+
* when unmatched), and load it with any --dense/--zh overlay and any
|
|
51
|
+
* integration extension applied. Shared by the detail and section leaves so
|
|
52
|
+
* topic normalization and unknown-topic handling live in exactly one place.
|
|
56
53
|
*
|
|
57
54
|
* @param {string} topic
|
|
58
55
|
* @param {object} [options]
|
|
@@ -62,7 +59,7 @@ export function compileTopic(catalog: DocsCatalog, entry: import("../../foundati
|
|
|
62
59
|
* @param {string} [options.cwd]
|
|
63
60
|
* @returns {Promise<{
|
|
64
61
|
* catalog: DocsCatalog,
|
|
65
|
-
*
|
|
62
|
+
* docsData: import('./docs.type.mjs').DocsDetailResponse['data'],
|
|
66
63
|
* lang: string | null,
|
|
67
64
|
* }>}
|
|
68
65
|
*/
|
|
@@ -73,7 +70,7 @@ export function resolveTopicDocs(topic: string, options?: {
|
|
|
73
70
|
cwd?: string | undefined;
|
|
74
71
|
}): Promise<{
|
|
75
72
|
catalog: DocsCatalog;
|
|
76
|
-
|
|
73
|
+
docsData: import("./docs.type.mjs").DocsDetailResponse["data"];
|
|
77
74
|
lang: string | null;
|
|
78
75
|
}>;
|
|
79
76
|
/** The localized overlays a docs read can apply. */
|
package/api/docs/_adapter.mjs
CHANGED
|
@@ -7,24 +7,29 @@
|
|
|
7
7
|
* packages/cli/assets/docs/{topic}.doc.mjs plus every topic the configured
|
|
8
8
|
* integrations contribute — and, when a --dense/--zh overlay is requested,
|
|
9
9
|
* the sibling {topic}.doc.dense.mjs / {topic}.doc.zh.mjs.
|
|
10
|
-
* @output Catalog access,
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @position Sits beside docs.mjs (api/docs/).
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* @output Catalog access, overlay- and extension-merged reference-doc data,
|
|
11
|
+
* and a combined resolve step ({catalog, docsData}) that the detail and
|
|
12
|
+
* section leaves share.
|
|
13
|
+
* @position Sits beside docs.mjs (api/docs/). Owns everything ≥2 leaves need so
|
|
14
|
+
* no leaf re-implements resolution, overlay merging, or unknown-topic
|
|
15
|
+
* handling. Discovery itself lives in foundation/discovery/docs-discovery,
|
|
16
|
+
* which api/search and the agent-docs block read through the same catalog.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import * as fs from 'node:fs';
|
|
20
20
|
import * as path from 'node:path';
|
|
21
21
|
import {pathToFileURL} from 'node:url';
|
|
22
22
|
import {Project} from '../../foundation/config/project.mjs';
|
|
23
|
-
import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
|
|
24
23
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
DocsCatalog,
|
|
25
|
+
mergeTopic,
|
|
26
|
+
problemsInTopic,
|
|
27
|
+
withSourceTitle,
|
|
28
|
+
} from '../../foundation/discovery/docs-discovery.mjs';
|
|
29
|
+
import {
|
|
30
|
+
sectionKeyProblems,
|
|
31
|
+
withSectionKeys,
|
|
32
|
+
} from '../../foundation/discovery/docs-section-key.mjs';
|
|
28
33
|
import {AstryxError} from '../error.mjs';
|
|
29
34
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
30
35
|
import {parseDoc} from '../../authoring/doctypes/parse.mjs';
|
|
@@ -51,176 +56,141 @@ export async function loadDocsCatalog(cwd = process.cwd()) {
|
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
/** The localized overlays a docs read can apply. */
|
|
55
|
-
export const OVERLAY_LANGUAGES = ['zh', 'dense'];
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Where the `lang` overlay of a doc file lives: `{topic}.doc.{lang}.mjs`.
|
|
59
|
-
* @param {string} docPath
|
|
60
|
-
* @param {string} lang
|
|
61
|
-
* @returns {string}
|
|
62
|
-
*/
|
|
63
|
-
function overlayPath(docPath, lang) {
|
|
64
|
-
return path.join(
|
|
65
|
-
path.dirname(docPath),
|
|
66
|
-
`${path.basename(docPath, '.doc.mjs')}.doc.${lang}.mjs`,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* The overlay languages a topic ships for its own file or any extension.
|
|
72
|
-
* @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
|
|
73
|
-
* @returns {string[]}
|
|
74
|
-
*/
|
|
75
|
-
export function overlayLanguages(entry) {
|
|
76
|
-
const files = [entry.path, ...entry.extensions.map(ext => ext.path)];
|
|
77
|
-
return OVERLAY_LANGUAGES.filter(lang =>
|
|
78
|
-
files.some(file => fs.existsSync(overlayPath(file, lang))),
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
59
|
/**
|
|
83
|
-
* The overlay a read applies: none for the authored language.
|
|
84
|
-
* @param {string | null | undefined} lang
|
|
85
|
-
* @returns {string | null}
|
|
86
|
-
*/
|
|
87
|
-
function overlayLanguage(lang) {
|
|
88
|
-
return lang && lang !== 'en' ? lang : null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Load one authored file and the overlay for `lang`. A failure is recorded on
|
|
93
|
-
* the result, not thrown, so the compiler reports it in reading order.
|
|
94
60
|
* @param {string} docPath
|
|
95
|
-
* @param {string
|
|
96
|
-
* @returns {Promise<import('
|
|
61
|
+
* @param {{lang?: string|null}} [opts]
|
|
62
|
+
* @returns {Promise<import('./docs.type.mjs').DocsDetailResponse['data']>}
|
|
97
63
|
*/
|
|
98
|
-
async function
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
doc = parseDoc(mod.docs ?? mod.default, file);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
return {file, error};
|
|
64
|
+
export async function loadReferenceDocs(docPath, {lang} = {}) {
|
|
65
|
+
const mod = await import(pathToFileURL(docPath).href);
|
|
66
|
+
const parsed = parseDoc(mod.docs ?? mod.default, path.basename(docPath));
|
|
67
|
+
if (!('sections' in parsed)) {
|
|
68
|
+
throw new Error(`${path.basename(docPath)} is not a reference document.`);
|
|
106
69
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
file,
|
|
114
|
-
doc,
|
|
115
|
-
overlay: translationMod.docsZh || translationMod.docsDense || null,
|
|
116
|
-
};
|
|
117
|
-
} catch (overlayError) {
|
|
118
|
-
return {file, doc, overlayError};
|
|
70
|
+
const problems = problemsInTopic(parsed);
|
|
71
|
+
if (problems.length > 0) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`${path.basename(docPath)} is invalid: ${problems.join('; ')}`,
|
|
74
|
+
);
|
|
119
75
|
}
|
|
120
|
-
|
|
76
|
+
const docs = parsed;
|
|
77
|
+
if (!lang || lang === 'en') return docs;
|
|
121
78
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
79
|
+
const translationPath = overlayPath(docPath, lang);
|
|
80
|
+
if (!fs.existsSync(translationPath)) return docs;
|
|
81
|
+
|
|
82
|
+
const translationMod = await import(pathToFileURL(translationPath).href);
|
|
83
|
+
const translation = translationMod.docsZh || translationMod.docsDense;
|
|
84
|
+
if (!translation) return docs;
|
|
85
|
+
|
|
86
|
+
// Overlays are keyed to a base section by title (`section`), not by array
|
|
87
|
+
// position. Position-keying silently grafted each overlay title onto whatever
|
|
88
|
+
// base section happened to share its index, so an overlay that omitted or
|
|
89
|
+
// reordered a section corrupted every section after it — `docs tokens --dense`
|
|
90
|
+
// printed the colour table under a "Spacing" heading (#2182). An overlay may
|
|
91
|
+
// now cover any subset of sections, in any order; sections it does not name
|
|
92
|
+
// keep their base content.
|
|
93
|
+
/** @type {Map<string, any>} */
|
|
94
|
+
const bySection = new Map();
|
|
95
|
+
for (const ts of translation.sections ?? []) {
|
|
96
|
+
if (ts?.section != null) bySection.set(ts.section, ts);
|
|
135
97
|
}
|
|
98
|
+
|
|
136
99
|
return {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
100
|
+
...docs,
|
|
101
|
+
description: translation.description || docs.description,
|
|
102
|
+
sections: docs.sections.map(
|
|
103
|
+
(
|
|
104
|
+
/** @type {import('@astryxdesign/cli/authoring').ReferenceSection} */ section,
|
|
105
|
+
) => {
|
|
106
|
+
const ts = bySection.get(section.title);
|
|
107
|
+
if (!ts) return section;
|
|
108
|
+
const localized = {
|
|
109
|
+
...section,
|
|
110
|
+
title: ts.title || section.title,
|
|
111
|
+
content: section.content.map(
|
|
112
|
+
(
|
|
113
|
+
/** @type {import('@astryxdesign/cli/authoring').ReferenceContentBlock} */ block,
|
|
114
|
+
/** @type {number} */ bi,
|
|
115
|
+
) => {
|
|
116
|
+
const tb = ts.content?.[bi];
|
|
117
|
+
if (!tb) return block;
|
|
118
|
+
if (tb.type === 'prose' && block.type === 'prose')
|
|
119
|
+
return {...block, text: tb.text};
|
|
120
|
+
if (tb.type === 'list' && block.type === 'list')
|
|
121
|
+
return {...block, items: tb.items};
|
|
122
|
+
return block;
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
};
|
|
126
|
+
return withSourceTitle(localized, section.title);
|
|
127
|
+
},
|
|
128
|
+
),
|
|
143
129
|
};
|
|
144
130
|
}
|
|
145
131
|
|
|
146
|
-
/** @type {WeakMap<DocsCatalog, Map<string, Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>>>} */
|
|
147
|
-
const loweredByCatalog = new WeakMap();
|
|
148
|
-
|
|
149
132
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
133
|
+
* Load one catalog entry: its own doc, plus any extension an integration
|
|
134
|
+
* merged onto it, in configuration order.
|
|
135
|
+
*
|
|
136
|
+
* A localization overlay applies to each file before the extensions are
|
|
137
|
+
* merged, so an extension written in the base language stays readable under
|
|
138
|
+
* `--dense`/`--zh` (it replaces its own sections and leaves the rest
|
|
139
|
+
* translated) rather than being dropped.
|
|
140
|
+
*
|
|
155
141
|
* @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
|
|
156
|
-
* @param {string
|
|
157
|
-
* @returns {Promise<import('
|
|
142
|
+
* @param {{lang?: string|null}} [opts]
|
|
143
|
+
* @returns {Promise<import('./docs.type.mjs').DocsDetailResponse['data']>}
|
|
158
144
|
*/
|
|
159
|
-
export function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
deepFreeze(lowerReferenceTopic(input)),
|
|
171
|
-
);
|
|
172
|
-
cache.set(key, lowered);
|
|
145
|
+
export async function loadTopicDoc(entry, {lang} = {}) {
|
|
146
|
+
let doc = await loadReferenceDocs(entry.path, {lang});
|
|
147
|
+
for (const extension of entry.extensions) {
|
|
148
|
+
doc = mergeTopic(doc, await loadReferenceDocs(extension.path, {lang}));
|
|
149
|
+
// Merging matches on keys, so this holds unless merge itself regresses.
|
|
150
|
+
const problems = sectionKeyProblems(doc.sections);
|
|
151
|
+
if (problems.length > 0) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`${path.basename(extension.path)}, extending ${entry.name}, leaves two sections with one key: ${problems.join('; ')}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
173
156
|
}
|
|
174
|
-
|
|
157
|
+
// Derived keys are stamped only now, so they never take part in merging.
|
|
158
|
+
return withSectionKeys(doc);
|
|
175
159
|
}
|
|
176
160
|
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
* @template T
|
|
180
|
-
* @param {T} value
|
|
181
|
-
* @returns {T}
|
|
182
|
-
*/
|
|
183
|
-
function deepFreeze(value) {
|
|
184
|
-
if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
185
|
-
Object.freeze(value);
|
|
186
|
-
for (const child of Object.values(value)) deepFreeze(child);
|
|
187
|
-
}
|
|
188
|
-
return value;
|
|
189
|
-
}
|
|
161
|
+
/** The localized overlays a docs read can apply. */
|
|
162
|
+
export const OVERLAY_LANGUAGES = ['zh', 'dense'];
|
|
190
163
|
|
|
191
164
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* @param {
|
|
195
|
-
* @
|
|
196
|
-
* @returns {(topic: string) => Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode | null>}
|
|
165
|
+
* Where the `lang` overlay of a doc file lives: `{topic}.doc.{lang}.mjs`.
|
|
166
|
+
* @param {string} docPath
|
|
167
|
+
* @param {string} lang
|
|
168
|
+
* @returns {string}
|
|
197
169
|
*/
|
|
198
|
-
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
170
|
+
function overlayPath(docPath, lang) {
|
|
171
|
+
return path.join(
|
|
172
|
+
path.dirname(docPath),
|
|
173
|
+
`${path.basename(docPath, '.doc.mjs')}.doc.${lang}.mjs`,
|
|
174
|
+
);
|
|
203
175
|
}
|
|
204
176
|
|
|
205
177
|
/**
|
|
206
|
-
*
|
|
207
|
-
* @param {DocsCatalog} catalog
|
|
178
|
+
* The overlay languages a topic ships for its own file or any extension.
|
|
208
179
|
* @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
|
|
209
|
-
* @
|
|
210
|
-
* @returns {Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>}
|
|
180
|
+
* @returns {string[]}
|
|
211
181
|
*/
|
|
212
|
-
export
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
182
|
+
export function overlayLanguages(entry) {
|
|
183
|
+
const files = [entry.path, ...entry.extensions.map(ext => ext.path)];
|
|
184
|
+
return OVERLAY_LANGUAGES.filter(lang =>
|
|
185
|
+
files.some(file => fs.existsSync(overlayPath(file, lang))),
|
|
216
186
|
);
|
|
217
187
|
}
|
|
218
188
|
|
|
219
189
|
/**
|
|
220
190
|
* Resolve `topic` against the project's catalog (throwing `ERR_UNKNOWN_TOPIC`
|
|
221
|
-
* when unmatched) and
|
|
222
|
-
* integration extension applied. Shared by the leaves so
|
|
223
|
-
* and unknown-topic handling live in exactly one place.
|
|
191
|
+
* when unmatched), and load it with any --dense/--zh overlay and any
|
|
192
|
+
* integration extension applied. Shared by the detail and section leaves so
|
|
193
|
+
* topic normalization and unknown-topic handling live in exactly one place.
|
|
224
194
|
*
|
|
225
195
|
* @param {string} topic
|
|
226
196
|
* @param {object} [options]
|
|
@@ -230,7 +200,7 @@ export async function compileTopic(catalog, entry, lang = null) {
|
|
|
230
200
|
* @param {string} [options.cwd]
|
|
231
201
|
* @returns {Promise<{
|
|
232
202
|
* catalog: DocsCatalog,
|
|
233
|
-
*
|
|
203
|
+
* docsData: import('./docs.type.mjs').DocsDetailResponse['data'],
|
|
234
204
|
* lang: string | null,
|
|
235
205
|
* }>}
|
|
236
206
|
*/
|
|
@@ -251,6 +221,6 @@ export async function resolveTopicDocs(topic, options = {}) {
|
|
|
251
221
|
);
|
|
252
222
|
}
|
|
253
223
|
|
|
254
|
-
const
|
|
255
|
-
return {catalog,
|
|
224
|
+
const docsData = await loadTopicDoc(entry, {lang: effectiveLang});
|
|
225
|
+
return {catalog, docsData, lang: effectiveLang};
|
|
256
226
|
}
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
2
|
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Resolve token-ref blocks by inlining the referenced section's table.
|
|
6
|
+
* This allows section docs to reference token tables without duplicating data.
|
|
7
|
+
*
|
|
8
|
+
* The reference is resolved through the catalog, so a topic may point at one
|
|
9
|
+
* an integration contributed (or replaced) rather than only at a built-in. It
|
|
10
|
+
* names the section by key or authored title, so it resolves in every language.
|
|
11
|
+
* @param {import('../docs.type.mjs').DocsDetailResponse['data']} docsData
|
|
12
|
+
* @param {import('../../../foundation/discovery/docs-discovery.mjs').DocsCatalog} catalog
|
|
13
|
+
* @param {{lang?: string | null}} [options]
|
|
14
|
+
* @returns {Promise<import('../docs.type.mjs').DocsDetailResponse['data']>}
|
|
15
|
+
*/
|
|
16
|
+
export function resolveTokenRefs(docsData: import("../docs.type.mjs").DocsDetailResponse["data"], catalog: import("../../../foundation/discovery/docs-discovery.mjs").DocsCatalog, { lang }?: {
|
|
17
|
+
lang?: string | null;
|
|
18
|
+
}): Promise<import("../docs.type.mjs").DocsDetailResponse["data"]>;
|
|
4
19
|
/**
|
|
5
20
|
* @param {string} topic
|
|
6
21
|
* @param {object} [options]
|
|
@@ -3,17 +3,84 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file docs.detail leaf — load one topic's full reference doc.
|
|
5
5
|
*
|
|
6
|
-
* @input A topic name plus optional {lang, zh, dense
|
|
7
|
-
* via the shared adapter
|
|
6
|
+
* @input A topic name plus optional {lang, zh, dense}. Resolves and loads the
|
|
7
|
+
* topic via the shared adapter, then inlines any token-ref blocks.
|
|
8
8
|
* @output { type: 'docs.detail', data: ReferenceDoc } — the full doc with
|
|
9
|
-
* token-refs
|
|
10
|
-
* @position Leaf under api/docs.
|
|
11
|
-
*
|
|
9
|
+
* token-refs resolved, matching `xds --json docs <topic>`.
|
|
10
|
+
* @position Leaf under api/docs. Owns resolveTokenRefs (used only here); shares
|
|
11
|
+
* discovery/loading/topic-resolution with the section leaf via _adapter.mjs.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
14
|
+
import {loadTopicDoc, resolveTopicDocs} from '../_adapter.mjs';
|
|
15
|
+
import {
|
|
16
|
+
sectionKey,
|
|
17
|
+
sourceTitle,
|
|
18
|
+
withSourceTitle,
|
|
19
|
+
} from '../../../foundation/discovery/docs-section-key.mjs';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve token-ref blocks by inlining the referenced section's table.
|
|
23
|
+
* This allows section docs to reference token tables without duplicating data.
|
|
24
|
+
*
|
|
25
|
+
* The reference is resolved through the catalog, so a topic may point at one
|
|
26
|
+
* an integration contributed (or replaced) rather than only at a built-in. It
|
|
27
|
+
* names the section by key or authored title, so it resolves in every language.
|
|
28
|
+
* @param {import('../docs.type.mjs').DocsDetailResponse['data']} docsData
|
|
29
|
+
* @param {import('../../../foundation/discovery/docs-discovery.mjs').DocsCatalog} catalog
|
|
30
|
+
* @param {{lang?: string | null}} [options]
|
|
31
|
+
* @returns {Promise<import('../docs.type.mjs').DocsDetailResponse['data']>}
|
|
32
|
+
*/
|
|
33
|
+
export async function resolveTokenRefs(docsData, catalog, {lang = null} = {}) {
|
|
34
|
+
const resolved = {...docsData, sections: [...docsData.sections]};
|
|
35
|
+
for (let si = 0; si < resolved.sections.length; si++) {
|
|
36
|
+
const section = resolved.sections[si];
|
|
37
|
+
/** @type {import('@astryxdesign/cli/authoring').ReferenceContentBlock[]} */
|
|
38
|
+
const newContent = [];
|
|
39
|
+
for (const block of section.content) {
|
|
40
|
+
if (block.type === 'token-ref') {
|
|
41
|
+
const refEntry = catalog.resolve(block.topic);
|
|
42
|
+
if (!refEntry) {
|
|
43
|
+
newContent.push({type: 'prose', text: `[token-ref: unknown topic "${block.topic}"]`});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const refDocs = await loadTopicDoc(refEntry, {lang});
|
|
47
|
+
const wanted = block.section.toLowerCase();
|
|
48
|
+
const refSection = refDocs.sections.find(
|
|
49
|
+
(/** @type {import('@astryxdesign/cli/authoring').ReferenceSection} */ s) =>
|
|
50
|
+
sectionKey(s) === block.section ||
|
|
51
|
+
sourceTitle(s).toLowerCase() === wanted,
|
|
52
|
+
);
|
|
53
|
+
if (!refSection) {
|
|
54
|
+
newContent.push({type: 'prose', text: `[token-ref: section "${block.section}" not found in "${block.topic}"]`});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Inline the referenced section's content blocks (tables, prose, etc.)
|
|
58
|
+
// and carry over the previewType
|
|
59
|
+
for (const refBlock of refSection.content) {
|
|
60
|
+
newContent.push(refBlock);
|
|
61
|
+
}
|
|
62
|
+
// If the referenced section has a previewType, attach it to our section
|
|
63
|
+
if (refSection.previewType && !section.previewType) {
|
|
64
|
+
resolved.sections[si] = withSourceTitle(
|
|
65
|
+
{...section, previewType: refSection.previewType, content: newContent},
|
|
66
|
+
sourceTitle(section),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
newContent.push(block);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (resolved.sections[si] === section) {
|
|
74
|
+
resolved.sections[si] = withSourceTitle(
|
|
75
|
+
{...section, content: newContent},
|
|
76
|
+
sourceTitle(section),
|
|
77
|
+
);
|
|
78
|
+
} else {
|
|
79
|
+
resolved.sections[si].content = newContent;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return resolved;
|
|
83
|
+
}
|
|
17
84
|
|
|
18
85
|
/**
|
|
19
86
|
* @param {string} topic
|
|
@@ -25,10 +92,7 @@ import {referenceTargets, resolveTopicDocs} from '../_adapter.mjs';
|
|
|
25
92
|
* @returns {Promise<import('../docs.type.mjs').DocsDetailResponse>}
|
|
26
93
|
*/
|
|
27
94
|
export async function detail(topic, options = {}) {
|
|
28
|
-
const {catalog,
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
referenceTargets(catalog, lang),
|
|
32
|
-
);
|
|
33
|
-
return {type: 'docs.detail', data: detailView(linked)};
|
|
95
|
+
const {catalog, docsData, lang} = await resolveTopicDocs(topic, options);
|
|
96
|
+
const resolved = await resolveTokenRefs(docsData, catalog, {lang});
|
|
97
|
+
return {type: 'docs.detail', data: resolved};
|
|
34
98
|
}
|
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
* @file docs.detail.section leaf — load a single section of a topic.
|
|
5
5
|
*
|
|
6
6
|
* @input A topic name, a section query, and optional {lang, zh, dense}. Resolves
|
|
7
|
-
* the topic via the shared adapter, finds the section
|
|
8
|
-
*
|
|
9
|
-
* the query, and links only that section.
|
|
7
|
+
* and loads the topic via the shared adapter, then finds the section by its
|
|
8
|
+
* stable key, then by exact title, then by a title that contains the query.
|
|
10
9
|
* @output { type: 'docs.detail.section', data: ReferenceSection } with any
|
|
11
10
|
* token-ref blocks inlined — matching `astryx --json docs <topic> <section>`.
|
|
12
11
|
* Throws ERR_UNKNOWN_SECTION when nothing matches, or when the query matches
|
|
13
12
|
* more than one section (the candidates come back as suggestions).
|
|
14
|
-
* @position Leaf nested under api/docs/detail. Shares
|
|
15
|
-
* detail leaf via _adapter.mjs
|
|
13
|
+
* @position Leaf nested under api/docs/detail. Shares discovery/loading/
|
|
14
|
+
* topic-resolution with the detail leaf via _adapter.mjs.
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
17
|
import {AstryxError} from '../../../error.mjs';
|
|
@@ -21,12 +20,8 @@ import {
|
|
|
21
20
|
findDocSection,
|
|
22
21
|
sectionKey,
|
|
23
22
|
} from '../../../../foundation/discovery/docs-section-key.mjs';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
readerSections,
|
|
27
|
-
sectionView,
|
|
28
|
-
} from '../../../../foundation/doc-compiler/lenses.mjs';
|
|
29
|
-
import {referenceTargets, resolveTopicDocs} from '../../_adapter.mjs';
|
|
23
|
+
import {resolveTopicDocs} from '../../_adapter.mjs';
|
|
24
|
+
import {resolveTokenRefs} from '../detail.mjs';
|
|
30
25
|
|
|
31
26
|
/**
|
|
32
27
|
* @param {string} topic
|
|
@@ -49,30 +44,31 @@ export async function section(topic, sectionName, options = {}) {
|
|
|
49
44
|
ERROR_CODES.ERR_UNKNOWN_SECTION,
|
|
50
45
|
);
|
|
51
46
|
}
|
|
47
|
+
const {catalog, docsData, lang} = await resolveTopicDocs(topic, options);
|
|
52
48
|
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
const {section: match, candidates} = findDocSection(
|
|
50
|
+
docsData.sections,
|
|
51
|
+
sectionName,
|
|
52
|
+
);
|
|
56
53
|
if (!match) {
|
|
57
54
|
const ambiguous = candidates.length > 1;
|
|
58
55
|
throw new AstryxError(
|
|
59
56
|
ambiguous
|
|
60
57
|
? `Section "${sectionName}" matches ${candidates.length} sections in "${topic}". Read one by its key.`
|
|
61
58
|
: `Section "${sectionName}" not found in "${topic}"`,
|
|
62
|
-
(ambiguous ? candidates : sections).map(s => ({
|
|
59
|
+
(ambiguous ? candidates : docsData.sections).map(s => ({
|
|
63
60
|
name: sectionKey(s),
|
|
64
61
|
reason: s.title,
|
|
65
62
|
})),
|
|
66
63
|
ERROR_CODES.ERR_UNKNOWN_SECTION,
|
|
67
64
|
);
|
|
68
65
|
}
|
|
69
|
-
|
|
70
66
|
// A section read on its own inlines its token refs, as the whole topic does;
|
|
71
|
-
// otherwise a section that is only a token-ref prints blank.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
// otherwise a section that is only a token-ref prints blank.
|
|
68
|
+
const {sections} = await resolveTokenRefs(
|
|
69
|
+
{...docsData, sections: [match]},
|
|
70
|
+
catalog,
|
|
71
|
+
{lang},
|
|
76
72
|
);
|
|
77
|
-
return {type: 'docs.detail.section', data:
|
|
73
|
+
return {type: 'docs.detail.section', data: sections[0]};
|
|
78
74
|
}
|