@astryxdesign/cli 0.4.5 → 0.4.6-canary.cea9ecd
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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file End-to-end tests for integration-contributed doc topics: a scaffolded
|
|
5
|
+
* consumer project whose configured integration ships a `docs` root, read back
|
|
6
|
+
* through the public surfaces — `docs()`, `search()`, and the agent-docs block.
|
|
7
|
+
*
|
|
8
|
+
* The unit-level rules (what a docs root contributes, what the catalog does
|
|
9
|
+
* with `replaces`/`extends`) are covered in
|
|
10
|
+
* foundation/discovery/docs-discovery.test.mjs. What is pinned here is that a
|
|
11
|
+
* contributed topic is indistinguishable from a built-in one at the surfaces
|
|
12
|
+
* an agent actually reads.
|
|
13
|
+
*
|
|
14
|
+
* Fixtures live under a repo-local temp dir, not /tmp, because Vite refuses to
|
|
15
|
+
* dynamically import a module from outside the project root.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
19
|
+
import * as fs from 'node:fs';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import {docs} from './docs.mjs';
|
|
22
|
+
import {search} from '../search/search.mjs';
|
|
23
|
+
import {loadDocsCatalog} from './_adapter.mjs';
|
|
24
|
+
import {AstryxError} from '../error.mjs';
|
|
25
|
+
|
|
26
|
+
const SLOW = 30_000;
|
|
27
|
+
|
|
28
|
+
let tmpDir;
|
|
29
|
+
|
|
30
|
+
/** A minimal, valid topic. */
|
|
31
|
+
function topic(fields) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'generic',
|
|
34
|
+
name: 'deploying',
|
|
35
|
+
title: 'Deploying',
|
|
36
|
+
description: 'How to ship an app built with Acme widgets.',
|
|
37
|
+
category: 'guide',
|
|
38
|
+
sections: [
|
|
39
|
+
{title: 'Overview', content: [{type: 'prose', text: 'Push the button.'}]},
|
|
40
|
+
],
|
|
41
|
+
...fields,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A consumer project that configures one integration, optionally with a docs
|
|
47
|
+
* root holding the given topics.
|
|
48
|
+
* @param {Record<string, object|string>} [topics] file name → doc
|
|
49
|
+
* @param {{config?: string}} [options]
|
|
50
|
+
*/
|
|
51
|
+
function scaffold(topics, {config} = {}) {
|
|
52
|
+
fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({name: 'consumer'}));
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
path.join(tmpDir, 'astryx.config.mjs'),
|
|
55
|
+
config ?? "export default {integrations: ['@acme/widgets']};\n",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const pkgDir = path.join(tmpDir, 'node_modules', '@acme', 'widgets');
|
|
59
|
+
fs.mkdirSync(pkgDir, {recursive: true});
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
path.join(pkgDir, 'package.json'),
|
|
62
|
+
JSON.stringify({name: '@acme/widgets', version: '1.0.0'}),
|
|
63
|
+
);
|
|
64
|
+
fs.writeFileSync(
|
|
65
|
+
path.join(pkgDir, 'astryx.integration.mjs'),
|
|
66
|
+
`export default ${JSON.stringify(topics ? {docs: './docs'} : {})};\n`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (topics) {
|
|
70
|
+
const docsDir = path.join(pkgDir, 'docs');
|
|
71
|
+
fs.mkdirSync(docsDir, {recursive: true});
|
|
72
|
+
for (const [file, doc] of Object.entries(topics)) {
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
path.join(docsDir, file),
|
|
75
|
+
typeof doc === 'string'
|
|
76
|
+
? doc
|
|
77
|
+
: `export const docs = ${JSON.stringify(doc, null, 2)};\n`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-integration-docs-test-'));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('integration-contributed topics', () => {
|
|
92
|
+
it('lists and reads like a built-in topic, naming its owner', async () => {
|
|
93
|
+
scaffold({'deploying.doc.mjs': topic()});
|
|
94
|
+
|
|
95
|
+
const listed = await docs(undefined, undefined, {cwd: tmpDir});
|
|
96
|
+
const entry = listed.data.find(t => t.topic === 'deploying');
|
|
97
|
+
expect(entry).toMatchObject({
|
|
98
|
+
topic: 'deploying',
|
|
99
|
+
description: 'How to ship an app built with Acme widgets.',
|
|
100
|
+
package: '@acme/widgets',
|
|
101
|
+
});
|
|
102
|
+
// The built-in topics keep their own owner.
|
|
103
|
+
expect(listed.data.find(t => t.topic === 'tokens').package).toBe('@astryxdesign/cli');
|
|
104
|
+
|
|
105
|
+
const detail = await docs('deploying', undefined, {cwd: tmpDir});
|
|
106
|
+
expect(detail.type).toBe('docs.detail');
|
|
107
|
+
expect(detail.data.sections[0].content[0].text).toBe('Push the button.');
|
|
108
|
+
|
|
109
|
+
const section = await docs('deploying', 'overview', {cwd: tmpDir});
|
|
110
|
+
expect(section.data.title).toBe('Overview');
|
|
111
|
+
}, SLOW);
|
|
112
|
+
|
|
113
|
+
it('is invisible to a project that does not configure the integration', async () => {
|
|
114
|
+
scaffold({'deploying.doc.mjs': topic()}, {config: 'export default {};\n'});
|
|
115
|
+
const listed = await docs(undefined, undefined, {cwd: tmpDir});
|
|
116
|
+
expect(listed.data.some(t => t.topic === 'deploying')).toBe(false);
|
|
117
|
+
}, SLOW);
|
|
118
|
+
|
|
119
|
+
it('serves the replacement of a built-in topic, and says what it replaced', async () => {
|
|
120
|
+
scaffold({
|
|
121
|
+
'getting-started.doc.mjs': topic({
|
|
122
|
+
name: 'getting-started',
|
|
123
|
+
replaces: 'getting-started',
|
|
124
|
+
title: 'Getting started',
|
|
125
|
+
description: 'Install Acme widgets.',
|
|
126
|
+
sections: [
|
|
127
|
+
{title: 'Install', content: [{type: 'prose', text: 'yarn add @acme/widgets'}]},
|
|
128
|
+
],
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const detail = await docs('getting-started', undefined, {cwd: tmpDir});
|
|
133
|
+
expect(detail.data.sections.map(s => s.title)).toEqual(['Install']);
|
|
134
|
+
expect(detail.data.sections[0].content[0].text).toBe('yarn add @acme/widgets');
|
|
135
|
+
|
|
136
|
+
const listed = await docs(undefined, undefined, {cwd: tmpDir});
|
|
137
|
+
const entries = listed.data.filter(t => t.topic === 'getting-started');
|
|
138
|
+
expect(entries).toHaveLength(1);
|
|
139
|
+
expect(entries[0]).toMatchObject({
|
|
140
|
+
package: '@acme/widgets',
|
|
141
|
+
replaces: 'getting-started',
|
|
142
|
+
});
|
|
143
|
+
}, SLOW);
|
|
144
|
+
|
|
145
|
+
it('keeps the replaced name resolving when the replacement renames it', async () => {
|
|
146
|
+
scaffold({
|
|
147
|
+
'setup.doc.mjs': topic({name: 'setup', replaces: 'getting-started'}),
|
|
148
|
+
});
|
|
149
|
+
const byOldName = await docs('getting-started', undefined, {cwd: tmpDir});
|
|
150
|
+
const byNewName = await docs('setup', undefined, {cwd: tmpDir});
|
|
151
|
+
expect(byOldName.data.name).toBe('setup');
|
|
152
|
+
expect(byNewName.data.name).toBe('setup');
|
|
153
|
+
}, SLOW);
|
|
154
|
+
|
|
155
|
+
it('merges an extension into the topic it extends', async () => {
|
|
156
|
+
const builtin = await docs('theme');
|
|
157
|
+
const baseSectionTitle = builtin.data.sections[0].title;
|
|
158
|
+
scaffold({
|
|
159
|
+
'theme-internal.doc.mjs': topic({
|
|
160
|
+
name: 'theme-internal',
|
|
161
|
+
extends: 'theme',
|
|
162
|
+
sections: [
|
|
163
|
+
{title: baseSectionTitle, content: [{type: 'prose', text: 'Use the Acme theme.'}]},
|
|
164
|
+
{title: 'Acme themes', content: [{type: 'prose', text: 'Three of them.'}]},
|
|
165
|
+
],
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const extended = await docs('theme', undefined, {cwd: tmpDir});
|
|
170
|
+
// The extension is not a topic of its own.
|
|
171
|
+
expect((await docs(undefined, undefined, {cwd: tmpDir})).data.some(
|
|
172
|
+
t => t.topic === 'theme-internal',
|
|
173
|
+
)).toBe(false);
|
|
174
|
+
expect(extended.data.sections[0].content[0].text).toBe('Use the Acme theme.');
|
|
175
|
+
expect(extended.data.sections.at(-1).title).toBe('Acme themes');
|
|
176
|
+
// Everything the extension did not name is still the base doc's.
|
|
177
|
+
expect(extended.data.sections.length).toBe(builtin.data.sections.length + 1);
|
|
178
|
+
}, SLOW);
|
|
179
|
+
|
|
180
|
+
it('offers the contributed topics as suggestions on an unknown one', async () => {
|
|
181
|
+
scaffold({'deploying.doc.mjs': topic()});
|
|
182
|
+
await expect(docs('nope-not-a-topic', undefined, {cwd: tmpDir})).rejects.toBeInstanceOf(
|
|
183
|
+
AstryxError,
|
|
184
|
+
);
|
|
185
|
+
try {
|
|
186
|
+
await docs('nope-not-a-topic', undefined, {cwd: tmpDir});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
expect(err.suggestions.map(s => s.name)).toContain('deploying');
|
|
189
|
+
}
|
|
190
|
+
}, SLOW);
|
|
191
|
+
|
|
192
|
+
it('indexes a contributed topic in search', async () => {
|
|
193
|
+
scaffold({'deploying.doc.mjs': topic()});
|
|
194
|
+
const {data} = await search('deploying', {cwd: tmpDir, type: 'doc'});
|
|
195
|
+
expect(data.results[0]).toMatchObject({
|
|
196
|
+
domain: 'doc',
|
|
197
|
+
name: 'deploying',
|
|
198
|
+
command: 'astryx docs deploying',
|
|
199
|
+
});
|
|
200
|
+
}, SLOW);
|
|
201
|
+
|
|
202
|
+
it("falls back to the CLI's own topics when the project config is unreadable", async () => {
|
|
203
|
+
scaffold({'deploying.doc.mjs': topic()}, {config: 'export default {integrations: 42};\n'});
|
|
204
|
+
const catalog = await loadDocsCatalog(tmpDir);
|
|
205
|
+
expect(catalog.resolve('tokens')).toBeTruthy();
|
|
206
|
+
expect(catalog.resolve('deploying')).toBeUndefined();
|
|
207
|
+
}, SLOW);
|
|
208
|
+
});
|
package/api/docs/list/list.d.mts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
+
* @param {object} [options]
|
|
6
|
+
* @param {string} [options.cwd]
|
|
5
7
|
* @returns {Promise<import('../docs.type.mjs').DocsListResponse>}
|
|
6
8
|
*/
|
|
7
|
-
export function list(
|
|
9
|
+
export function list({ cwd }?: {
|
|
10
|
+
cwd?: string | undefined;
|
|
11
|
+
}): Promise<import("../docs.type.mjs").DocsListResponse>;
|
package/api/docs/list/list.mjs
CHANGED
|
@@ -3,31 +3,47 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file docs.list leaf — enumerate the available reference-doc topics.
|
|
5
5
|
*
|
|
6
|
-
* @input
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* @input The project's doc catalog (built-in topics plus the ones configured
|
|
7
|
+
* integrations contribute), via the shared adapter. A built-in topic's
|
|
8
|
+
* English `description` is read from its file; a contributed topic already
|
|
9
|
+
* carries the one discovery read. The listing never applies --dense/--zh
|
|
10
|
+
* overlays.
|
|
11
|
+
* @output { type: 'docs.list', data: DocsListEntry[] } — one entry per topic in
|
|
12
|
+
* read order, each naming the package that owns it, matching
|
|
13
|
+
* `astryx --json docs`.
|
|
11
14
|
* @position Leaf under api/docs. Sibling of detail; both share _adapter.mjs.
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import {pathToFileURL} from 'node:url';
|
|
15
|
-
import {
|
|
18
|
+
import {loadDocsCatalog} from '../_adapter.mjs';
|
|
16
19
|
|
|
17
20
|
/**
|
|
21
|
+
* @param {object} [options]
|
|
22
|
+
* @param {string} [options.cwd]
|
|
18
23
|
* @returns {Promise<import('../docs.type.mjs').DocsListResponse>}
|
|
19
24
|
*/
|
|
20
|
-
export async function list() {
|
|
21
|
-
const
|
|
25
|
+
export async function list({cwd} = {}) {
|
|
26
|
+
const catalog = await loadDocsCatalog(cwd);
|
|
22
27
|
/** @type {Array<import('../docs.type.mjs').DocsListEntry>} */
|
|
23
28
|
const entries = [];
|
|
24
|
-
for (const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
for (const entry of catalog.entries()) {
|
|
30
|
+
let description = entry.description ?? '';
|
|
31
|
+
if (entry.description == null) {
|
|
32
|
+
try {
|
|
33
|
+
const mod = await import(pathToFileURL(entry.path).href);
|
|
34
|
+
description = (mod.docs ?? mod.default)?.description ?? '';
|
|
35
|
+
} catch {
|
|
36
|
+
description = '';
|
|
37
|
+
}
|
|
30
38
|
}
|
|
39
|
+
/** @type {import('../docs.type.mjs').DocsListEntry} */
|
|
40
|
+
const listed = {
|
|
41
|
+
topic: entry.name,
|
|
42
|
+
description,
|
|
43
|
+
package: entry.package,
|
|
44
|
+
};
|
|
45
|
+
if (entry.replaces != null) listed.replaces = entry.replaces;
|
|
46
|
+
entries.push(listed);
|
|
31
47
|
}
|
|
32
48
|
return {type: 'docs.list', data: entries};
|
|
33
49
|
}
|
package/api/init/run/run.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {CLI_ROOT} from '../../../foundation/fs/paths.mjs';
|
|
|
20
20
|
import {PathSafetyError} from '../../../foundation/fs/path-safety.mjs';
|
|
21
21
|
import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
|
|
22
22
|
import {installAgentDocs} from '../../../foundation/agent-docs/agent-docs.mjs';
|
|
23
|
+
import {loadDocsCatalog} from '../../docs/_adapter.mjs';
|
|
23
24
|
import {themeTemplate} from '../../theme/template/template.mjs';
|
|
24
25
|
import {listTemplates} from '../../template/template.mjs';
|
|
25
26
|
import {AstryxError} from '../../error.mjs';
|
|
@@ -71,7 +72,7 @@ export function getNextSteps(invocation) {
|
|
|
71
72
|
* @param {string} invocation
|
|
72
73
|
* @param {import('../init.type.mjs').InitRunData} data
|
|
73
74
|
*/
|
|
74
|
-
function applyAgents(cwd, options, invocation, data) {
|
|
75
|
+
async function applyAgents(cwd, options, invocation, data) {
|
|
75
76
|
// Validate --agent up front (a hard error, not a swallowed install failure).
|
|
76
77
|
// ERR_UNKNOWN_AGENT was defined but never wired — a typo like `--agent claud`
|
|
77
78
|
// otherwise silently fell back to writing AGENTS.md. Mirrors --features.
|
|
@@ -88,7 +89,11 @@ function applyAgents(cwd, options, invocation, data) {
|
|
|
88
89
|
? options.agentDocsPath
|
|
89
90
|
: [options.agentDocsPath]
|
|
90
91
|
: undefined;
|
|
91
|
-
|
|
92
|
+
// The block names the topics the agent can read, and an integration's
|
|
93
|
+
// topics are part of that set — resolved here rather than inside
|
|
94
|
+
// installAgentDocs, which is sync and cannot load a project.
|
|
95
|
+
const topics = (await loadDocsCatalog(cwd)).names();
|
|
96
|
+
const written = installAgentDocs(cwd, {agent: options.agent, paths, topics});
|
|
92
97
|
data.docsWritten = written;
|
|
93
98
|
logger.log(`✓ AI agent docs installed → ${written.join(', ')}`);
|
|
94
99
|
} catch (err) {
|
|
@@ -248,7 +253,7 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
248
253
|
nextSteps: false,
|
|
249
254
|
};
|
|
250
255
|
for (const feature of features) {
|
|
251
|
-
if (feature === 'agents') applyAgents(cwd, options, invocation, data);
|
|
256
|
+
if (feature === 'agents') await applyAgents(cwd, options, invocation, data);
|
|
252
257
|
if (feature === 'theme') applyTheme(cwd, invocation, data);
|
|
253
258
|
if (feature === 'template') {
|
|
254
259
|
applyTemplate(cwd, {templateName: options.templateName}, invocation, data);
|
|
@@ -272,7 +277,7 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
272
277
|
templatePath: null,
|
|
273
278
|
nextSteps: true,
|
|
274
279
|
};
|
|
275
|
-
applyAgents(cwd, options, invocation, data);
|
|
280
|
+
await applyAgents(cwd, options, invocation, data);
|
|
276
281
|
logger.log('');
|
|
277
282
|
logger.log(
|
|
278
283
|
` Tip: \`${invocation} init --all\` also points you to the theme and page-building workflows.`,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* error) so `validate-integration` can stay exit-0 in a non-integration dir.
|
|
19
19
|
*
|
|
20
20
|
* The on-disk contribution validators themselves (roots + codemods/templates/
|
|
21
|
-
* components, behind `validateLoadedIntegration`) live in
|
|
21
|
+
* components/docs, behind `validateLoadedIntegration`) live in
|
|
22
22
|
* `foundation/integrations/validate-contributions.mjs`, because foundation also
|
|
23
23
|
* runs them: `Project` collects integration issues and `integration-warnings`
|
|
24
24
|
* nudges about them on ordinary commands. This file re-exports
|
|
@@ -158,6 +158,7 @@ async function validateAtPackageDir(packageDir, identity) {
|
|
|
158
158
|
components: resolveRoot(manifest.components),
|
|
159
159
|
templates: resolveRoot(manifest.templates),
|
|
160
160
|
codemods: resolveRoot(manifest.codemods),
|
|
161
|
+
docs: resolveRoot(manifest.docs),
|
|
161
162
|
issuesUrl: manifest.issuesUrl,
|
|
162
163
|
__spec: identity.name,
|
|
163
164
|
__packageDir: packageDir,
|
package/api/search/search.mjs
CHANGED
|
@@ -30,10 +30,8 @@
|
|
|
30
30
|
* sorts above an incidental mention.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import * as fs from 'node:fs';
|
|
34
|
-
import * as path from 'node:path';
|
|
35
33
|
import {pathToFileURL} from 'node:url';
|
|
36
|
-
import {findCoreDir
|
|
34
|
+
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
37
35
|
import {
|
|
38
36
|
discoverComponents,
|
|
39
37
|
findComponentReadme,
|
|
@@ -42,11 +40,10 @@ import {
|
|
|
42
40
|
import {discoverHooks, findHookDoc} from '../../foundation/discovery/hook-discovery.mjs';
|
|
43
41
|
import {levenshteinDistance} from '../../foundation/text/string-utils.mjs';
|
|
44
42
|
import {discoverTemplates, extractComponents} from '../template/template.mjs';
|
|
43
|
+
import {loadDocsCatalog, loadTopicDoc} from '../docs/_adapter.mjs';
|
|
45
44
|
import {AstryxError} from '../error.mjs';
|
|
46
45
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
47
46
|
|
|
48
|
-
const DOCS_DIR = path.join(CLI_ROOT, 'assets', 'docs');
|
|
49
|
-
|
|
50
47
|
/**
|
|
51
48
|
* A search candidate gathered from one content domain. Extra underscore-
|
|
52
49
|
* prefixed fields carry domain-specific payload used only by {@link toResult}.
|
|
@@ -424,17 +421,31 @@ async function gatherHooks(coreDir) {
|
|
|
424
421
|
|
|
425
422
|
/**
|
|
426
423
|
* Build doc-topic candidates: topic name + description + section prose.
|
|
424
|
+
*
|
|
425
|
+
* Reads the project's catalog rather than the CLI's own docs directory, so a
|
|
426
|
+
* topic an integration contributed (or replaced) is searchable exactly like a
|
|
427
|
+
* built-in one — otherwise the replacement is served by `astryx docs` but
|
|
428
|
+
* invisible to the command whose job is finding it.
|
|
429
|
+
* @param {string} cwd
|
|
427
430
|
* @returns {Promise<Candidate[]>}
|
|
428
431
|
*/
|
|
429
|
-
async function gatherDocs() {
|
|
430
|
-
if (!fs.existsSync(DOCS_DIR)) return [];
|
|
432
|
+
async function gatherDocs(cwd) {
|
|
431
433
|
/** @type {Candidate[]} */
|
|
432
434
|
const candidates = [];
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
435
|
+
let entries;
|
|
436
|
+
try {
|
|
437
|
+
entries = (await loadDocsCatalog(cwd)).entries();
|
|
438
|
+
} catch {
|
|
439
|
+
return candidates;
|
|
440
|
+
}
|
|
441
|
+
for (const entry of entries) {
|
|
442
|
+
let doc = null;
|
|
443
|
+
try {
|
|
444
|
+
doc = await loadTopicDoc(entry);
|
|
445
|
+
} catch {
|
|
446
|
+
// A topic that cannot be loaded is reported by the commands that own
|
|
447
|
+
// integration issues; search just cannot index it.
|
|
448
|
+
}
|
|
438
449
|
let description = '';
|
|
439
450
|
/** @type {string[]} */
|
|
440
451
|
const prose = [];
|
|
@@ -449,11 +460,11 @@ async function gatherDocs() {
|
|
|
449
460
|
}
|
|
450
461
|
candidates.push({
|
|
451
462
|
domain: 'doc',
|
|
452
|
-
name:
|
|
463
|
+
name: entry.name,
|
|
453
464
|
keywords: [],
|
|
454
465
|
description,
|
|
455
466
|
prose,
|
|
456
|
-
_title: doc?.title ||
|
|
467
|
+
_title: doc?.title || entry.title || entry.name,
|
|
457
468
|
});
|
|
458
469
|
}
|
|
459
470
|
return candidates;
|
|
@@ -602,7 +613,7 @@ export async function search(query, options = {}) {
|
|
|
602
613
|
const [components, hooks, docTopics, templates] = await Promise.all([
|
|
603
614
|
wants('component') ? gatherComponents(coreDir) : [],
|
|
604
615
|
wants('hook') ? gatherHooks(coreDir) : [],
|
|
605
|
-
wants('doc') ? gatherDocs() : [],
|
|
616
|
+
wants('doc') ? gatherDocs(cwd) : [],
|
|
606
617
|
wants('template') ? gatherTemplates(cwd) : [],
|
|
607
618
|
]);
|
|
608
619
|
|
|
@@ -52,13 +52,13 @@ export function runPostCodemodHooks(hooks: import("../../authoring/config/type")
|
|
|
52
52
|
* EVERY upgrade path, including the no-codemods short-circuits (#4168).
|
|
53
53
|
*
|
|
54
54
|
* @param {{cwd: string, installedVersion: string, apply: boolean}} ctx
|
|
55
|
-
* @returns {import('./upgrade.type.mjs').AgentDocsSummary}
|
|
55
|
+
* @returns {Promise<import('./upgrade.type.mjs').AgentDocsSummary>}
|
|
56
56
|
*/
|
|
57
57
|
export function refreshAgentDocs({ cwd, installedVersion, apply }: {
|
|
58
58
|
cwd: string;
|
|
59
59
|
installedVersion: string;
|
|
60
60
|
apply: boolean;
|
|
61
|
-
}): import("./upgrade.type.mjs").AgentDocsSummary
|
|
61
|
+
}): Promise<import("./upgrade.type.mjs").AgentDocsSummary>;
|
|
62
62
|
/**
|
|
63
63
|
* Every registered codemod (oldest→newest) for `upgrade --list`. Registry walk
|
|
64
64
|
* + flatten; nothing is run.
|
package/api/upgrade/_adapter.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from '../../assets/codemods/integration-discovery.mjs';
|
|
28
28
|
import {runIntegrationCodemods} from '../../assets/codemods/integration-runner.mjs';
|
|
29
29
|
import {installAgentDocs, inspectAgentDocs} from '../../foundation/agent-docs/agent-docs.mjs';
|
|
30
|
+
import {loadDocsCatalog} from '../docs/_adapter.mjs';
|
|
30
31
|
import {formatCliCommand} from '../../foundation/env/package-manager.mjs';
|
|
31
32
|
import {Project} from '../../foundation/config/project.mjs';
|
|
32
33
|
import {loadIntegrations} from '../../foundation/integrations/integrations.mjs';
|
|
@@ -145,9 +146,9 @@ export async function runPostCodemodHooks(hooks, context) {
|
|
|
145
146
|
* EVERY upgrade path, including the no-codemods short-circuits (#4168).
|
|
146
147
|
*
|
|
147
148
|
* @param {{cwd: string, installedVersion: string, apply: boolean}} ctx
|
|
148
|
-
* @returns {import('./upgrade.type.mjs').AgentDocsSummary}
|
|
149
|
+
* @returns {Promise<import('./upgrade.type.mjs').AgentDocsSummary>}
|
|
149
150
|
*/
|
|
150
|
-
export function refreshAgentDocs({cwd, installedVersion, apply}) {
|
|
151
|
+
export async function refreshAgentDocs({cwd, installedVersion, apply}) {
|
|
151
152
|
const inspection = inspectAgentDocs(cwd, installedVersion);
|
|
152
153
|
/** @type {import('./upgrade.type.mjs').AgentDocsSummary} */
|
|
153
154
|
const summary = {
|
|
@@ -186,7 +187,10 @@ export function refreshAgentDocs({cwd, installedVersion, apply}) {
|
|
|
186
187
|
|
|
187
188
|
// Apply: rewrite only files that already carry a marker (onlyReplace).
|
|
188
189
|
try {
|
|
189
|
-
const written = installAgentDocs(cwd, {
|
|
190
|
+
const written = installAgentDocs(cwd, {
|
|
191
|
+
onlyReplace: true,
|
|
192
|
+
topics: (await loadDocsCatalog(cwd)).names(),
|
|
193
|
+
});
|
|
190
194
|
summary.refreshed = written.length > 0;
|
|
191
195
|
summary.files = written;
|
|
192
196
|
if (summary.refreshed) {
|
package/api/upgrade/run/run.mjs
CHANGED
|
@@ -90,7 +90,7 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
90
90
|
|
|
91
91
|
// Sync the managed agent-docs block FIRST — it documents the installed library
|
|
92
92
|
// independent of codemods, so refresh on every path (issue #4168).
|
|
93
|
-
const agentDocs = refreshAgentDocs({cwd, installedVersion: targetVersion, apply: apply || false});
|
|
93
|
+
const agentDocs = await refreshAgentDocs({cwd, installedVersion: targetVersion, apply: apply || false});
|
|
94
94
|
|
|
95
95
|
if (!options.force && semverGte(currentVersion, targetVersion)) {
|
|
96
96
|
return statusUpToDate({from: currentVersion, to: targetVersion, agentDocs});
|