@astryxdesign/cli 0.7.2-canary.db3a2e7 → 0.7.2-canary.e26723d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/api/build/help/help.mjs +1 -1
- package/api/component/_adapter.d.mts +41 -1
- package/api/component/_adapter.mjs +93 -7
- package/api/component/component.mjs +22 -5
- package/api/component/component.test.mjs +420 -167
- package/api/component/integration-export-name.test.mjs +644 -0
- package/api/component/list/list.mjs +42 -4
- package/api/docs/_adapter.mjs +207 -1
- package/api/docs/integrationDocs.test.mjs +124 -0
- package/api/doctor/theme-checks.mjs +1 -1
- package/api/gap-report/gap-report.mjs +67 -4
- package/api/init/run/run.mjs +2 -2
- package/api/integration/_adapter.d.mts +12 -0
- package/api/integration/_adapter.mjs +54 -0
- package/api/integration/add-contribution.component-names.test.mjs +66 -0
- package/api/integration/add-contribution.mjs +7 -3
- package/api/integration/authoring-checks.mjs +11 -2
- package/api/integration/authoring-checks.test.mjs +39 -0
- package/api/integration/authoring-checks.type.d.mts +9 -0
- package/api/integration/authoring-checks.type.mjs +14 -1
- package/api/integration/integrationComponentConflicts.doc.mjs +21 -5
- package/api/integration/pack-check.mjs +61 -107
- package/api/integration/pack-check.test.mjs +359 -0
- package/api/integration/validate-integration.test.mjs +11 -0
- package/api/search/search.d.mts +9 -0
- package/api/search/search.mjs +108 -16
- package/api/search/search.test.mjs +36 -0
- package/api/swizzle/copy/copy.mjs +28 -14
- package/api/template/template.test.mjs +120 -3
- package/api/theme/build/build.family.test.mjs +16 -0
- package/api/theme/build/build.mjs +81 -22
- package/api/theme/themeBuild.doc.mjs +3 -3
- package/assets/codemods/transforms/next/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/migrate-list-edge-compensation-to-edge.mjs +198 -0
- package/assets/docs/README.md +10 -0
- package/assets/docs/author-a-theme.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +3 -3
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +10 -0
- package/assets/docs/tree/check-the-props.doc.mjs +145 -0
- package/assets/docs/tree/checks.doc.mjs +4 -4
- package/assets/docs/tree/component-family.doc.mjs +5 -1
- package/assets/docs/tree/component-imports.doc.mjs +8 -0
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/links.doc.mjs +2 -2
- package/assets/docs/tree/single-component.doc.mjs +3 -2
- package/assets/docs/tree/troubleshooting.doc.mjs +9 -1
- package/assets/docs/tree/type-scale.doc.mjs +3 -3
- package/assets/docs/tree/versioning.doc.mjs +1 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +2 -1
- package/assets/docs/use-a-theme.doc.mjs +2 -2
- package/assets/docs/working-with-ai.doc.mjs +43 -4
- package/assets/templates/blocks/components/Drawer/DrawerForm.doc.mjs +24 -0
- package/assets/templates/blocks/components/Drawer/DrawerForm.tsx +62 -0
- package/assets/templates/blocks/components/Drawer/DrawerScrim.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerScrim.tsx +68 -0
- package/assets/templates/blocks/components/Drawer/DrawerShowcase.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerShowcase.tsx +41 -0
- package/assets/templates/blocks/components/Drawer/DrawerStart.doc.mjs +21 -0
- package/assets/templates/blocks/components/Drawer/DrawerStart.tsx +41 -0
- package/assets/templates/blocks/components/Drawer/DrawerWidths.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerWidths.tsx +54 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.tsx +73 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.doc.mjs +22 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.tsx +49 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.doc.mjs +21 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.tsx +42 -0
- package/assets/templates/pages/detail-page/page.tsx +3 -3
- package/assets/templates/pages/table-inbox/page.tsx +10 -8
- package/assets/theme.template.ts +1 -1
- package/authoring/doctypes/base/type.ts +6 -0
- package/authoring/doctypes/component/component.doc.mjs +4 -4
- package/authoring/doctypes/component/type.ts +6 -3
- package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
- package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
- package/authoring/doctypes/template/parse.d.mts +6 -2
- package/authoring/doctypes/template/parse.mjs +3 -1
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +8 -1
- package/authoring/integration/parse.test.mjs +10 -0
- package/authoring/integration/schema.d.mts +2 -0
- package/authoring/integration/schema.mjs +1 -0
- package/authoring/integration/type.ts +4 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
- package/clients/cli/commands/build-theme.mjs +1 -1
- package/clients/cli/commands/build-theme.path-safety.test.mjs +84 -0
- package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
- package/clients/cli/commands/doctor-integration-components.doc.mjs +9 -3
- package/clients/cli/commands/doctor-integration.test.mjs +392 -0
- package/clients/cli/commands/doctor.mjs +74 -0
- package/clients/cli/commands/theme-build.doc.mjs +2 -2
- package/clients/cli/lib/component-format.mjs +5 -1
- package/foundation/config/project.mjs +82 -0
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- package/foundation/discovery/component-discovery.d.mts +190 -10
- package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
- package/foundation/discovery/component-discovery.mjs +864 -56
- package/foundation/discovery/component-discovery.test.mjs +96 -0
- package/foundation/discovery/component-replacement.mjs +18 -0
- package/foundation/discovery/component-replacement.test.mjs +40 -0
- package/foundation/discovery/doc-types.d.mts +157 -0
- package/foundation/discovery/doc-types.mjs +1224 -0
- package/foundation/discovery/doc-types.test.mjs +652 -0
- package/foundation/discovery/docs-discovery.d.mts +81 -0
- package/foundation/discovery/docs-discovery.mjs +57 -0
- package/foundation/discovery/module-exports.d.mts +27 -0
- package/foundation/discovery/module-exports.mjs +356 -31
- package/foundation/discovery/module-exports.test.mjs +172 -0
- package/foundation/discovery/package-tsconfig.d.mts +14 -0
- package/foundation/discovery/package-tsconfig.mjs +228 -0
- package/foundation/discovery/package-tsconfig.test.mjs +210 -0
- package/foundation/discovery/project-typescript.d.mts +23 -0
- package/foundation/discovery/project-typescript.mjs +61 -0
- package/foundation/discovery/prop-drift.d.mts +220 -0
- package/foundation/discovery/prop-drift.mjs +1765 -0
- package/foundation/discovery/prop-drift.test.mjs +1197 -0
- package/foundation/discovery/template-adapter.mjs +74 -5
- package/foundation/discovery/type-match.d.mts +53 -0
- package/foundation/discovery/type-match.mjs +660 -0
- package/foundation/discovery/type-match.test.mjs +169 -0
- package/foundation/discovery/type-text.d.mts +68 -0
- package/foundation/discovery/type-text.mjs +658 -0
- package/foundation/discovery/type-text.test.mjs +196 -0
- package/foundation/discovery/unresolved-references.d.mts +62 -0
- package/foundation/discovery/unresolved-references.mjs +790 -0
- package/foundation/discovery/unresolved-references.test.mjs +567 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +9 -1
- package/foundation/doc-compiler/inputs.mjs +23 -0
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/links.d.mts +9 -5
- package/foundation/doc-compiler/links.mjs +4 -2
- package/foundation/integrations/contribution-inventory.mjs +6 -1
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/xle/registry.mjs +4 -0
- package/foundation/xle/registry.test.mjs +28 -0
- package/package.json +10 -10
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
discoverComponents,
|
|
17
17
|
discoverExternalComponentsGrouped,
|
|
18
18
|
discoverValidIntegrationComponents,
|
|
19
|
+
familyMember,
|
|
19
20
|
findComponentReadme,
|
|
20
21
|
findExternalComponentDoc,
|
|
21
22
|
resolveImportPath,
|
|
@@ -66,6 +67,33 @@ async function legacyImport(ext, name, coreDir, docOpts) {
|
|
|
66
67
|
*/
|
|
67
68
|
const coreEntry = doc => ({name: doc.name, package: CORE_PACKAGE, ...doc});
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The full-level entry for a component documented as a member of its family's
|
|
72
|
+
* doc (VStack in Stack's doc): a component doc built from the member's own
|
|
73
|
+
* entry, under its own name, display name and import, shaped like every other
|
|
74
|
+
* full entry. Null when the doc is the component's own.
|
|
75
|
+
* @param {any} docs
|
|
76
|
+
* @param {string} name
|
|
77
|
+
* @param {string} coreDir
|
|
78
|
+
* @returns {any}
|
|
79
|
+
*/
|
|
80
|
+
function memberEntry(docs, name, coreDir) {
|
|
81
|
+
const member = familyMember(docs, name);
|
|
82
|
+
if (!member) return null;
|
|
83
|
+
return {
|
|
84
|
+
name,
|
|
85
|
+
package: CORE_PACKAGE,
|
|
86
|
+
displayName: member.displayName ?? name,
|
|
87
|
+
...(docs.group ? {group: docs.group} : {}),
|
|
88
|
+
...(docs.category ? {category: docs.category} : {}),
|
|
89
|
+
usage: member.usage ?? {description: member.description ?? ''},
|
|
90
|
+
props: member.props ?? [],
|
|
91
|
+
...(member.examples ? {examples: member.examples} : {}),
|
|
92
|
+
...(docs.theming ? {theming: docs.theming} : {}),
|
|
93
|
+
import: resolveImportPath(coreDir, name),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
69
97
|
/**
|
|
70
98
|
* The entry for a Core slot an integration component replaces
|
|
71
99
|
* (spec:AST-035 FR11), at the list's detail level: the replacement's own name,
|
|
@@ -177,7 +205,11 @@ export async function componentList(
|
|
|
177
205
|
entries.push({
|
|
178
206
|
name: comp,
|
|
179
207
|
package: CORE_PACKAGE,
|
|
180
|
-
description:
|
|
208
|
+
description:
|
|
209
|
+
familyMember(docs, comp)?.description ||
|
|
210
|
+
docs.usage?.description ||
|
|
211
|
+
docs.description ||
|
|
212
|
+
'',
|
|
181
213
|
import: resolveImportPath(coreDir, comp),
|
|
182
214
|
});
|
|
183
215
|
} catch {
|
|
@@ -215,7 +247,8 @@ export async function componentList(
|
|
|
215
247
|
const readme = findComponentReadme(coreDir, comp);
|
|
216
248
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
217
249
|
try {
|
|
218
|
-
|
|
250
|
+
const docs = await loadComponentDoc(readme, {zh, lang, dense});
|
|
251
|
+
entries.push(memberEntry(docs, comp, coreDir) ?? coreEntry(docs));
|
|
219
252
|
} catch {
|
|
220
253
|
entries.push({name: `XDS${comp}`, package: CORE_PACKAGE, description: ''});
|
|
221
254
|
}
|
|
@@ -269,7 +302,11 @@ export async function componentList(
|
|
|
269
302
|
result[cat].push({
|
|
270
303
|
name: comp,
|
|
271
304
|
package: CORE_PACKAGE,
|
|
272
|
-
description:
|
|
305
|
+
description:
|
|
306
|
+
familyMember(docs, comp)?.description ||
|
|
307
|
+
docs.usage?.description ||
|
|
308
|
+
docs.description ||
|
|
309
|
+
'',
|
|
273
310
|
import: resolveImportPath(coreDir, comp),
|
|
274
311
|
});
|
|
275
312
|
} catch {
|
|
@@ -310,7 +347,8 @@ export async function componentList(
|
|
|
310
347
|
const readme = findComponentReadme(coreDir, comp);
|
|
311
348
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
312
349
|
try {
|
|
313
|
-
|
|
350
|
+
const docs = await loadComponentDoc(readme, {zh, lang, dense});
|
|
351
|
+
result[cat].push(memberEntry(docs, comp, coreDir) ?? coreEntry(docs));
|
|
314
352
|
} catch {
|
|
315
353
|
result[cat].push({name: `XDS${comp}`, package: CORE_PACKAGE, description: ''});
|
|
316
354
|
}
|
package/api/docs/_adapter.mjs
CHANGED
|
@@ -53,6 +53,17 @@ import {
|
|
|
53
53
|
selfDocSection,
|
|
54
54
|
} from '../../foundation/discovery/authoring-self-docs.mjs';
|
|
55
55
|
import {CLI_PROVIDER_ID} from '../../foundation/identity/providers.mjs';
|
|
56
|
+
import {
|
|
57
|
+
CORE_PACKAGE,
|
|
58
|
+
discoverOwnedComponents,
|
|
59
|
+
discoverValidIntegrationComponents,
|
|
60
|
+
} from '../../foundation/discovery/component-discovery.mjs';
|
|
61
|
+
import {loadComponentDoc} from '../../foundation/discovery/component-loader.mjs';
|
|
62
|
+
import {
|
|
63
|
+
discoverCoreTemplates,
|
|
64
|
+
discoverIntegrationTemplatesForOne,
|
|
65
|
+
} from '../../foundation/discovery/template-adapter.mjs';
|
|
66
|
+
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
56
67
|
import {AstryxError} from '../error.mjs';
|
|
57
68
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
58
69
|
|
|
@@ -495,12 +506,205 @@ async function docFinder(catalog, fromProvider) {
|
|
|
495
506
|
};
|
|
496
507
|
}
|
|
497
508
|
}
|
|
509
|
+
if (CONTRIBUTED_KINDS.has(parsed.kind)) {
|
|
510
|
+
const link = await contributedDocLink(
|
|
511
|
+
catalog,
|
|
512
|
+
provider,
|
|
513
|
+
parsed.kind,
|
|
514
|
+
parsed.name,
|
|
515
|
+
);
|
|
516
|
+
if (link)
|
|
517
|
+
return {link: {target, ...link}, kind: parsed.kind, typed: null};
|
|
518
|
+
}
|
|
519
|
+
const searchType =
|
|
520
|
+
parsed.kind === 'component'
|
|
521
|
+
? 'component'
|
|
522
|
+
: parsed.kind === 'page' || parsed.kind === 'block'
|
|
523
|
+
? 'template'
|
|
524
|
+
: 'doc';
|
|
498
525
|
return {
|
|
499
|
-
problem: `"${target}" names no doc. Find it with \`astryx search ${parsed.name} --type
|
|
526
|
+
problem: `"${target}" names no doc. Find it with \`astryx search ${parsed.name} --type ${searchType}\`, then name it as \`[<provider>:]<kind>:<name>\`.`,
|
|
500
527
|
};
|
|
501
528
|
};
|
|
502
529
|
}
|
|
503
530
|
|
|
531
|
+
/** The doc kinds no docs tree holds: components and templates. */
|
|
532
|
+
const CONTRIBUTED_KINDS = new Set(['component', 'page', 'block']);
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* The link to a component, page, or block doc (spec:AST-047 FR9). No docs tree
|
|
536
|
+
* holds these, so a link finds them among the component and template records
|
|
537
|
+
* the catalog carries, the ones `astryx component` and `astryx template` open,
|
|
538
|
+
* and reads as that command. A component is named by its export name, a
|
|
539
|
+
* template by its id; anything but Core's names its package.
|
|
540
|
+
* @param {DocsCatalog} catalog
|
|
541
|
+
* @param {string} provider normalized provider id
|
|
542
|
+
* @param {string} kind `component`, `page`, or `block`
|
|
543
|
+
* @param {string} name
|
|
544
|
+
* @returns {Promise<Omit<import('../../foundation/doc-compiler/links.mjs').DocLink, 'target'> | null>}
|
|
545
|
+
*/
|
|
546
|
+
async function contributedDocLink(catalog, provider, kind, name) {
|
|
547
|
+
const contributions = await catalog.contributions();
|
|
548
|
+
if (!contributions) return null;
|
|
549
|
+
const sameProvider = (/** @type {string} */ providerId) => {
|
|
550
|
+
try {
|
|
551
|
+
return normalizeProviderId(providerId) === provider;
|
|
552
|
+
} catch {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
if (kind === 'component') {
|
|
557
|
+
const record = contributions.components.find(
|
|
558
|
+
component =>
|
|
559
|
+
component.name === name && sameProvider(component.providerId),
|
|
560
|
+
);
|
|
561
|
+
if (!record) return null;
|
|
562
|
+
/** @type {any} */
|
|
563
|
+
let doc = null;
|
|
564
|
+
try {
|
|
565
|
+
doc = record.docPath ? await loadComponentDoc(record.docPath) : null;
|
|
566
|
+
} catch {
|
|
567
|
+
// The component checks report a doc that does not load; the link
|
|
568
|
+
// still opens the component.
|
|
569
|
+
}
|
|
570
|
+
const entry =
|
|
571
|
+
doc?.name === name
|
|
572
|
+
? doc
|
|
573
|
+
: doc?.components?.find(
|
|
574
|
+
(/** @type {any} */ each) => each.name === name,
|
|
575
|
+
);
|
|
576
|
+
// Bare, Core's name opens Core's component unless another package ships
|
|
577
|
+
// the same name or replaces it (spec:AST-035 FR11); then the link selects
|
|
578
|
+
// Core, as it always selects any other package.
|
|
579
|
+
const selectPackage =
|
|
580
|
+
record.package !== CORE_PACKAGE ||
|
|
581
|
+
contributions.components.some(
|
|
582
|
+
other =>
|
|
583
|
+
other.package !== CORE_PACKAGE &&
|
|
584
|
+
(other.name === name || other.replaces === name),
|
|
585
|
+
);
|
|
586
|
+
return {
|
|
587
|
+
id: createDocId(provider, 'component', name),
|
|
588
|
+
route: null,
|
|
589
|
+
title: entry?.displayName ?? name,
|
|
590
|
+
summary: entry?.description ?? doc?.description ?? '',
|
|
591
|
+
command: `astryx component ${name}${selectPackage ? ` --package ${record.package}` : ''}`,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
const template = contributions.templates.find(
|
|
595
|
+
each =>
|
|
596
|
+
each.type === kind && each.id === name && sameProvider(each.providerId),
|
|
597
|
+
);
|
|
598
|
+
if (!template) return null;
|
|
599
|
+
// The same for a Core template: another package's template of its kind
|
|
600
|
+
// with its id, or one that replaces it, takes the bare id.
|
|
601
|
+
const selectPackage =
|
|
602
|
+
template.package !== CORE_PACKAGE ||
|
|
603
|
+
contributions.templates.some(
|
|
604
|
+
other =>
|
|
605
|
+
other.package !== CORE_PACKAGE &&
|
|
606
|
+
other.type === template.type &&
|
|
607
|
+
(other.id === name || other.replaces === name),
|
|
608
|
+
);
|
|
609
|
+
return {
|
|
610
|
+
id: createDocId(provider, template.type, name),
|
|
611
|
+
route: null,
|
|
612
|
+
title: template.displayName ?? template.name,
|
|
613
|
+
summary: template.description ?? '',
|
|
614
|
+
command: `astryx template ${name} --type ${template.type}${selectPackage ? ` --package ${template.package}` : ''}`,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* The component and template docs one integration package's links can name:
|
|
620
|
+
* Core's and its own, as `doctor integration docs` checks it before any app
|
|
621
|
+
* configures it.
|
|
622
|
+
* @param {any} integration a loaded integration: its name, provider id, roots,
|
|
623
|
+
* and package directory
|
|
624
|
+
* @returns {Promise<import('../../foundation/discovery/docs-discovery.mjs').LinkableContributions>}
|
|
625
|
+
*/
|
|
626
|
+
async function packageContributions(integration) {
|
|
627
|
+
const own = integration.providerId ?? integration.name;
|
|
628
|
+
/** @type {import('../../foundation/discovery/docs-discovery.mjs').LinkableComponent[]} */
|
|
629
|
+
const components = [];
|
|
630
|
+
const coreDir = findCoreDir(integration.__packageDir ?? process.cwd());
|
|
631
|
+
if (coreDir) {
|
|
632
|
+
try {
|
|
633
|
+
for (const record of discoverOwnedComponents(coreDir, [])) {
|
|
634
|
+
components.push({
|
|
635
|
+
providerId: CORE_PACKAGE,
|
|
636
|
+
package: CORE_PACKAGE,
|
|
637
|
+
name: record.name,
|
|
638
|
+
docPath: record.docPath,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
} catch {
|
|
642
|
+
// The components check reports a Core it cannot read.
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (integration.components) {
|
|
646
|
+
try {
|
|
647
|
+
const {components: records} =
|
|
648
|
+
await discoverValidIntegrationComponents(integration);
|
|
649
|
+
for (const record of records) {
|
|
650
|
+
components.push({
|
|
651
|
+
providerId: own,
|
|
652
|
+
package: integration.name,
|
|
653
|
+
name: record.name,
|
|
654
|
+
docPath: record.docPath,
|
|
655
|
+
// Declared: this package alone cannot tell whether an app applies
|
|
656
|
+
// it, so a link to the Core name it names selects Core.
|
|
657
|
+
...(typeof record.replaces === 'string'
|
|
658
|
+
? {replaces: record.replaces}
|
|
659
|
+
: {}),
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
} catch {
|
|
663
|
+
// `validate` reports components that do not load.
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/** @type {import('../../foundation/discovery/docs-discovery.mjs').LinkableTemplate[]} */
|
|
667
|
+
const templates = [];
|
|
668
|
+
/** @param {any} template @param {string} providerId @param {string} pkg */
|
|
669
|
+
const addTemplate = (template, providerId, pkg) => {
|
|
670
|
+
if (template.type !== 'page' && template.type !== 'block') return;
|
|
671
|
+
templates.push({
|
|
672
|
+
providerId,
|
|
673
|
+
package: pkg,
|
|
674
|
+
type: template.type,
|
|
675
|
+
id: template.dirName,
|
|
676
|
+
name: template.name,
|
|
677
|
+
...(template.displayName == null
|
|
678
|
+
? {}
|
|
679
|
+
: {displayName: template.displayName}),
|
|
680
|
+
...(template.description == null
|
|
681
|
+
? {}
|
|
682
|
+
: {description: template.description}),
|
|
683
|
+
...(typeof template.replaces === 'string'
|
|
684
|
+
? {replaces: template.replaces}
|
|
685
|
+
: {}),
|
|
686
|
+
});
|
|
687
|
+
};
|
|
688
|
+
try {
|
|
689
|
+
for (const template of await discoverCoreTemplates()) {
|
|
690
|
+
addTemplate(template, CORE_PACKAGE, CORE_PACKAGE);
|
|
691
|
+
}
|
|
692
|
+
} catch {
|
|
693
|
+
// Core templates that do not load leave nothing to link to.
|
|
694
|
+
}
|
|
695
|
+
if (integration.templates) {
|
|
696
|
+
try {
|
|
697
|
+
const {templates: records} =
|
|
698
|
+
await discoverIntegrationTemplatesForOne(integration);
|
|
699
|
+
for (const template of records)
|
|
700
|
+
addTemplate(template, own, integration.name);
|
|
701
|
+
} catch {
|
|
702
|
+
// `validate` reports templates that do not load.
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return {components, templates};
|
|
706
|
+
}
|
|
707
|
+
|
|
504
708
|
/**
|
|
505
709
|
* How a doc's links find their targets (spec:AST-047 FR9), as
|
|
506
710
|
* {@link docFinder} finds them: each resolves to the link a read shows.
|
|
@@ -701,6 +905,7 @@ export async function docsLinkProblems(
|
|
|
701
905
|
*/
|
|
702
906
|
export async function packageDocsProblems(integration, discovered) {
|
|
703
907
|
const catalog = DocsCatalog.fromBuiltins();
|
|
908
|
+
catalog.setContributions(() => packageContributions(integration));
|
|
704
909
|
for (const record of discovered.records) catalog.add(record);
|
|
705
910
|
catalog.addTreeInputs({
|
|
706
911
|
namespaces: discovered.namespaces.map(input => ({...input, rank: 1})),
|
|
@@ -794,6 +999,7 @@ async function namespaceReferenceTarget(catalog, route, lang) {
|
|
|
794
999
|
*/
|
|
795
1000
|
export async function packageReferenceProblems(integration, discovered) {
|
|
796
1001
|
const catalog = DocsCatalog.fromBuiltins();
|
|
1002
|
+
catalog.setContributions(() => packageContributions(integration));
|
|
797
1003
|
for (const record of discovered.records) catalog.add(record);
|
|
798
1004
|
catalog.addTreeInputs({
|
|
799
1005
|
namespaces: discovered.namespaces.map(input => ({...input, rank: 1})),
|
|
@@ -22,6 +22,7 @@ import {docs} from './docs.mjs';
|
|
|
22
22
|
import {search} from '../search/search.mjs';
|
|
23
23
|
import {loadDocsCatalog} from './_adapter.mjs';
|
|
24
24
|
import {AstryxError} from '../error.mjs';
|
|
25
|
+
import {discoverCoreTemplates} from '../../foundation/discovery/template-adapter.mjs';
|
|
25
26
|
|
|
26
27
|
const SLOW = 30_000;
|
|
27
28
|
|
|
@@ -110,6 +111,129 @@ describe('integration-contributed topics', () => {
|
|
|
110
111
|
expect(section.data.title).toBe('Overview');
|
|
111
112
|
}, SLOW);
|
|
112
113
|
|
|
114
|
+
it('reads a link to a component or a template as the command that opens it', async () => {
|
|
115
|
+
scaffold({
|
|
116
|
+
'using-widgets.doc.mjs': topic({
|
|
117
|
+
name: 'using-widgets',
|
|
118
|
+
title: 'Using widgets',
|
|
119
|
+
sections: [
|
|
120
|
+
{
|
|
121
|
+
title: 'Overview',
|
|
122
|
+
content: [
|
|
123
|
+
{
|
|
124
|
+
type: 'prose',
|
|
125
|
+
text: 'Start with {@link component:AcmeButton} or {@link block:acme-card}, built on {@link @astryxdesign/core:component:Button}.',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
const pkgDir = path.join(tmpDir, 'node_modules', '@acme', 'widgets');
|
|
133
|
+
fs.writeFileSync(
|
|
134
|
+
path.join(pkgDir, 'astryx.integration.mjs'),
|
|
135
|
+
"export default {components: './components', templates: './templates', docs: './docs'};\n",
|
|
136
|
+
);
|
|
137
|
+
fs.mkdirSync(path.join(pkgDir, 'components'));
|
|
138
|
+
fs.writeFileSync(
|
|
139
|
+
path.join(pkgDir, 'components', 'AcmeButton.doc.mjs'),
|
|
140
|
+
"export default {type: 'component', name: 'AcmeButton', displayName: 'Acme Button', description: 'A button.', props: []};\n",
|
|
141
|
+
);
|
|
142
|
+
fs.writeFileSync(
|
|
143
|
+
path.join(pkgDir, 'components', 'AcmeButton.tsx'),
|
|
144
|
+
'export function AcmeButton() { return null; }\n',
|
|
145
|
+
);
|
|
146
|
+
fs.mkdirSync(path.join(pkgDir, 'templates'));
|
|
147
|
+
fs.writeFileSync(
|
|
148
|
+
path.join(pkgDir, 'templates', 'acme-card.doc.mjs'),
|
|
149
|
+
"export default {type: 'block', name: 'Acme card', description: 'A card.'};\n",
|
|
150
|
+
);
|
|
151
|
+
fs.writeFileSync(
|
|
152
|
+
path.join(pkgDir, 'templates', 'acme-card.tsx'),
|
|
153
|
+
'export default function Fixture() { return null; }\n',
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const detail = await docs('using-widgets', undefined, {cwd: tmpDir, full: true});
|
|
157
|
+
|
|
158
|
+
const prose = JSON.stringify(detail.data.sections);
|
|
159
|
+
expect(prose).toContain('`astryx component AcmeButton --package @acme/widgets`');
|
|
160
|
+
expect(prose).toContain(
|
|
161
|
+
'`astryx template acme-card --type block --package @acme/widgets`',
|
|
162
|
+
);
|
|
163
|
+
expect(prose).toContain('`astryx component Button`');
|
|
164
|
+
expect(prose).not.toContain('{@link');
|
|
165
|
+
}, SLOW);
|
|
166
|
+
|
|
167
|
+
it('selects Core in a link only where the bare name would open something else', async () => {
|
|
168
|
+
const core = await discoverCoreTemplates();
|
|
169
|
+
const page = core.find(template => template.type === 'page')?.dirName;
|
|
170
|
+
const block = core.find(template => template.type === 'block')?.dirName;
|
|
171
|
+
scaffold({
|
|
172
|
+
'using-widgets.doc.mjs': topic({
|
|
173
|
+
name: 'using-widgets',
|
|
174
|
+
title: 'Using widgets',
|
|
175
|
+
sections: [
|
|
176
|
+
{
|
|
177
|
+
title: 'Overview',
|
|
178
|
+
content: [
|
|
179
|
+
{
|
|
180
|
+
type: 'prose',
|
|
181
|
+
text:
|
|
182
|
+
'Core: {@link @astryxdesign/core:component:Button}, {@link @astryxdesign/core:component:Badge}, ' +
|
|
183
|
+
`{@link @astryxdesign/core:page:${page}}, {@link @astryxdesign/core:block:${block}}.`,
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
const pkgDir = path.join(tmpDir, 'node_modules', '@acme', 'widgets');
|
|
191
|
+
// The CLI range that turns component replacement on.
|
|
192
|
+
fs.writeFileSync(
|
|
193
|
+
path.join(pkgDir, 'package.json'),
|
|
194
|
+
JSON.stringify({
|
|
195
|
+
name: '@acme/widgets',
|
|
196
|
+
version: '1.0.0',
|
|
197
|
+
peerDependencies: {'@astryxdesign/cli': '>=0.6.7'},
|
|
198
|
+
peerDependenciesMeta: {'@astryxdesign/cli': {optional: true}},
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
fs.writeFileSync(
|
|
202
|
+
path.join(pkgDir, 'astryx.integration.mjs'),
|
|
203
|
+
"export default {components: './components', templates: './templates', docs: './docs'};\n",
|
|
204
|
+
);
|
|
205
|
+
fs.mkdirSync(path.join(pkgDir, 'components'));
|
|
206
|
+
// AcmeButton replaces Core's Button, so bare `Button` opens AcmeButton.
|
|
207
|
+
fs.writeFileSync(
|
|
208
|
+
path.join(pkgDir, 'components', 'AcmeButton.doc.mjs'),
|
|
209
|
+
"export default {type: 'component', name: 'AcmeButton', displayName: 'Acme Button', description: 'A button.', replaces: 'Button', props: []};\n",
|
|
210
|
+
);
|
|
211
|
+
fs.writeFileSync(
|
|
212
|
+
path.join(pkgDir, 'components', 'AcmeButton.tsx'),
|
|
213
|
+
'export function AcmeButton() { return null; }\n',
|
|
214
|
+
);
|
|
215
|
+
// A page template with a Core page's id makes the bare id ambiguous.
|
|
216
|
+
fs.mkdirSync(path.join(pkgDir, 'templates'));
|
|
217
|
+
fs.writeFileSync(
|
|
218
|
+
path.join(pkgDir, 'templates', `${page}.doc.mjs`),
|
|
219
|
+
"export default {type: 'page', name: 'Acme page', displayName: 'Acme page', description: 'A page.'};\n",
|
|
220
|
+
);
|
|
221
|
+
fs.writeFileSync(
|
|
222
|
+
path.join(pkgDir, 'templates', `${page}.tsx`),
|
|
223
|
+
'export default function Fixture() { return null; }\n',
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const detail = await docs('using-widgets', undefined, {cwd: tmpDir, full: true});
|
|
227
|
+
|
|
228
|
+
const prose = JSON.stringify(detail.data.sections);
|
|
229
|
+
expect(prose).toContain('`astryx component Button --package @astryxdesign/core`');
|
|
230
|
+
expect(prose).toContain('`astryx component Badge`');
|
|
231
|
+
expect(prose).toContain(
|
|
232
|
+
`\`astryx template ${page} --type page --package @astryxdesign/core\``,
|
|
233
|
+
);
|
|
234
|
+
expect(prose).toContain(`\`astryx template ${block} --type block\``);
|
|
235
|
+
}, SLOW);
|
|
236
|
+
|
|
113
237
|
it('is invisible to a project that does not configure the integration', async () => {
|
|
114
238
|
scaffold({'deploying.doc.mjs': topic()}, {config: 'export default {};\n'});
|
|
115
239
|
const listed = await docs(undefined, undefined, {cwd: tmpDir});
|
|
@@ -736,7 +736,7 @@ export async function checkAppThemes(cwd) {
|
|
|
736
736
|
label: 'App imports the theme module',
|
|
737
737
|
status: 'warn',
|
|
738
738
|
message: `Could not prove that project source imports ${state.module.path}${importEvidence.parseFailures ? `; ${importEvidence.parseFailures} source file(s) could not be parsed` : ''}.`,
|
|
739
|
-
fix: `Import {themes, defaultThemeSlug} from the generated module and {Theme} from '@astryxdesign/core', then render <Theme theme={themes[defaultThemeSlug]}>.`,
|
|
739
|
+
fix: `Import {themes, defaultThemeSlug} from the generated module and {Theme} from '@astryxdesign/core/theme', then render <Theme theme={themes[defaultThemeSlug]}>.`,
|
|
740
740
|
});
|
|
741
741
|
}
|
|
742
742
|
|
|
@@ -17,7 +17,18 @@
|
|
|
17
17
|
import {Worker} from 'node:worker_threads';
|
|
18
18
|
import {AstryxError} from '../error.mjs';
|
|
19
19
|
import {Project} from '../../foundation/config/project.mjs';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
CORE_PACKAGE,
|
|
22
|
+
discoverIntegrationComponents,
|
|
23
|
+
docStem,
|
|
24
|
+
findComponentReadme,
|
|
25
|
+
findIntegrationComponent,
|
|
26
|
+
integrationRecordHold,
|
|
27
|
+
isIntegrationMember,
|
|
28
|
+
unscopedComponentOwners,
|
|
29
|
+
} from '../../foundation/discovery/component-discovery.mjs';
|
|
30
|
+
import {legacyExternalKeeps} from '../component/_adapter.mjs';
|
|
31
|
+
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
21
32
|
import {captureEnv} from '../../foundation/debug/event.mjs';
|
|
22
33
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
23
34
|
import {parseGapReportReceipt} from '../../authoring/gap-report/parse.mjs';
|
|
@@ -178,13 +189,65 @@ async function selectTarget(project, component, packageName) {
|
|
|
178
189
|
coreName,
|
|
179
190
|
);
|
|
180
191
|
if (replacement) return targetForPackage(project, replacement.package);
|
|
192
|
+
// The owner rule runs over every owner discovery finds, loadable or not
|
|
193
|
+
// (a doc recorded under another export still answers to its file's name);
|
|
194
|
+
// then only owners whose docs load route a report.
|
|
195
|
+
const wanted = bareComponent.toLowerCase();
|
|
196
|
+
const loads = new Set(records.map(r => `${r.package}\0${r.docPath}`));
|
|
197
|
+
const recorded = project.loadedIntegrations.flatMap(integration =>
|
|
198
|
+
discoverIntegrationComponents(integration).flatMap(r =>
|
|
199
|
+
r.name.toLowerCase() === wanted
|
|
200
|
+
? [{...r, hold: integrationRecordHold(r)}]
|
|
201
|
+
: docStem(r.docPath).toLowerCase() === wanted && !isIntegrationMember(r)
|
|
202
|
+
? [{...r, hold: 'doc'}]
|
|
203
|
+
: [],
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
// The checks below find files by name, so they run on every spelling the
|
|
207
|
+
// case-blind match above found, as well as the one typed.
|
|
208
|
+
const spellings = new Set([
|
|
209
|
+
bareComponent,
|
|
210
|
+
...recorded.flatMap(r =>
|
|
211
|
+
[r.name, docStem(r.docPath)].filter(n => n.toLowerCase() === wanted),
|
|
212
|
+
),
|
|
213
|
+
]);
|
|
214
|
+
// Core also holds a name only its doc lookup finds, as in detail: a name a
|
|
215
|
+
// doc records yields to it, though it routes no report itself.
|
|
216
|
+
const coreDir = findCoreDir(project.cwd);
|
|
217
|
+
const coreDocs =
|
|
218
|
+
coreDir != null &&
|
|
219
|
+
[...spellings].some(name => findComponentReadme(coreDir, name) != null);
|
|
220
|
+
const ruled = unscopedComponentOwners([
|
|
221
|
+
...records
|
|
222
|
+
.filter(
|
|
223
|
+
r => r.package === CORE_PACKAGE && r.name.toLowerCase() === wanted,
|
|
224
|
+
)
|
|
225
|
+
.map(r => ({...r, hold: 'core'})),
|
|
226
|
+
...recorded,
|
|
227
|
+
// A hidden doc named after its file still holds that name, as the
|
|
228
|
+
// component lookup finds it; it routes no report.
|
|
229
|
+
...project.loadedIntegrations.flatMap(integration =>
|
|
230
|
+
[...spellings].flatMap(spelling => {
|
|
231
|
+
const found = findIntegrationComponent(integration, spelling);
|
|
232
|
+
return found?.hold === 'file'
|
|
233
|
+
? [{...found, package: integration.name}]
|
|
234
|
+
: [];
|
|
235
|
+
}),
|
|
236
|
+
),
|
|
237
|
+
]).filter(r => !coreDocs || r.hold !== 'doc');
|
|
238
|
+
// A legacy docs package keeps an export name a doc records, as in detail.
|
|
181
239
|
const owners = [
|
|
182
240
|
...new Set(
|
|
183
|
-
|
|
241
|
+
([...spellings].some(name =>
|
|
242
|
+
legacyExternalKeeps(project.cwd, name, ruled),
|
|
243
|
+
)
|
|
244
|
+
? []
|
|
245
|
+
: ruled
|
|
246
|
+
)
|
|
184
247
|
.filter(
|
|
185
|
-
|
|
248
|
+
r => r.hold === 'core' || loads.has(`${r.package}\0${r.docPath}`),
|
|
186
249
|
)
|
|
187
|
-
.map(
|
|
250
|
+
.map(r => r.package),
|
|
188
251
|
),
|
|
189
252
|
];
|
|
190
253
|
|
package/api/init/run/run.mjs
CHANGED
|
@@ -50,11 +50,11 @@ export function getNextSteps(invocation) {
|
|
|
50
50
|
` (run \`${invocation} doctor\` to verify)`,
|
|
51
51
|
" 2. Import base styles: import '@astryxdesign/core/reset.css'",
|
|
52
52
|
" and import '@astryxdesign/core/astryx.css'",
|
|
53
|
-
" 3. Import components: import { Button } from '@astryxdesign/core'",
|
|
53
|
+
" 3. Import components: import { Button } from '@astryxdesign/core/Button'",
|
|
54
54
|
' 4. Add and wire a theme:',
|
|
55
55
|
' npm install @astryxdesign/theme-neutral',
|
|
56
56
|
` ${invocation} theme add neutral`,
|
|
57
|
-
" import { Theme } from '@astryxdesign/core'",
|
|
57
|
+
" import { Theme } from '@astryxdesign/core/theme'",
|
|
58
58
|
" import { themes, defaultThemeSlug } from './astryx-themes'",
|
|
59
59
|
' <Theme theme={themes[defaultThemeSlug]}>...</Theme>',
|
|
60
60
|
` Extend an imported theme to customize it. Run \`${invocation} theme eject <slug>\` only to fork source.`,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Each component doc's props compared with the component's TypeScript props,
|
|
6
|
+
* read with the project's own compiler (spec:AST-039 FR12). Without one, the
|
|
7
|
+
* report says the comparison was skipped.
|
|
8
|
+
* @param {any} integration a loaded integration
|
|
9
|
+
* @param {string} cwd
|
|
10
|
+
* @returns {Promise<import('../../foundation/discovery/prop-drift.mjs').PropDriftReport>}
|
|
11
|
+
*/
|
|
12
|
+
export function integrationPropDrift(integration: any, cwd: string): Promise<import("../../foundation/discovery/prop-drift.mjs").PropDriftReport>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The integration subject's access to the environment (INV21): reading
|
|
5
|
+
* one package's component docs and sources with the project's own TypeScript.
|
|
6
|
+
*
|
|
7
|
+
* @input a loaded integration and the directory the command runs in
|
|
8
|
+
* @output the reports the integration checks attach to their results
|
|
9
|
+
* @position api/integration (internal; not exported from ./api)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
discoverValidIntegrationComponents,
|
|
14
|
+
forEachIntegrationDoc,
|
|
15
|
+
integrationSourceForDoc,
|
|
16
|
+
} from '../../foundation/discovery/component-discovery.mjs';
|
|
17
|
+
import {comparePropDocs} from '../../foundation/discovery/prop-drift.mjs';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Each component doc's props compared with the component's TypeScript props,
|
|
21
|
+
* read with the project's own compiler (spec:AST-039 FR12). Without one, the
|
|
22
|
+
* report says the comparison was skipped.
|
|
23
|
+
* @param {any} integration a loaded integration
|
|
24
|
+
* @param {string} cwd
|
|
25
|
+
* @returns {Promise<import('../../foundation/discovery/prop-drift.mjs').PropDriftReport>}
|
|
26
|
+
*/
|
|
27
|
+
export async function integrationPropDrift(integration, cwd) {
|
|
28
|
+
const packageDir = /** @type {{__packageDir?: string}} */ (integration)
|
|
29
|
+
.__packageDir;
|
|
30
|
+
const {components} = await discoverValidIntegrationComponents(integration);
|
|
31
|
+
// Every doc the package ships has its prop tables compared or listed as not
|
|
32
|
+
// compared. A doc discovery leaves out (a hidden one, a second doc for one
|
|
33
|
+
// name, or one whose source or own file doesn't load) gets a record under
|
|
34
|
+
// its file name.
|
|
35
|
+
const recorded = new Set(components.map(c => c.docPath));
|
|
36
|
+
/** @type {typeof components} */
|
|
37
|
+
const left = [];
|
|
38
|
+
forEachIntegrationDoc(integration?.components, (docPath, stem) => {
|
|
39
|
+
if (recorded.has(docPath)) return;
|
|
40
|
+
recorded.add(docPath);
|
|
41
|
+
left.push({
|
|
42
|
+
name: stem,
|
|
43
|
+
package: integration.name,
|
|
44
|
+
docPath,
|
|
45
|
+
sourcePath: integrationSourceForDoc(docPath),
|
|
46
|
+
issuesUrl: integration.issuesUrl,
|
|
47
|
+
group: null,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
return comparePropDocs([...components, ...left], {
|
|
51
|
+
packageDir: packageDir ?? cwd,
|
|
52
|
+
resolveFrom: [cwd, packageDir],
|
|
53
|
+
});
|
|
54
|
+
}
|