@astryxdesign/cli 0.4.5 → 0.4.6-canary.3b74294
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/CHANGELOG.md +26 -0
- package/api/component/_adapter.d.mts +22 -5
- package/api/component/_adapter.mjs +51 -3
- package/api/docs/_adapter.d.mts +37 -8
- package/api/docs/_adapter.mjs +70 -41
- package/api/docs/detail/detail.d.mts +2 -0
- package/api/docs/detail/detail.mjs +12 -10
- package/api/docs/detail/section/section.d.mts +2 -0
- package/api/docs/detail/section/section.mjs +1 -0
- package/api/docs/docs.d.mts +2 -0
- package/api/docs/docs.doc.mjs +11 -2
- package/api/docs/docs.mjs +2 -1
- package/api/docs/docs.type.d.mts +15 -0
- package/api/docs/docs.type.mjs +6 -0
- package/api/docs/integrationDocs.test.mjs +208 -0
- package/api/docs/list/list.d.mts +5 -1
- package/api/docs/list/list.mjs +30 -14
- package/api/init/run/run.mjs +9 -4
- package/api/integration/validate-integration.mjs +2 -1
- package/api/integration/validate-integration.type.d.mts +1 -0
- package/api/integration/validate-integration.type.mjs +1 -0
- package/api/search/search.mjs +26 -15
- package/api/upgrade/_adapter.d.mts +2 -2
- package/api/upgrade/_adapter.mjs +7 -3
- package/api/upgrade/run/run.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +245 -0
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +139 -13
- package/assets/docs/cli-integrations.doc.mjs +41 -3
- package/assets/docs/layout.doc.dense.mjs +275 -30
- package/assets/docs/layout.doc.mjs +412 -78
- package/assets/docs/theme.doc.mjs +3 -3
- package/assets/templates/blocks/components/AvatarGroup/AvatarGroupInteractive.doc.mjs +15 -0
- package/assets/templates/blocks/components/AvatarGroup/AvatarGroupInteractive.tsx +47 -0
- package/assets/templates/blocks/components/AvatarGroup/AvatarGroupShowcase.doc.mjs +1 -1
- package/assets/templates/blocks/components/Breadcrumbs/BreadcrumbsCustomSeparator.tsx +18 -5
- package/assets/templates/blocks/components/Selector/SelectorOptionDescriptions.doc.mjs +14 -0
- package/assets/templates/blocks/components/Selector/SelectorOptionDescriptions.tsx +76 -0
- package/assets/templates/pages/dashboard-cohort-funnel/page.tsx +914 -0
- package/assets/templates/pages/dashboard-cohort-funnel/template.doc.mjs +12 -0
- package/assets/templates/pages/dashboard-data/page.tsx +894 -0
- package/assets/templates/pages/dashboard-data/template.doc.mjs +13 -0
- package/assets/templates/pages/dashboard-executive-summary/page.tsx +874 -0
- package/assets/templates/pages/dashboard-executive-summary/template.doc.mjs +13 -0
- package/assets/templates/pages/dashboard-project-status/page.tsx +1139 -0
- package/assets/templates/pages/dashboard-project-status/template.doc.mjs +13 -0
- package/assets/templates/pages/dashboard-service-monitoring/page.tsx +1590 -0
- package/assets/templates/pages/dashboard-service-monitoring/template.doc.mjs +12 -0
- package/authoring/doctypes/_schema.d.mts +2 -0
- package/authoring/doctypes/_schema.mjs +5 -0
- package/authoring/doctypes/reference/reference.doc.mjs +14 -0
- package/authoring/doctypes/reference/type.ts +12 -0
- package/authoring/doctypes/template/type.ts +2 -0
- package/authoring/integration/integration.doc.mjs +10 -1
- package/authoring/integration/parse.d.mts +1 -0
- package/authoring/integration/parse.mjs +1 -0
- package/authoring/integration/parse.test.mjs +1 -0
- package/authoring/integration/type.ts +5 -0
- package/clients/cli/commands/component-ownership.test.mjs +63 -3
- package/clients/cli/commands/theme-build.doc.mjs +1 -1
- package/foundation/agent-docs/agent-docs.d.mts +19 -3
- package/foundation/agent-docs/agent-docs.mjs +30 -11
- package/foundation/agent-docs/agent-docs.test.mjs +38 -0
- package/foundation/config/project.d.mts +16 -0
- package/foundation/config/project.mjs +65 -4
- package/foundation/config/project.test.mjs +66 -0
- package/foundation/discovery/docs-discovery.d.mts +185 -0
- package/foundation/discovery/docs-discovery.mjs +544 -0
- package/foundation/discovery/docs-discovery.test.mjs +341 -0
- package/foundation/integrations/integrations.d.mts +8 -6
- package/foundation/integrations/integrations.mjs +6 -4
- package/foundation/integrations/validate-contributions.mjs +30 -2
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @xds/cli
|
|
2
2
|
|
|
3
|
+
# 0.4.6
|
|
4
|
+
|
|
5
|
+
#### New Features
|
|
6
|
+
|
|
7
|
+
- An integration can contribute reference-doc topics: point `docs` at a root in `astryx.integration.*` and every `{topic}.doc.{ts,mjs,js}` under it is served by `astryx docs`, indexed by `astryx search`, and named in the agent-docs block, beside the built-in topics. A topic may also declare `replaces: '<topic>'` to take over an existing one (renaming it leaves the old name resolving as an alias) or `extends: '<topic>'` to merge onto one section by section. A name that collides without declaring either is an `invalid_doc` issue rather than a silent override, and `validate-integration` reports it. (#5311)
|
|
8
|
+
Also fixes the agent-docs block's topic list, which scanned for `\w+` and so silently dropped every hyphenated topic — `getting-started`, `cli-integrations`, `browser-support`, `styling-libraries` and `working-with-ai` were missing from every block ever written, and an agent cannot ask for a topic it was never told about.
|
|
9
|
+
- Five dashboard page templates: `dashboard-cohort-funnel`, `dashboard-data`, `dashboard-executive-summary`, `dashboard-project-status` and `dashboard-service-monitoring`. Each is a complete page — layout, realistic sample data, and the component choices that go with the shape of the data — so `astryx template <name>` gives you something to edit rather than a blank frame (#5245).
|
|
10
|
+
|
|
11
|
+
#### Fixes
|
|
12
|
+
|
|
13
|
+
- `component` built the import specifier for an integration component by joining the package name and the component name, which assumes every component is exported from a subpath named after itself. Components are commonly grouped behind a single entry point named after the concept, so the suggested import pointed at a subpath the package does not export and did not resolve (#4810).
|
|
14
|
+
The specifier is now resolved against the owning package's `exports` map, keyed on the directory the component's doc file sits in, and falls back to the package root when that directory is not an exported subpath. A specifier a doc file states for itself is also no longer overwritten.
|
|
15
|
+
- The upgrade codemod no longer collapses significant JSX whitespace when it renames an element tag. Renaming `<OldName>` next to text and a `{expression}` (e.g. `hello {name} world`) previously dropped the adjacent space (`hello {name}world`); element-tag renames are now spliced into the output so the surrounding JSX is left untouched (#5149).
|
|
16
|
+
- The XDS-prefix codemod no longer produces a file that will not compile. Dropping the prefix renames `XDSButton` to `Button`, but if the file already had a local binding called `Button` the rewrite collided with it and shadowed one of the two. The import is now aliased instead, so both survive and the file still typechecks (#5225).
|
|
17
|
+
|
|
18
|
+
#### Contributors
|
|
19
|
+
|
|
20
|
+
Thanks to everyone who contributed to this release:
|
|
21
|
+
|
|
22
|
+
- @ejhammond
|
|
23
|
+
- @josephfarina
|
|
24
|
+
- @kentonquatman
|
|
25
|
+
- @rubyycheung
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
3
29
|
# 0.4.5
|
|
4
30
|
|
|
5
31
|
---
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @property {any[]} [components]
|
|
13
13
|
* @property {{description?: string}} [usage]
|
|
14
14
|
* @property {any} [theming]
|
|
15
|
+
* @property {string} [import] set when the doc states its own import specifier
|
|
15
16
|
*/
|
|
16
17
|
/**
|
|
17
18
|
* Options object for `loadDocs`, matching its declared parameter shape (used
|
|
@@ -29,6 +30,12 @@
|
|
|
29
30
|
* @property {string|undefined} issuesUrl
|
|
30
31
|
* @property {import('../../foundation/integrations/integrations.mjs').LoadedIntegration|null} integration
|
|
31
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* What ownership shaping needs from an owner. The core and legacy-external
|
|
35
|
+
* paths synthesize a bare `{package, sourcePath}` rather than resolving a full
|
|
36
|
+
* {@link ComponentOwner}, so everything past those two is optional here.
|
|
37
|
+
* @typedef {Partial<ComponentOwner> & {package: string, sourcePath: string|null}} OwnershipSubject
|
|
38
|
+
*/
|
|
32
39
|
/**
|
|
33
40
|
* A back-compat external package discovered via `pkg.astryx.docs`.
|
|
34
41
|
* @typedef {{name: string, category: string, docsDir: string}} ExternalPackageRef
|
|
@@ -152,15 +159,12 @@ export function extractProps(docs: LoadedComponentDoc): any[];
|
|
|
152
159
|
* swizzleable source file exists for the owner). Existing doc fields (name,
|
|
153
160
|
* usage, props, …) are preserved.
|
|
154
161
|
* @param {LoadedComponentDoc} docs
|
|
155
|
-
* @param {
|
|
162
|
+
* @param {OwnershipSubject} owner
|
|
156
163
|
* @param {string} componentName
|
|
157
164
|
* @param {string} coreDir
|
|
158
165
|
* @returns {import('./component.type.mjs').ComponentDetailResponse['data']}
|
|
159
166
|
*/
|
|
160
|
-
export function withOwnership(docs: LoadedComponentDoc, owner:
|
|
161
|
-
package: string;
|
|
162
|
-
sourcePath: string | null;
|
|
163
|
-
}, componentName: string, coreDir: string): import("./component.type.mjs").ComponentDetailResponse["data"];
|
|
167
|
+
export function withOwnership(docs: LoadedComponentDoc, owner: OwnershipSubject, componentName: string, coreDir: string): import("./component.type.mjs").ComponentDetailResponse["data"];
|
|
164
168
|
/**
|
|
165
169
|
* When the caller asked for "Code" but the resolved doc is for "CodeBlock"
|
|
166
170
|
* (parent), scope the response to just the matching sub-component. Returns the
|
|
@@ -193,6 +197,10 @@ export type LoadedComponentDoc = {
|
|
|
193
197
|
description?: string;
|
|
194
198
|
} | undefined;
|
|
195
199
|
theming?: any;
|
|
200
|
+
/**
|
|
201
|
+
* set when the doc states its own import specifier
|
|
202
|
+
*/
|
|
203
|
+
import?: string | undefined;
|
|
196
204
|
};
|
|
197
205
|
/**
|
|
198
206
|
* Options object for `loadDocs`, matching its declared parameter shape (used
|
|
@@ -215,6 +223,15 @@ export type ComponentOwner = {
|
|
|
215
223
|
issuesUrl: string | undefined;
|
|
216
224
|
integration: import("../../foundation/integrations/integrations.mjs").LoadedIntegration | null;
|
|
217
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* What ownership shaping needs from an owner. The core and legacy-external
|
|
228
|
+
* paths synthesize a bare `{package, sourcePath}` rather than resolving a full
|
|
229
|
+
* {@link ComponentOwner}, so everything past those two is optional here.
|
|
230
|
+
*/
|
|
231
|
+
export type OwnershipSubject = Partial<ComponentOwner> & {
|
|
232
|
+
package: string;
|
|
233
|
+
sourcePath: string | null;
|
|
234
|
+
};
|
|
218
235
|
/**
|
|
219
236
|
* A back-compat external package discovered via `pkg.astryx.docs`.
|
|
220
237
|
*/
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* deduped, so each leaf stays a thin projection.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
21
23
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
22
24
|
import {findCoreDir, discoverExternalPackages} from '../../foundation/fs/paths.mjs';
|
|
23
25
|
import {
|
|
@@ -48,6 +50,7 @@ export {CORE_PACKAGE};
|
|
|
48
50
|
* @property {any[]} [components]
|
|
49
51
|
* @property {{description?: string}} [usage]
|
|
50
52
|
* @property {any} [theming]
|
|
53
|
+
* @property {string} [import] set when the doc states its own import specifier
|
|
51
54
|
*/
|
|
52
55
|
|
|
53
56
|
/**
|
|
@@ -68,6 +71,13 @@ export {CORE_PACKAGE};
|
|
|
68
71
|
* @property {import('../../foundation/integrations/integrations.mjs').LoadedIntegration|null} integration
|
|
69
72
|
*/
|
|
70
73
|
|
|
74
|
+
/**
|
|
75
|
+
* What ownership shaping needs from an owner. The core and legacy-external
|
|
76
|
+
* paths synthesize a bare `{package, sourcePath}` rather than resolving a full
|
|
77
|
+
* {@link ComponentOwner}, so everything past those two is optional here.
|
|
78
|
+
* @typedef {Partial<ComponentOwner> & {package: string, sourcePath: string|null}} OwnershipSubject
|
|
79
|
+
*/
|
|
80
|
+
|
|
71
81
|
/**
|
|
72
82
|
* A back-compat external package discovered via `pkg.astryx.docs`.
|
|
73
83
|
* @typedef {{name: string, category: string, docsDir: string}} ExternalPackageRef
|
|
@@ -357,7 +367,7 @@ export function extractProps(docs) {
|
|
|
357
367
|
* swizzleable source file exists for the owner). Existing doc fields (name,
|
|
358
368
|
* usage, props, …) are preserved.
|
|
359
369
|
* @param {LoadedComponentDoc} docs
|
|
360
|
-
* @param {
|
|
370
|
+
* @param {OwnershipSubject} owner
|
|
361
371
|
* @param {string} componentName
|
|
362
372
|
* @param {string} coreDir
|
|
363
373
|
* @returns {import('./component.type.mjs').ComponentDetailResponse['data']}
|
|
@@ -366,15 +376,53 @@ export function withOwnership(docs, owner, componentName, coreDir) {
|
|
|
366
376
|
const importSpec =
|
|
367
377
|
owner.package === CORE_PACKAGE
|
|
368
378
|
? resolveImportPath(coreDir, componentName)
|
|
369
|
-
:
|
|
379
|
+
: resolveIntegrationImportPath(owner, componentName);
|
|
370
380
|
return /** @type {any} */ ({
|
|
371
381
|
...docs,
|
|
372
382
|
package: owner.package,
|
|
373
|
-
|
|
383
|
+
// A doc file may state its own specifier, e.g. when one entry point exports
|
|
384
|
+
// several components. Only fall back to a resolved one when it does not.
|
|
385
|
+
import: docs.import ?? importSpec,
|
|
374
386
|
sourceAvailable: owner.sourcePath != null,
|
|
375
387
|
});
|
|
376
388
|
}
|
|
377
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Resolve the specifier an integration component is imported from, against the
|
|
392
|
+
* owning package's `exports` map.
|
|
393
|
+
*
|
|
394
|
+
* A component lives in a directory that need not share its name — several
|
|
395
|
+
* components can be exported from one entry point — so the specifier has to
|
|
396
|
+
* come from the directory the doc file sits in, checked against `exports`,
|
|
397
|
+
* rather than from the component name. Falls back to the package root when the
|
|
398
|
+
* directory is not an exported subpath, matching what a consumer would have to
|
|
399
|
+
* write by hand.
|
|
400
|
+
*
|
|
401
|
+
* @param {OwnershipSubject} owner
|
|
402
|
+
* @param {string} componentName
|
|
403
|
+
* @returns {string}
|
|
404
|
+
*/
|
|
405
|
+
function resolveIntegrationImportPath(owner, componentName) {
|
|
406
|
+
const packageDir = owner.integration?.__packageDir;
|
|
407
|
+
const directory = owner.docPath
|
|
408
|
+
? path.basename(path.dirname(owner.docPath))
|
|
409
|
+
: componentName;
|
|
410
|
+
if (!packageDir) {
|
|
411
|
+
return owner.package;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const manifest = JSON.parse(
|
|
415
|
+
fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8'),
|
|
416
|
+
);
|
|
417
|
+
if (manifest.exports?.[`./${directory}`]) {
|
|
418
|
+
return `${owner.package}/${directory}`;
|
|
419
|
+
}
|
|
420
|
+
} catch {
|
|
421
|
+
// An unreadable or malformed manifest is not worth failing a lookup over.
|
|
422
|
+
}
|
|
423
|
+
return owner.package;
|
|
424
|
+
}
|
|
425
|
+
|
|
378
426
|
/**
|
|
379
427
|
* When the caller asked for "Code" but the resolved doc is for "CodeBlock"
|
|
380
428
|
* (parent), scope the response to just the matching sub-component. Returns the
|
package/api/docs/_adapter.d.mts
CHANGED
|
@@ -2,9 +2,19 @@
|
|
|
2
2
|
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* The project's topics: the built-in ones plus whatever the configured
|
|
6
|
+
* integrations contribute.
|
|
7
|
+
*
|
|
8
|
+
* A docs read must not depend on a healthy project config. `astryx docs
|
|
9
|
+
* tokens` answered without loading anything before integrations could
|
|
10
|
+
* contribute topics, and it still answers when the config is unreadable — the
|
|
11
|
+
* built-in topics are the floor, and the integration issues surface on the
|
|
12
|
+
* commands that own them.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} [cwd]
|
|
15
|
+
* @returns {Promise<DocsCatalog>}
|
|
6
16
|
*/
|
|
7
|
-
export function
|
|
17
|
+
export function loadDocsCatalog(cwd?: string): Promise<DocsCatalog>;
|
|
8
18
|
/**
|
|
9
19
|
* @param {string} docPath
|
|
10
20
|
* @param {{lang?: string|null}} [opts]
|
|
@@ -14,18 +24,35 @@ export function loadReferenceDocs(docPath: string, { lang }?: {
|
|
|
14
24
|
lang?: string | null;
|
|
15
25
|
}): Promise<import("./docs.type.mjs").DocsDetailResponse["data"]>;
|
|
16
26
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
+
*
|
|
35
|
+
* @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
|
|
36
|
+
* @param {{lang?: string|null}} [opts]
|
|
37
|
+
* @returns {Promise<import('./docs.type.mjs').DocsDetailResponse['data']>}
|
|
38
|
+
*/
|
|
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"]>;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve `topic` against the project's catalog (throwing `ERR_UNKNOWN_TOPIC`
|
|
44
|
+
* when unmatched), and load it with any --dense/--zh overlay and any
|
|
45
|
+
* integration extension applied. Shared by the detail and section leaves so
|
|
46
|
+
* topic normalization and unknown-topic handling live in exactly one place.
|
|
21
47
|
*
|
|
22
48
|
* @param {string} topic
|
|
23
49
|
* @param {object} [options]
|
|
24
50
|
* @param {string} [options.lang]
|
|
25
51
|
* @param {boolean} [options.zh]
|
|
26
52
|
* @param {boolean} [options.dense]
|
|
53
|
+
* @param {string} [options.cwd]
|
|
27
54
|
* @returns {Promise<{
|
|
28
|
-
*
|
|
55
|
+
* catalog: DocsCatalog,
|
|
29
56
|
* docsData: import('./docs.type.mjs').DocsDetailResponse['data'],
|
|
30
57
|
* }>}
|
|
31
58
|
*/
|
|
@@ -33,7 +60,9 @@ export function resolveTopicDocs(topic: string, options?: {
|
|
|
33
60
|
lang?: string | undefined;
|
|
34
61
|
zh?: boolean | undefined;
|
|
35
62
|
dense?: boolean | undefined;
|
|
63
|
+
cwd?: string | undefined;
|
|
36
64
|
}): Promise<{
|
|
37
|
-
|
|
65
|
+
catalog: DocsCatalog;
|
|
38
66
|
docsData: import("./docs.type.mjs").DocsDetailResponse["data"];
|
|
39
67
|
}>;
|
|
68
|
+
import { DocsCatalog } from '../../foundation/discovery/docs-discovery.mjs';
|
package/api/docs/_adapter.mjs
CHANGED
|
@@ -3,35 +3,50 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file Shared doc-loading and topic-resolution helpers for the docs leaves.
|
|
5
5
|
*
|
|
6
|
-
* @input
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* @input The project's doc catalog — the CLI's own
|
|
7
|
+
* packages/cli/assets/docs/{topic}.doc.mjs plus every topic the configured
|
|
8
|
+
* integrations contribute — and, when a --dense/--zh overlay is requested,
|
|
9
|
+
* the sibling {topic}.doc.dense.mjs / {topic}.doc.zh.mjs.
|
|
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.
|
|
10
13
|
* @position Sits beside docs.mjs (api/docs/). Owns everything ≥2 leaves need so
|
|
11
|
-
* no leaf re-implements
|
|
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.
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
19
|
import * as fs from 'node:fs';
|
|
15
20
|
import * as path from 'node:path';
|
|
16
21
|
import {pathToFileURL} from 'node:url';
|
|
17
|
-
import {
|
|
22
|
+
import {Project} from '../../foundation/config/project.mjs';
|
|
23
|
+
import {
|
|
24
|
+
DocsCatalog,
|
|
25
|
+
mergeTopic,
|
|
26
|
+
} from '../../foundation/discovery/docs-discovery.mjs';
|
|
18
27
|
import {AstryxError} from '../error.mjs';
|
|
19
28
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
20
29
|
|
|
21
|
-
const DOCS_DIR = path.join(CLI_ROOT, 'assets', 'docs');
|
|
22
|
-
|
|
23
30
|
/**
|
|
24
|
-
*
|
|
31
|
+
* The project's topics: the built-in ones plus whatever the configured
|
|
32
|
+
* integrations contribute.
|
|
33
|
+
*
|
|
34
|
+
* A docs read must not depend on a healthy project config. `astryx docs
|
|
35
|
+
* tokens` answered without loading anything before integrations could
|
|
36
|
+
* contribute topics, and it still answers when the config is unreadable — the
|
|
37
|
+
* built-in topics are the floor, and the integration issues surface on the
|
|
38
|
+
* commands that own them.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} [cwd]
|
|
41
|
+
* @returns {Promise<DocsCatalog>}
|
|
25
42
|
*/
|
|
26
|
-
export function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (match) topics[match[1]] = path.join(DOCS_DIR, file);
|
|
43
|
+
export async function loadDocsCatalog(cwd = process.cwd()) {
|
|
44
|
+
try {
|
|
45
|
+
const project = await Project.load(cwd);
|
|
46
|
+
return await project.docs();
|
|
47
|
+
} catch {
|
|
48
|
+
return DocsCatalog.fromBuiltins();
|
|
33
49
|
}
|
|
34
|
-
return topics;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/**
|
|
@@ -41,7 +56,7 @@ export function discoverTopics() {
|
|
|
41
56
|
*/
|
|
42
57
|
export async function loadReferenceDocs(docPath, {lang} = {}) {
|
|
43
58
|
const mod = await import(pathToFileURL(docPath).href);
|
|
44
|
-
const docs = mod.docs;
|
|
59
|
+
const docs = mod.docs ?? mod.default;
|
|
45
60
|
if (!lang || lang === 'en') return docs;
|
|
46
61
|
|
|
47
62
|
const dir = path.dirname(docPath);
|
|
@@ -96,46 +111,60 @@ export async function loadReferenceDocs(docPath, {lang} = {}) {
|
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
114
|
+
* Load one catalog entry: its own doc, plus any extension an integration
|
|
115
|
+
* merged onto it, in configuration order.
|
|
116
|
+
*
|
|
117
|
+
* A localization overlay applies to each file before the extensions are
|
|
118
|
+
* merged, so an extension written in the base language stays readable under
|
|
119
|
+
* `--dense`/`--zh` (it replaces its own sections and leaves the rest
|
|
120
|
+
* translated) rather than being dropped.
|
|
121
|
+
*
|
|
122
|
+
* @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
|
|
123
|
+
* @param {{lang?: string|null}} [opts]
|
|
124
|
+
* @returns {Promise<import('./docs.type.mjs').DocsDetailResponse['data']>}
|
|
125
|
+
*/
|
|
126
|
+
export async function loadTopicDoc(entry, {lang} = {}) {
|
|
127
|
+
let doc = await loadReferenceDocs(entry.path, {lang});
|
|
128
|
+
for (const extension of entry.extensions) {
|
|
129
|
+
doc = mergeTopic(doc, await loadReferenceDocs(extension.path, {lang}));
|
|
130
|
+
}
|
|
131
|
+
return doc;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve `topic` against the project's catalog (throwing `ERR_UNKNOWN_TOPIC`
|
|
136
|
+
* when unmatched), and load it with any --dense/--zh overlay and any
|
|
137
|
+
* integration extension applied. Shared by the detail and section leaves so
|
|
138
|
+
* topic normalization and unknown-topic handling live in exactly one place.
|
|
103
139
|
*
|
|
104
140
|
* @param {string} topic
|
|
105
141
|
* @param {object} [options]
|
|
106
142
|
* @param {string} [options.lang]
|
|
107
143
|
* @param {boolean} [options.zh]
|
|
108
144
|
* @param {boolean} [options.dense]
|
|
145
|
+
* @param {string} [options.cwd]
|
|
109
146
|
* @returns {Promise<{
|
|
110
|
-
*
|
|
147
|
+
* catalog: DocsCatalog,
|
|
111
148
|
* docsData: import('./docs.type.mjs').DocsDetailResponse['data'],
|
|
112
149
|
* }>}
|
|
113
150
|
*/
|
|
114
151
|
export async function resolveTopicDocs(topic, options = {}) {
|
|
115
|
-
const {lang = null, zh = false, dense = false} = options;
|
|
152
|
+
const {lang = null, zh = false, dense = false, cwd} = options;
|
|
116
153
|
const effectiveLang = lang || (dense ? 'dense' : zh ? 'zh' : null);
|
|
117
|
-
const
|
|
154
|
+
const catalog = await loadDocsCatalog(cwd);
|
|
118
155
|
|
|
119
|
-
// A public API caller could pass a non-string topic;
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
156
|
+
// A public API caller could pass a non-string topic; `resolve` answers
|
|
157
|
+
// undefined for one, which lands on the same stable code as an unknown name
|
|
158
|
+
// rather than a raw TypeError (which downgrades to ERR_UNKNOWN).
|
|
159
|
+
const entry = catalog.resolve(topic);
|
|
160
|
+
if (!entry) {
|
|
123
161
|
throw new AstryxError(
|
|
124
162
|
`Unknown topic "${String(topic)}"`,
|
|
125
|
-
|
|
126
|
-
ERROR_CODES.ERR_UNKNOWN_TOPIC,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const normalized = topic.toLowerCase();
|
|
131
|
-
if (!topics[normalized]) {
|
|
132
|
-
throw new AstryxError(
|
|
133
|
-
`Unknown topic "${topic}"`,
|
|
134
|
-
Object.keys(topics).map(t => ({name: t, reason: 'available topic'})),
|
|
163
|
+
catalog.names().map(t => ({name: t, reason: 'available topic'})),
|
|
135
164
|
ERROR_CODES.ERR_UNKNOWN_TOPIC,
|
|
136
165
|
);
|
|
137
166
|
}
|
|
138
167
|
|
|
139
|
-
const docsData = await
|
|
140
|
-
return {
|
|
168
|
+
const docsData = await loadTopicDoc(entry, {lang: effectiveLang});
|
|
169
|
+
return {catalog, docsData};
|
|
141
170
|
}
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* @param {string} [options.lang]
|
|
8
8
|
* @param {boolean} [options.zh]
|
|
9
9
|
* @param {boolean} [options.dense]
|
|
10
|
+
* @param {string} [options.cwd]
|
|
10
11
|
* @returns {Promise<import('../docs.type.mjs').DocsDetailResponse>}
|
|
11
12
|
*/
|
|
12
13
|
export function detail(topic: string, options?: {
|
|
13
14
|
lang?: string | undefined;
|
|
14
15
|
zh?: boolean | undefined;
|
|
15
16
|
dense?: boolean | undefined;
|
|
17
|
+
cwd?: string | undefined;
|
|
16
18
|
}): Promise<import("../docs.type.mjs").DocsDetailResponse>;
|
|
@@ -11,17 +11,19 @@
|
|
|
11
11
|
* discovery/loading/topic-resolution with the section leaf via _adapter.mjs.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
import {resolveTopicDocs} from '../_adapter.mjs';
|
|
14
|
+
import {loadTopicDoc, resolveTopicDocs} from '../_adapter.mjs';
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Resolve token-ref blocks by inlining the referenced section's table.
|
|
19
18
|
* This allows section docs to reference token tables without duplicating data.
|
|
19
|
+
*
|
|
20
|
+
* The reference is resolved through the catalog, so a topic may point at one
|
|
21
|
+
* an integration contributed (or replaced) rather than only at a built-in.
|
|
20
22
|
* @param {import('../docs.type.mjs').DocsDetailResponse['data']} docsData
|
|
21
|
-
* @param {
|
|
23
|
+
* @param {import('../../../foundation/discovery/docs-discovery.mjs').DocsCatalog} catalog
|
|
22
24
|
* @returns {Promise<import('../docs.type.mjs').DocsDetailResponse['data']>}
|
|
23
25
|
*/
|
|
24
|
-
async function resolveTokenRefs(docsData,
|
|
26
|
+
async function resolveTokenRefs(docsData, catalog) {
|
|
25
27
|
const resolved = {...docsData, sections: [...docsData.sections]};
|
|
26
28
|
for (let si = 0; si < resolved.sections.length; si++) {
|
|
27
29
|
const section = resolved.sections[si];
|
|
@@ -29,13 +31,12 @@ async function resolveTokenRefs(docsData, topics) {
|
|
|
29
31
|
const newContent = [];
|
|
30
32
|
for (const block of section.content) {
|
|
31
33
|
if (block.type === 'token-ref') {
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
34
|
+
const refEntry = catalog.resolve(block.topic);
|
|
35
|
+
if (!refEntry) {
|
|
34
36
|
newContent.push({type: 'prose', text: `[token-ref: unknown topic "${block.topic}"]`});
|
|
35
37
|
continue;
|
|
36
38
|
}
|
|
37
|
-
const
|
|
38
|
-
const refDocs = refMod.docs;
|
|
39
|
+
const refDocs = await loadTopicDoc(refEntry);
|
|
39
40
|
const refSection = refDocs.sections.find(
|
|
40
41
|
(/** @type {import('@astryxdesign/cli/authoring').ReferenceSection} */ s) =>
|
|
41
42
|
s.title.toLowerCase() === block.section.toLowerCase(),
|
|
@@ -72,10 +73,11 @@ async function resolveTokenRefs(docsData, topics) {
|
|
|
72
73
|
* @param {string} [options.lang]
|
|
73
74
|
* @param {boolean} [options.zh]
|
|
74
75
|
* @param {boolean} [options.dense]
|
|
76
|
+
* @param {string} [options.cwd]
|
|
75
77
|
* @returns {Promise<import('../docs.type.mjs').DocsDetailResponse>}
|
|
76
78
|
*/
|
|
77
79
|
export async function detail(topic, options = {}) {
|
|
78
|
-
const {
|
|
79
|
-
const resolved = await resolveTokenRefs(docsData,
|
|
80
|
+
const {catalog, docsData} = await resolveTopicDocs(topic, options);
|
|
81
|
+
const resolved = await resolveTokenRefs(docsData, catalog);
|
|
80
82
|
return {type: 'docs.detail', data: resolved};
|
|
81
83
|
}
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
* @param {string} [options.lang]
|
|
9
9
|
* @param {boolean} [options.zh]
|
|
10
10
|
* @param {boolean} [options.dense]
|
|
11
|
+
* @param {string} [options.cwd]
|
|
11
12
|
* @returns {Promise<import('../../docs.type.mjs').DocsDetailSectionResponse>}
|
|
12
13
|
*/
|
|
13
14
|
export function section(topic: string, sectionName: string, options?: {
|
|
14
15
|
lang?: string | undefined;
|
|
15
16
|
zh?: boolean | undefined;
|
|
16
17
|
dense?: boolean | undefined;
|
|
18
|
+
cwd?: string | undefined;
|
|
17
19
|
}): Promise<import("../../docs.type.mjs").DocsDetailSectionResponse>;
|
|
@@ -24,6 +24,7 @@ import {resolveTopicDocs} from '../../_adapter.mjs';
|
|
|
24
24
|
* @param {string} [options.lang]
|
|
25
25
|
* @param {boolean} [options.zh]
|
|
26
26
|
* @param {boolean} [options.dense]
|
|
27
|
+
* @param {string} [options.cwd]
|
|
27
28
|
* @returns {Promise<import('../../docs.type.mjs').DocsDetailSectionResponse>}
|
|
28
29
|
*/
|
|
29
30
|
export async function section(topic, sectionName, options = {}) {
|
package/api/docs/docs.d.mts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @param {string} [options.lang]
|
|
9
9
|
* @param {boolean} [options.zh]
|
|
10
10
|
* @param {boolean} [options.dense]
|
|
11
|
+
* @param {string} [options.cwd]
|
|
11
12
|
* @returns {Promise<
|
|
12
13
|
* import('./docs.type.mjs').DocsListResponse |
|
|
13
14
|
* import('./docs.type.mjs').DocsDetailResponse |
|
|
@@ -18,6 +19,7 @@ export function docs(topic?: string, section?: string, options?: {
|
|
|
18
19
|
lang?: string | undefined;
|
|
19
20
|
zh?: boolean | undefined;
|
|
20
21
|
dense?: boolean | undefined;
|
|
22
|
+
cwd?: string | undefined;
|
|
21
23
|
}): Promise<import("./docs.type.mjs").DocsListResponse | import("./docs.type.mjs").DocsDetailResponse | import("./docs.type.mjs").DocsDetailSectionResponse>;
|
|
22
24
|
import { list } from './list/list.mjs';
|
|
23
25
|
import { detail } from './detail/detail.mjs';
|
package/api/docs/docs.doc.mjs
CHANGED
|
@@ -18,7 +18,10 @@ export const doc = {
|
|
|
18
18
|
'Routes on its arguments: no topic lists every reference-doc topic; a topic ' +
|
|
19
19
|
'returns that full ReferenceDoc (with token-ref blocks inlined); a topic ' +
|
|
20
20
|
'plus a section returns the first section whose title contains the ' +
|
|
21
|
-
'(case-insensitive) query.
|
|
21
|
+
'(case-insensitive) query. The topic set is the CLI\'s own docs plus the ' +
|
|
22
|
+
'ones the project\'s configured integrations contribute — including any ' +
|
|
23
|
+
'topic an integration replaces or extends — so it depends on the cwd. ' +
|
|
24
|
+
'Overlay options select localized or dense variants.',
|
|
22
25
|
importPath: '@astryxdesign/cli/api',
|
|
23
26
|
signature:
|
|
24
27
|
'docs(topic?: string, section?: string, options?: DocsOptions): Promise<DocsListResponse | DocsDetailResponse | DocsDetailSectionResponse>',
|
|
@@ -60,12 +63,18 @@ export const doc = {
|
|
|
60
63
|
type: 'boolean',
|
|
61
64
|
description: 'Return the token-efficient dense doc variant.',
|
|
62
65
|
},
|
|
66
|
+
{
|
|
67
|
+
name: 'options.cwd',
|
|
68
|
+
type: 'string',
|
|
69
|
+
description:
|
|
70
|
+
"Project directory whose configured integrations contribute topics. Defaults to process.cwd(); an unreadable config falls back to the CLI's own topics.",
|
|
71
|
+
},
|
|
63
72
|
],
|
|
64
73
|
returns: [
|
|
65
74
|
{
|
|
66
75
|
type: 'docs.list',
|
|
67
76
|
description:
|
|
68
|
-
'All available reference-doc topics as DocsListEntry[] ({topic, description}), in
|
|
77
|
+
'All available reference-doc topics as DocsListEntry[] ({topic, description, package, replaces?}), in read order.',
|
|
69
78
|
},
|
|
70
79
|
{
|
|
71
80
|
type: 'docs.detail',
|
package/api/docs/docs.mjs
CHANGED
|
@@ -29,6 +29,7 @@ export {list, detail, sectionLeaf as section};
|
|
|
29
29
|
* @param {string} [options.lang]
|
|
30
30
|
* @param {boolean} [options.zh]
|
|
31
31
|
* @param {boolean} [options.dense]
|
|
32
|
+
* @param {string} [options.cwd]
|
|
32
33
|
* @returns {Promise<
|
|
33
34
|
* import('./docs.type.mjs').DocsListResponse |
|
|
34
35
|
* import('./docs.type.mjs').DocsDetailResponse |
|
|
@@ -36,7 +37,7 @@ export {list, detail, sectionLeaf as section};
|
|
|
36
37
|
* >}
|
|
37
38
|
*/
|
|
38
39
|
export async function docs(topic, section, options = {}) {
|
|
39
|
-
if (!topic) return list();
|
|
40
|
+
if (!topic) return list(options);
|
|
40
41
|
if (section) return sectionLeaf(topic, section, options);
|
|
41
42
|
return detail(topic, options);
|
|
42
43
|
}
|
package/api/docs/docs.type.d.mts
CHANGED
|
@@ -11,6 +11,16 @@ export type DocsListResponse = {
|
|
|
11
11
|
export type DocsListEntry = {
|
|
12
12
|
topic: string;
|
|
13
13
|
description: string;
|
|
14
|
+
/**
|
|
15
|
+
* the package that owns this topic —
|
|
16
|
+
* '@astryxdesign/cli' for a built-in one, else the contributing integration
|
|
17
|
+
*/
|
|
18
|
+
package: string;
|
|
19
|
+
/**
|
|
20
|
+
* the topic this one took the place of, when it
|
|
21
|
+
* was contributed as a replacement
|
|
22
|
+
*/
|
|
23
|
+
replaces?: string | undefined;
|
|
14
24
|
};
|
|
15
25
|
/**
|
|
16
26
|
* xds --json docs <topic>
|
|
@@ -33,4 +43,9 @@ export type DocsOptions = {
|
|
|
33
43
|
lang?: string | undefined;
|
|
34
44
|
zh?: boolean | undefined;
|
|
35
45
|
dense?: boolean | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* project directory whose configured integrations
|
|
48
|
+
* contribute topics; defaults to process.cwd()
|
|
49
|
+
*/
|
|
50
|
+
cwd?: string | undefined;
|
|
36
51
|
};
|
package/api/docs/docs.type.mjs
CHANGED
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
* @typedef {object} DocsListEntry
|
|
24
24
|
* @property {string} topic
|
|
25
25
|
* @property {string} description
|
|
26
|
+
* @property {string} package the package that owns this topic —
|
|
27
|
+
* '@astryxdesign/cli' for a built-in one, else the contributing integration
|
|
28
|
+
* @property {string} [replaces] the topic this one took the place of, when it
|
|
29
|
+
* was contributed as a replacement
|
|
26
30
|
*/
|
|
27
31
|
|
|
28
32
|
/**
|
|
@@ -45,6 +49,8 @@
|
|
|
45
49
|
* @property {string} [lang]
|
|
46
50
|
* @property {boolean} [zh]
|
|
47
51
|
* @property {boolean} [dense]
|
|
52
|
+
* @property {string} [cwd] project directory whose configured integrations
|
|
53
|
+
* contribute topics; defaults to process.cwd()
|
|
48
54
|
*/
|
|
49
55
|
|
|
50
56
|
export {};
|