@astryxdesign/cli 0.1.2 → 0.1.3
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 +64 -1
- package/README.md +49 -55
- package/docs/layout.doc.dense.mjs +90 -0
- package/docs/layout.doc.mjs +160 -0
- package/docs/principles.doc.dense.mjs +2 -2
- package/docs/principles.doc.mjs +4 -0
- package/docs/principles.doc.zh.mjs +2 -2
- package/docs/theme.doc.mjs +2 -2
- package/package.json +13 -1
- package/src/api/blog.mjs +192 -0
- package/src/api/blog.test.mjs +144 -0
- package/src/api/component.mjs +253 -12
- package/src/api/discover.mjs +14 -7
- package/src/api/doctor.mjs +10 -25
- package/src/api/index.mjs +1 -0
- package/src/api/layout.mjs +11 -10
- package/src/api/layout.test.mjs +4 -1
- package/src/api/template-integration.test.mjs +225 -0
- package/src/api/template.mjs +242 -84
- package/src/api/validate-integration.mjs +370 -0
- package/src/api/validate-integration.test.mjs +222 -0
- package/src/codemod.mjs +93 -0
- package/src/codemod.test.mjs +134 -0
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/__tests__/runner.test.mjs +103 -0
- package/src/codemods/integration-discovery.mjs +168 -0
- package/src/codemods/integration-discovery.test.mjs +234 -0
- package/src/codemods/integration-runner.mjs +109 -0
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/run-codemod.mjs +207 -0
- package/src/codemods/runner.mjs +73 -96
- package/src/codemods/transforms/v0.0.15/index.mjs +0 -13
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +67 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +61 -0
- package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +104 -0
- package/src/codemods/transforms/{v0.0.15 → v0.1.0}/drop-xds-prefix-imports.mjs +15 -4
- package/src/codemods/transforms/v0.1.0/index.mjs +43 -6
- package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +77 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +78 -0
- package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +360 -0
- package/src/codemods/transforms/v0.1.3/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +260 -0
- package/src/commands/agent-docs.mjs +4 -1
- package/src/commands/blog.mjs +60 -0
- package/src/commands/build-theme.import-path.test.mjs +2 -12
- package/src/commands/build-theme.mjs +117 -0
- package/src/commands/build-theme.prose.test.mjs +5 -15
- package/src/commands/build-theme.watch.test.mjs +149 -0
- package/src/commands/component/index.mjs +44 -10
- package/src/commands/component-ownership.test.mjs +227 -0
- package/src/commands/discover.mjs +5 -16
- package/src/commands/doctor.test.mjs +3 -3
- package/src/commands/ensure-core-built.mjs +120 -0
- package/src/commands/json-contract.test.mjs +0 -32
- package/src/commands/swizzle.mjs +224 -235
- package/src/commands/swizzle.path-safety.test.mjs +3 -3
- package/src/commands/swizzle.routing.test.mjs +279 -0
- package/src/commands/template.mjs +33 -37
- package/src/commands/upgrade.config-ordering.test.mjs +273 -0
- package/src/commands/upgrade.integration-policy.test.mjs +204 -0
- package/src/commands/upgrade.mjs +349 -170
- package/src/commands/validate-integration.mjs +110 -0
- package/src/commands/validate-integration.test.mjs +124 -0
- package/src/config.mjs +7 -20
- package/src/config.test.mjs +89 -13
- package/src/index.mjs +8 -3
- package/src/integration.mjs +19 -0
- package/src/lib/component-discovery.mjs +177 -0
- package/src/lib/config-cache.mjs +91 -0
- package/src/lib/config-cache.test.mjs +83 -0
- package/src/lib/config-schema.mjs +41 -55
- package/src/lib/error-codes.mjs +15 -12
- package/src/lib/integration-warnings.mjs +62 -0
- package/src/lib/integration-warnings.test.mjs +102 -0
- package/src/lib/integrations.mjs +92 -115
- package/src/lib/integrations.test.mjs +88 -107
- package/src/lib/manifest.mjs +5 -2
- package/src/lib/module-loader.mjs +80 -0
- package/src/lib/module-loader.test.mjs +106 -0
- package/src/lib/project.mjs +502 -0
- package/src/lib/project.test.mjs +308 -0
- package/src/lib/site.mjs +20 -0
- package/src/template.mjs +73 -0
- package/src/template.test.mjs +127 -0
- package/src/types/api.d.ts +2 -6
- package/src/types/base.d.ts +3 -9
- package/src/types/codemod.d.ts +81 -0
- package/src/types/component.d.ts +27 -2
- package/src/types/config.d.ts +56 -85
- package/src/types/error-codes.d.ts +5 -3
- package/src/types/index.d.ts +0 -1
- package/src/types/integration.d.ts +29 -0
- package/src/types/swizzle.d.ts +9 -2
- package/src/types/template-api.d.ts +54 -0
- package/src/types/template.d.ts +10 -7
- package/src/types/upgrade.d.ts +29 -0
- package/src/types/validate-integration.d.ts +24 -0
- package/src/utils/github.mjs +0 -237
- package/src/utils/interactive.mjs +2 -2
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.doc.mjs +14 -0
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +28 -0
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.doc.mjs +14 -0
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.tsx +34 -0
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.doc.mjs +14 -0
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.tsx +21 -0
- package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.tsx +15 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.doc.mjs +14 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.tsx +16 -0
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.doc.mjs +19 -0
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.tsx +34 -0
- package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.tsx +30 -0
- package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.doc.mjs +14 -0
- package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.tsx +31 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.tsx +26 -0
- package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.tsx +32 -0
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.doc.mjs +18 -0
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.tsx +32 -0
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.tsx +27 -0
- package/templates/blocks/components/ContextMenu/ContextMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/ContextMenu/ContextMenuBasic.tsx +32 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +46 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +52 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +43 -0
- package/templates/blocks/components/DialogHeader/DialogHeaderBasic.doc.mjs +14 -0
- package/templates/blocks/components/DialogHeader/DialogHeaderBasic.tsx +30 -0
- package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +0 -6
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.tsx +27 -0
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +1 -5
- package/templates/blocks/components/FieldLabel/FieldLabelBasic.doc.mjs +14 -0
- package/templates/blocks/components/FieldLabel/FieldLabelBasic.tsx +20 -0
- package/templates/blocks/components/FieldStatus/FieldStatusBasic.doc.mjs +14 -0
- package/templates/blocks/components/FieldStatus/FieldStatusBasic.tsx +23 -0
- package/templates/blocks/components/FileInput/FileInputBasic.doc.mjs +14 -0
- package/templates/blocks/components/FileInput/FileInputBasic.tsx +22 -0
- package/templates/blocks/components/GridSpan/GridSpanColumns.doc.mjs +14 -0
- package/templates/blocks/components/GridSpan/GridSpanColumns.tsx +38 -0
- package/templates/blocks/components/HStack/HStackBasic.doc.mjs +14 -0
- package/templates/blocks/components/HStack/HStackBasic.tsx +16 -0
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx +57 -0
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
- package/templates/blocks/components/InputGroup/InputGroupBasic.doc.mjs +14 -0
- package/templates/blocks/components/InputGroup/InputGroupBasic.tsx +27 -0
- package/templates/blocks/components/LayoutContent/LayoutContentBasic.doc.mjs +23 -0
- package/templates/blocks/components/LayoutContent/LayoutContentBasic.tsx +40 -0
- package/templates/blocks/components/LayoutFooter/LayoutFooterActions.doc.mjs +22 -0
- package/templates/blocks/components/LayoutFooter/LayoutFooterActions.tsx +41 -0
- package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.doc.mjs +23 -0
- package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.tsx +40 -0
- package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.doc.mjs +22 -0
- package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.tsx +37 -0
- package/templates/blocks/components/Lightbox/LightboxGallery.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxGallery.tsx +53 -0
- package/templates/blocks/components/Lightbox/LightboxShowcase.tsx +4 -3
- package/templates/blocks/components/Lightbox/LightboxVideo.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxVideo.tsx +27 -0
- package/templates/blocks/components/Lightbox/LightboxZoom.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxZoom.tsx +33 -0
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +1 -1
- package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.tsx +19 -0
- package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.doc.mjs +23 -0
- package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.tsx +42 -0
- package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +0 -6
- package/templates/blocks/components/NavIcon/NavIconBasic.doc.mjs +14 -0
- package/templates/blocks/components/NavIcon/NavIconBasic.tsx +16 -0
- package/templates/blocks/components/RadioListItem/RadioListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/RadioListItem/RadioListItemBasic.tsx +30 -0
- package/templates/blocks/components/Resizable/ResizableSidebar.doc.mjs +25 -0
- package/templates/blocks/components/Resizable/ResizableSidebar.tsx +67 -0
- package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.tsx +21 -0
- package/templates/blocks/components/SelectorOption/SelectorOptionBasic.doc.mjs +14 -0
- package/templates/blocks/components/SelectorOption/SelectorOptionBasic.tsx +39 -0
- package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.tsx +55 -0
- package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.tsx +38 -0
- package/templates/blocks/components/SideNavItem/SideNavItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavItem/SideNavItemBasic.tsx +49 -0
- package/templates/blocks/components/SideNavSection/SideNavSectionBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavSection/SideNavSectionBasic.tsx +71 -0
- package/templates/blocks/components/StackItem/StackItemFill.doc.mjs +14 -0
- package/templates/blocks/components/StackItem/StackItemFill.tsx +28 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
- package/templates/blocks/components/TabMenu/TabMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/TabMenu/TabMenuBasic.tsx +23 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +1 -1
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.doc.mjs +14 -0
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.tsx +47 -0
- package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.tsx +22 -0
- package/templates/blocks/components/TopNavItem/TopNavItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavItem/TopNavItemBasic.tsx +21 -0
- package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.doc.mjs +20 -0
- package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.tsx +46 -0
- package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.tsx +16 -0
- package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.tsx +26 -0
- package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.tsx +43 -0
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.tsx +43 -0
- package/templates/blocks/components/VStack/VStackBasic.doc.mjs +14 -0
- package/templates/blocks/components/VStack/VStackBasic.tsx +20 -0
- package/templates/pages/kanban-board/page.tsx +729 -0
- package/templates/pages/kanban-board/template.doc.mjs +12 -0
- package/templates/pages/shell-nav/page.tsx +321 -0
- package/templates/pages/shell-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-side-nav/page.tsx +241 -0
- package/templates/pages/shell-side-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-top-nav/page.tsx +224 -0
- package/templates/pages/shell-top-nav/template.doc.mjs +12 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +0 -116
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +0 -230
- package/src/commands/gap-report.mjs +0 -464
- package/src/commands/gap-report.test.mjs +0 -168
- package/src/commands/swizzle-gap-safety.test.mjs +0 -273
- package/src/lib/config.mjs +0 -113
- package/src/lib/config.test.mjs +0 -91
- package/src/types/gap-report.d.ts +0 -29
- package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.doc.mjs +0 -14
- package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.tsx +0 -64
- package/templates/blocks/components/TreeListItem/TreeListItemShowcase.doc.mjs +0 -14
- package/templates/blocks/components/TreeListItem/TreeListItemShowcase.tsx +0 -60
- /package/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
package/src/api/component.mjs
CHANGED
|
@@ -11,18 +11,48 @@ import * as fs from 'node:fs';
|
|
|
11
11
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
12
12
|
import {findCoreDir, discoverExternalPackages} from '../utils/paths.mjs';
|
|
13
13
|
import {
|
|
14
|
+
CORE_PACKAGE,
|
|
14
15
|
discoverComponents,
|
|
15
16
|
discoverExternalComponentsGrouped,
|
|
17
|
+
discoverIntegrationComponents,
|
|
16
18
|
findComponentReadme,
|
|
17
19
|
findComponentSource,
|
|
18
20
|
findExternalComponentDoc,
|
|
21
|
+
findIntegrationComponentDoc,
|
|
22
|
+
findIntegrationComponentSource,
|
|
19
23
|
resolveImportPath,
|
|
20
24
|
} from '../lib/component-discovery.mjs';
|
|
25
|
+
import {Project} from '../lib/project.mjs';
|
|
21
26
|
import {loadDocs} from '../lib/component-loader.mjs';
|
|
22
27
|
import {searchComponents} from '../lib/string-utils.mjs';
|
|
23
28
|
import {AstryxError} from './error.mjs';
|
|
24
29
|
import {findShowcase, findRelatedBlocks} from './template.mjs';
|
|
25
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Load the configured integrations for `cwd`, swallowing any config errors so
|
|
33
|
+
* component discovery never hard-fails on a malformed/absent integration. An
|
|
34
|
+
* empty list means "core only".
|
|
35
|
+
* @param {string} cwd
|
|
36
|
+
* @returns {Promise<Array<{name: string, components?: string, issuesUrl?: string}>>}
|
|
37
|
+
*/
|
|
38
|
+
async function loadIntegrationsSafely(cwd) {
|
|
39
|
+
try {
|
|
40
|
+
const project = await Project.load(cwd);
|
|
41
|
+
return project.loadedIntegrations;
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a loaded integration by package name.
|
|
49
|
+
* @param {Array<{name: string}>} loadedIntegrations
|
|
50
|
+
* @param {string} packageName
|
|
51
|
+
*/
|
|
52
|
+
function findLoadedIntegration(loadedIntegrations, packageName) {
|
|
53
|
+
return loadedIntegrations.find(i => i.name === packageName) ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
26
56
|
/**
|
|
27
57
|
* Resolve an external package by name from the discovered externals list.
|
|
28
58
|
* @param {string} packageName - e.g. '@acme/xds-widgets'
|
|
@@ -131,8 +161,13 @@ export async function component(name, options = {}) {
|
|
|
131
161
|
return {type: 'component.full', data: {[match[0]]: entries}};
|
|
132
162
|
}
|
|
133
163
|
|
|
134
|
-
// Default: brief —
|
|
135
|
-
|
|
164
|
+
// Default: brief — package-qualified object list for the category.
|
|
165
|
+
// Pre-1.0 JSON contract: members are {name, package} objects, not bare
|
|
166
|
+
// strings, so consumers can disambiguate ownership.
|
|
167
|
+
return {
|
|
168
|
+
type: 'component.list',
|
|
169
|
+
data: {[match[0]]: match[1].map(n => ({name: n, package: CORE_PACKAGE}))},
|
|
170
|
+
};
|
|
136
171
|
}
|
|
137
172
|
|
|
138
173
|
// All components — merge core + external packages with grouped subcategories
|
|
@@ -179,43 +214,189 @@ export async function component(name, options = {}) {
|
|
|
179
214
|
return {type: 'component.full', data: result};
|
|
180
215
|
}
|
|
181
216
|
|
|
182
|
-
// Default: brief —
|
|
217
|
+
// Default: brief — package-qualified object list (core + integrations).
|
|
218
|
+
// Pre-1.0 JSON contract: each group's members are {name, package} objects.
|
|
219
|
+
/** @type {Record<string, Array<{name: string, package: string}>>} */
|
|
220
|
+
const listData = {};
|
|
221
|
+
for (const [cat, comps] of Object.entries(components)) {
|
|
222
|
+
listData[cat] = comps.map(n => ({name: n, package: CORE_PACKAGE}));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Integration components (authoritative source: loadedIntegrations).
|
|
226
|
+
const loadedIntegrations = await loadIntegrationsSafely(cwd);
|
|
227
|
+
const seenIntegration = new Set();
|
|
228
|
+
for (const integration of loadedIntegrations) {
|
|
229
|
+
seenIntegration.add(integration.name);
|
|
230
|
+
const owned = discoverIntegrationComponents(integration);
|
|
231
|
+
// Group integration components by their doc `group`, falling back to the
|
|
232
|
+
// package name. Keys are package-qualified so they never collide with
|
|
233
|
+
// core groups or each other.
|
|
234
|
+
/** @type {Map<string, Array<{name: string, package: string}>>} */
|
|
235
|
+
const byGroup = new Map();
|
|
236
|
+
for (const rec of owned) {
|
|
237
|
+
const groupLabel = rec.group ?? integration.name;
|
|
238
|
+
const key = `${groupLabel} (${integration.name})`;
|
|
239
|
+
if (!byGroup.has(key)) byGroup.set(key, []);
|
|
240
|
+
byGroup.get(key).push({name: rec.name, package: integration.name});
|
|
241
|
+
}
|
|
242
|
+
for (const [key, members] of byGroup) {
|
|
243
|
+
members.sort((a, b) => a.name.localeCompare(b.name));
|
|
244
|
+
listData[key] = members;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Back-compat: node_modules-scanned external packages (pkg.astryx.docs)
|
|
249
|
+
// that are NOT configured integrations. Preserves existing discovery for
|
|
250
|
+
// consumers that haven't adopted the config-integration flow.
|
|
183
251
|
const externals = discoverExternalPackages(cwd);
|
|
184
252
|
for (const ext of externals) {
|
|
253
|
+
if (seenIntegration.has(ext.name)) continue;
|
|
185
254
|
const grouped = discoverExternalComponentsGrouped(ext.docsDir);
|
|
186
255
|
const groupKeys = Object.keys(grouped);
|
|
187
256
|
if (groupKeys.length === 0) continue;
|
|
188
257
|
|
|
189
|
-
// If the package has subcategories (groups), emit each as a separate key.
|
|
190
|
-
// If no groups exist, fall back to the flat list under one key.
|
|
191
258
|
const hasGroups = groupKeys.some(
|
|
192
259
|
k => grouped[k].length > 1 || grouped[k][0] !== k,
|
|
193
260
|
);
|
|
194
261
|
|
|
195
262
|
if (hasGroups) {
|
|
196
263
|
for (const [group, members] of Object.entries(grouped)) {
|
|
197
|
-
|
|
264
|
+
listData[`${group} (${ext.name})`] = members.map(n => ({
|
|
265
|
+
name: n,
|
|
266
|
+
package: ext.name,
|
|
267
|
+
}));
|
|
198
268
|
}
|
|
199
269
|
} else {
|
|
200
|
-
// All ungrouped — single flat list under the package category
|
|
201
270
|
const allComps = Object.values(grouped).flat().sort();
|
|
202
271
|
if (allComps.length > 0) {
|
|
203
|
-
|
|
272
|
+
listData[`${ext.category} (${ext.name})`] = allComps.map(n => ({
|
|
273
|
+
name: n,
|
|
274
|
+
package: ext.name,
|
|
275
|
+
}));
|
|
204
276
|
}
|
|
205
277
|
}
|
|
206
278
|
}
|
|
207
|
-
return {type: 'component.list', data:
|
|
279
|
+
return {type: 'component.list', data: listData};
|
|
208
280
|
}
|
|
209
281
|
|
|
210
282
|
// ── Single component ───────────────────────────────────────────
|
|
211
283
|
|
|
284
|
+
if (typeof name !== 'string') {
|
|
285
|
+
throw new AstryxError(
|
|
286
|
+
`No component named "${String(name)}"`,
|
|
287
|
+
undefined,
|
|
288
|
+
ERROR_CODES.ERR_UNKNOWN_COMPONENT,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
212
292
|
const dirName = name.replace(/^XDS/, '');
|
|
213
293
|
|
|
294
|
+
// Ownership-aware resolution. Build the set of OWNER packages that provide a
|
|
295
|
+
// component with this name across core + every loaded integration. This is
|
|
296
|
+
// what lets the CLI disambiguate by package and expose the owner's source +
|
|
297
|
+
// issuesUrl (the inputs the future integration-component swizzle needs).
|
|
298
|
+
const loadedIntegrations = await loadIntegrationsSafely(cwd);
|
|
299
|
+
const coreDocPath = findComponentReadme(coreDir, dirName);
|
|
300
|
+
/**
|
|
301
|
+
* @type {Array<{
|
|
302
|
+
* package: string,
|
|
303
|
+
* docPath: string,
|
|
304
|
+
* sourcePath: string|null,
|
|
305
|
+
* issuesUrl: string|undefined,
|
|
306
|
+
* integration: object|null,
|
|
307
|
+
* }>}
|
|
308
|
+
*/
|
|
309
|
+
const owners = [];
|
|
310
|
+
if (coreDocPath) {
|
|
311
|
+
owners.push({
|
|
312
|
+
package: CORE_PACKAGE,
|
|
313
|
+
docPath: coreDocPath,
|
|
314
|
+
sourcePath: findComponentSource(coreDir, dirName),
|
|
315
|
+
issuesUrl: undefined,
|
|
316
|
+
integration: null,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
for (const integration of loadedIntegrations) {
|
|
320
|
+
const docPath = findIntegrationComponentDoc(integration, dirName);
|
|
321
|
+
if (!docPath) continue;
|
|
322
|
+
owners.push({
|
|
323
|
+
package: integration.name,
|
|
324
|
+
docPath,
|
|
325
|
+
sourcePath: findIntegrationComponentSource(integration, dirName),
|
|
326
|
+
issuesUrl: integration.issuesUrl,
|
|
327
|
+
integration,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Augment a loaded `component.detail` doc with ownership metadata. Adds
|
|
333
|
+
* `package`, the resolved `import` specifier, and `sourceAvailable` (whether
|
|
334
|
+
* a swizzleable source file exists for the owner). Existing doc fields
|
|
335
|
+
* (name, usage, props, …) are preserved.
|
|
336
|
+
* @param {object} docs
|
|
337
|
+
* @param {{package: string, sourcePath: string|null}} owner
|
|
338
|
+
* @param {string} componentName
|
|
339
|
+
*/
|
|
340
|
+
function withOwnership(docs, owner, componentName) {
|
|
341
|
+
const importSpec =
|
|
342
|
+
owner.package === CORE_PACKAGE
|
|
343
|
+
? resolveImportPath(coreDir, componentName)
|
|
344
|
+
: `${owner.package}/${componentName}`;
|
|
345
|
+
return {
|
|
346
|
+
...docs,
|
|
347
|
+
package: owner.package,
|
|
348
|
+
import: importSpec,
|
|
349
|
+
sourceAvailable: owner.sourcePath != null,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
214
353
|
// When scoped to a specific package, search that package first.
|
|
215
354
|
// This is critical for components that exist in both core and an external
|
|
216
355
|
// package (e.g. AppShell, Button, SideNav) — the package scope ensures
|
|
217
356
|
// the external package's docs are returned, not core's.
|
|
218
357
|
if (packageScope) {
|
|
358
|
+
// Core scope: resolve from core directly.
|
|
359
|
+
if (packageScope === CORE_PACKAGE) {
|
|
360
|
+
const owner = owners.find(o => o.package === CORE_PACKAGE);
|
|
361
|
+
if (!owner) {
|
|
362
|
+
throw new AstryxError(`No component "${name}" in package "${packageScope}"`, undefined, ERROR_CODES.ERR_UNKNOWN_COMPONENT);
|
|
363
|
+
}
|
|
364
|
+
if (source) {
|
|
365
|
+
if (!owner.sourcePath) {
|
|
366
|
+
throw new AstryxError(`Source for "${name}" not found`, undefined, ERROR_CODES.ERR_NO_SOURCE);
|
|
367
|
+
}
|
|
368
|
+
return {type: 'component.detail.source', data: {component: dirName, source: fs.readFileSync(owner.sourcePath, 'utf-8')}};
|
|
369
|
+
}
|
|
370
|
+
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
371
|
+
if (props) {
|
|
372
|
+
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
373
|
+
return {type: 'component.detail.props', data: p};
|
|
374
|
+
}
|
|
375
|
+
return {type: 'component.detail', data: withOwnership(docs, owner, dirName)};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Integration scope (authoritative): resolve from the loaded integration.
|
|
379
|
+
const integration = findLoadedIntegration(loadedIntegrations, packageScope);
|
|
380
|
+
if (integration) {
|
|
381
|
+
const owner = owners.find(o => o.package === packageScope);
|
|
382
|
+
if (!owner) {
|
|
383
|
+
throw new AstryxError(`No component "${name}" in package "${packageScope}"`, undefined, ERROR_CODES.ERR_UNKNOWN_COMPONENT);
|
|
384
|
+
}
|
|
385
|
+
if (source) {
|
|
386
|
+
if (!owner.sourcePath) {
|
|
387
|
+
throw new AstryxError(`Source for "${name}" not found in package "${packageScope}"`, undefined, ERROR_CODES.ERR_NO_SOURCE);
|
|
388
|
+
}
|
|
389
|
+
return {type: 'component.detail.source', data: {component: dirName, source: fs.readFileSync(owner.sourcePath, 'utf-8')}};
|
|
390
|
+
}
|
|
391
|
+
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
392
|
+
if (props) {
|
|
393
|
+
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
394
|
+
return {type: 'component.detail.props', data: p};
|
|
395
|
+
}
|
|
396
|
+
return {type: 'component.detail', data: withOwnership(docs, owner, dirName)};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Legacy fallback: node_modules `pkg.astryx.docs` external package.
|
|
219
400
|
const ext = resolveExternalPackage(packageScope, cwd);
|
|
220
401
|
if (!ext) {
|
|
221
402
|
throw new AstryxError(`External package "${packageScope}" not found`, undefined, ERROR_CODES.ERR_UNKNOWN_PACKAGE);
|
|
@@ -244,11 +425,49 @@ export async function component(name, options = {}) {
|
|
|
244
425
|
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
245
426
|
return {type: 'component.detail.props', data: p};
|
|
246
427
|
}
|
|
247
|
-
return {
|
|
428
|
+
return {
|
|
429
|
+
type: 'component.detail',
|
|
430
|
+
data: withOwnership(docs, {package: ext.name, sourcePath: null}, dirName),
|
|
431
|
+
};
|
|
248
432
|
}
|
|
249
433
|
throw new AstryxError(`No component "${name}" in package "${packageScope}"`, undefined, ERROR_CODES.ERR_UNKNOWN_COMPONENT);
|
|
250
434
|
}
|
|
251
435
|
|
|
436
|
+
// Ambiguity: when the name is owned by MORE THAN ONE package (core and/or
|
|
437
|
+
// integrations) and the caller did not scope with --package, refuse to guess.
|
|
438
|
+
// NOTE: legacy `pkg.astryx.docs` externals are intentionally NOT part of this
|
|
439
|
+
// ambiguity set — they retain their historical core-first fallback below so
|
|
440
|
+
// existing consumers (and tests) keep working. Only config-driven integration
|
|
441
|
+
// ownership participates here.
|
|
442
|
+
if (owners.length > 1) {
|
|
443
|
+
throw new AstryxError(
|
|
444
|
+
`Component "${dirName}" is provided by multiple packages. Re-run with --package <pkg> to choose one.`,
|
|
445
|
+
owners.map(o => ({name: o.package, reason: 'provides this component'})),
|
|
446
|
+
ERROR_CODES.ERR_UNKNOWN_COMPONENT,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Single non-core owner (an integration provides it, core does not) — resolve
|
|
451
|
+
// from that integration so the integration component is authoritative.
|
|
452
|
+
if (owners.length === 1 && owners[0].package !== CORE_PACKAGE) {
|
|
453
|
+
const owner = owners[0];
|
|
454
|
+
if (source) {
|
|
455
|
+
if (!owner.sourcePath) {
|
|
456
|
+
throw new AstryxError(`Source for "${name}" not found`, undefined, ERROR_CODES.ERR_NO_SOURCE);
|
|
457
|
+
}
|
|
458
|
+
return {type: 'component.detail.source', data: {component: dirName, source: fs.readFileSync(owner.sourcePath, 'utf-8')}};
|
|
459
|
+
}
|
|
460
|
+
if (showcase) {
|
|
461
|
+
throw new AstryxError(`No showcase found for "${name}"`, undefined, ERROR_CODES.ERR_NO_SHOWCASE);
|
|
462
|
+
}
|
|
463
|
+
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
464
|
+
if (props) {
|
|
465
|
+
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
466
|
+
return {type: 'component.detail.props', data: p};
|
|
467
|
+
}
|
|
468
|
+
return {type: 'component.detail', data: withOwnership(docs, owner, dirName)};
|
|
469
|
+
}
|
|
470
|
+
|
|
252
471
|
if (source) {
|
|
253
472
|
const sourcePath = findComponentSource(coreDir, dirName);
|
|
254
473
|
if (!sourcePath) {
|
|
@@ -274,6 +493,10 @@ export async function component(name, options = {}) {
|
|
|
274
493
|
|
|
275
494
|
let readmePath = findComponentReadme(coreDir, dirName);
|
|
276
495
|
let resolvedName = dirName;
|
|
496
|
+
// Track the resolving owner so the detail payload can carry ownership info.
|
|
497
|
+
// Defaults to core; the legacy-external fallback below may reassign it.
|
|
498
|
+
let resolvedOwnerPackage = CORE_PACKAGE;
|
|
499
|
+
let resolvedSourcePath = readmePath ? findComponentSource(coreDir, dirName) : null;
|
|
277
500
|
|
|
278
501
|
if (!readmePath) {
|
|
279
502
|
const externals = discoverExternalPackages(cwd);
|
|
@@ -281,6 +504,8 @@ export async function component(name, options = {}) {
|
|
|
281
504
|
const extDocPath = findExternalComponentDoc(ext.docsDir, dirName);
|
|
282
505
|
if (extDocPath) {
|
|
283
506
|
readmePath = extDocPath;
|
|
507
|
+
resolvedOwnerPackage = ext.name;
|
|
508
|
+
resolvedSourcePath = null;
|
|
284
509
|
break;
|
|
285
510
|
}
|
|
286
511
|
}
|
|
@@ -299,6 +524,8 @@ export async function component(name, options = {}) {
|
|
|
299
524
|
if (topScore >= 90 && topTied.length === 1 && gap >= 20) {
|
|
300
525
|
resolvedName = topTied[0].name;
|
|
301
526
|
readmePath = findComponentReadme(coreDir, resolvedName);
|
|
527
|
+
resolvedOwnerPackage = CORE_PACKAGE;
|
|
528
|
+
resolvedSourcePath = findComponentSource(coreDir, resolvedName);
|
|
302
529
|
} else {
|
|
303
530
|
const threshold = Math.max(topScore - 20, 1);
|
|
304
531
|
const candidates = results.filter(r => r.score >= threshold).slice(0, 5);
|
|
@@ -382,7 +609,14 @@ export async function component(name, options = {}) {
|
|
|
382
609
|
if (props) {
|
|
383
610
|
return {type: 'component.detail.props', data: matchingComponent.props || []};
|
|
384
611
|
}
|
|
385
|
-
return {
|
|
612
|
+
return {
|
|
613
|
+
type: 'component.detail',
|
|
614
|
+
data: withOwnership(
|
|
615
|
+
scoped,
|
|
616
|
+
{package: resolvedOwnerPackage, sourcePath: resolvedSourcePath},
|
|
617
|
+
dirName,
|
|
618
|
+
),
|
|
619
|
+
};
|
|
386
620
|
}
|
|
387
621
|
|
|
388
622
|
if (props) {
|
|
@@ -390,5 +624,12 @@ export async function component(name, options = {}) {
|
|
|
390
624
|
return {type: 'component.detail.props', data: p};
|
|
391
625
|
}
|
|
392
626
|
|
|
393
|
-
return {
|
|
627
|
+
return {
|
|
628
|
+
type: 'component.detail',
|
|
629
|
+
data: withOwnership(
|
|
630
|
+
docs,
|
|
631
|
+
{package: resolvedOwnerPackage, sourcePath: resolvedSourcePath},
|
|
632
|
+
resolvedName,
|
|
633
|
+
),
|
|
634
|
+
};
|
|
394
635
|
}
|
package/src/api/discover.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file Programmatic API for the discover command.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {Project} from '../lib/project.mjs';
|
|
8
8
|
import {
|
|
9
9
|
scanAllPackages,
|
|
10
10
|
findComponentInPackages,
|
|
@@ -40,7 +40,7 @@ function validateDocs(docs) {
|
|
|
40
40
|
*/
|
|
41
41
|
export async function discover(query, options = {}) {
|
|
42
42
|
const {lang = null, zh = false} = options;
|
|
43
|
-
const
|
|
43
|
+
const project = await Project.load();
|
|
44
44
|
const toEntry = pkg => ({
|
|
45
45
|
name: pkg.name,
|
|
46
46
|
category: pkg.category,
|
|
@@ -50,14 +50,21 @@ export async function discover(query, options = {}) {
|
|
|
50
50
|
displayName: pkg.displayName,
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
// External packages come from configured integrations that declare a
|
|
54
|
+
// components root. Each becomes a scannable package keyed by its docsDir.
|
|
55
|
+
const explicitPackages = project.loadedIntegrations
|
|
56
|
+
.filter(integration => integration.components)
|
|
57
|
+
.map(integration => ({
|
|
58
|
+
name: integration.name,
|
|
59
|
+
version: integration.version,
|
|
60
|
+
category: integration.name,
|
|
61
|
+
docsDir: integration.components,
|
|
62
|
+
}));
|
|
63
|
+
if (explicitPackages.length === 0) {
|
|
57
64
|
return {type: 'discover.list', data: [], meta: {configured: false}};
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
const packages = scanAllPackages(
|
|
67
|
+
const packages = scanAllPackages([], explicitPackages);
|
|
61
68
|
|
|
62
69
|
if (packages.length === 0) {
|
|
63
70
|
return {type: 'discover.list', data: [], meta: {configured: true}};
|
package/src/api/doctor.mjs
CHANGED
|
@@ -27,7 +27,7 @@ import {createRequire} from 'node:module';
|
|
|
27
27
|
import {MIN_NODE_VERSION, isNodeVersionSupported} from '../lib/node-version.mjs';
|
|
28
28
|
import {CLI_ROOT, findCoreDir} from '../utils/paths.mjs';
|
|
29
29
|
import {detectPackageManager} from '../utils/package-manager.mjs';
|
|
30
|
-
import {findConfigPath,
|
|
30
|
+
import {findConfigPath, Project} from '../lib/project.mjs';
|
|
31
31
|
import {semverCompare} from '../utils/semver.mjs';
|
|
32
32
|
|
|
33
33
|
const _require = createRequire(import.meta.url);
|
|
@@ -240,7 +240,7 @@ export function checkThemes(ctx) {
|
|
|
240
240
|
label: 'Theme packages',
|
|
241
241
|
status: 'warn',
|
|
242
242
|
message: 'No @astryxdesign/theme-* packages are installed.',
|
|
243
|
-
fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set
|
|
243
|
+
fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set astryx.theme.',
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -279,8 +279,9 @@ export async function checkConfig(ctx) {
|
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
//
|
|
283
|
-
//
|
|
282
|
+
// Project.load swallows nothing — it surfaces a genuine load failure — but
|
|
283
|
+
// the config check wants to report a bad default export precisely, so we
|
|
284
|
+
// re-import directly to surface a genuine load failure as a FAIL.
|
|
284
285
|
try {
|
|
285
286
|
const {pathToFileURL} = await import('node:url');
|
|
286
287
|
const mod = await import(pathToFileURL(ctx.configPath).href);
|
|
@@ -291,25 +292,9 @@ export async function checkConfig(ctx) {
|
|
|
291
292
|
label: 'astryx.config.mjs',
|
|
292
293
|
status: 'fail',
|
|
293
294
|
message: `astryx.config.mjs default export is not an object (got ${typeof config}).`,
|
|
294
|
-
fix: 'Export a default object from astryx.config.mjs, e.g. `export default {
|
|
295
|
+
fix: 'Export a default object from astryx.config.mjs, e.g. `export default { integrations: [] };`.',
|
|
295
296
|
};
|
|
296
297
|
}
|
|
297
|
-
// Validate that `packages`, if present, is a string or array of strings.
|
|
298
|
-
if (config?.packages !== undefined) {
|
|
299
|
-
const arr = Array.isArray(config.packages)
|
|
300
|
-
? config.packages
|
|
301
|
-
: [config.packages];
|
|
302
|
-
const bad = arr.some(p => typeof p !== 'string');
|
|
303
|
-
if (bad) {
|
|
304
|
-
return {
|
|
305
|
-
id: 'config',
|
|
306
|
-
label: 'astryx.config.mjs',
|
|
307
|
-
status: 'fail',
|
|
308
|
-
message: 'astryx.config.mjs `packages` must be a string or array of strings.',
|
|
309
|
-
fix: 'Set `packages` to a path string or array of path strings.',
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
298
|
return {
|
|
314
299
|
id: 'config',
|
|
315
300
|
label: 'astryx.config.mjs',
|
|
@@ -369,7 +354,7 @@ export function checkAgentDocs(ctx) {
|
|
|
369
354
|
label: 'AI agent docs',
|
|
370
355
|
status: 'warn',
|
|
371
356
|
message: `Agent docs present (${present.join(', ')}) but no Astryx section markers found.`,
|
|
372
|
-
fix: 'Add the
|
|
357
|
+
fix: 'Add the Astryx section to your agent docs with `astryx init --features agents`.',
|
|
373
358
|
};
|
|
374
359
|
}
|
|
375
360
|
|
|
@@ -377,7 +362,7 @@ export function checkAgentDocs(ctx) {
|
|
|
377
362
|
id: 'agent-docs',
|
|
378
363
|
label: 'AI agent docs',
|
|
379
364
|
status: 'pass',
|
|
380
|
-
message: `
|
|
365
|
+
message: `Astryx agent docs section present in ${withMarkers.join(', ')}.`,
|
|
381
366
|
};
|
|
382
367
|
}
|
|
383
368
|
|
|
@@ -493,8 +478,8 @@ export async function runChecks(options = {}) {
|
|
|
493
478
|
// Resolve a possible theme key from config (best-effort; never throws).
|
|
494
479
|
let configTheme = null;
|
|
495
480
|
try {
|
|
496
|
-
const
|
|
497
|
-
configTheme =
|
|
481
|
+
const project = await Project.load(cwd);
|
|
482
|
+
configTheme = project.config?.theme ?? null;
|
|
498
483
|
} catch {
|
|
499
484
|
// Best-effort: a missing/invalid config leaves configTheme null.
|
|
500
485
|
}
|
package/src/api/index.mjs
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
export {component} from './component.mjs';
|
|
23
23
|
export {docs} from './docs.mjs';
|
|
24
|
+
export {blog} from './blog.mjs';
|
|
24
25
|
export {discover} from './discover.mjs';
|
|
25
26
|
export {template} from './template.mjs';
|
|
26
27
|
export {themeAdd, listThemes} from './theme-add.mjs';
|
package/src/api/layout.mjs
CHANGED
|
@@ -25,13 +25,14 @@ import {expand} from '../lib/xle/expand.mjs';
|
|
|
25
25
|
import {toCompact, toOutline} from '../lib/xle/print.mjs';
|
|
26
26
|
import {buildRegistry, ALIAS_TABLE} from '../lib/xle/registry.mjs';
|
|
27
27
|
import {discoverTemplates, stripTemplateAssetRefs} from './template.mjs';
|
|
28
|
-
import {
|
|
28
|
+
import {Project} from '../lib/project.mjs';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* The catalog a `{hint}` can resolve to: template blocks (spliced inline) plus
|
|
32
|
-
* any app-registered local components from astryx.config.mjs
|
|
33
|
-
* (imported by name). App components are how XLE
|
|
34
|
-
* KpiCard/chart/drawer set that the
|
|
32
|
+
* any app-registered local components from astryx.config.mjs
|
|
33
|
+
* `experimental.xle.components` (imported by name). App components are how XLE
|
|
34
|
+
* reaches domain pieces — the KpiCard/chart/drawer set that the
|
|
35
|
+
* @astryxdesign/core registry can't see.
|
|
35
36
|
*/
|
|
36
37
|
async function loadBlocks(cwd) {
|
|
37
38
|
const blocks = [];
|
|
@@ -42,20 +43,20 @@ async function loadBlocks(cwd) {
|
|
|
42
43
|
// discovery is best-effort
|
|
43
44
|
}
|
|
44
45
|
try {
|
|
45
|
-
const
|
|
46
|
-
const components = config.
|
|
46
|
+
const project = await Project.load(cwd);
|
|
47
|
+
const components = project.config.experimental?.xle?.components ?? {};
|
|
47
48
|
for (const [name, spec] of Object.entries(components)) {
|
|
48
|
-
const importPath =
|
|
49
|
+
const importPath = spec.from;
|
|
49
50
|
if (!importPath) continue;
|
|
50
51
|
blocks.push({
|
|
51
52
|
type: 'block',
|
|
52
53
|
kind: 'component',
|
|
53
54
|
dirName: name,
|
|
54
55
|
name,
|
|
55
|
-
description:
|
|
56
|
+
description: spec.description ?? '',
|
|
56
57
|
category: 'app',
|
|
57
58
|
importPath,
|
|
58
|
-
isDefault:
|
|
59
|
+
isDefault: Boolean(spec.default),
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
62
|
} catch {
|
|
@@ -281,7 +282,7 @@ TEMPLATE REFERENCING ({hint} pulls in real content — this is how XLE reaches
|
|
|
281
282
|
{kpi-card} standalone reference (no wrapper element) — place a component directly
|
|
282
283
|
{kpi-card}*4 repeat a reference; the definition/import is emitted once
|
|
283
284
|
app components register local ones in astryx.config.mjs to import them by name:
|
|
284
|
-
export default {
|
|
285
|
+
export default {experimental: {xle: {components: {KpiCard: {from: '@/components/KpiCard'}}}}}
|
|
285
286
|
then {kpi-card} → import {KpiCard} + <KpiCard /> (kebab ↔ Pascal)
|
|
286
287
|
|
|
287
288
|
STRUCTURE THE EXPANDER HANDLES
|
package/src/api/layout.test.mjs
CHANGED
|
@@ -196,11 +196,14 @@ describe('template referencing', () => {
|
|
|
196
196
|
|
|
197
197
|
it('imports app-registered local components (the local-component bridge)', async () => {
|
|
198
198
|
// Inside the workspace so @astryxdesign/core resolves; cleaned up after.
|
|
199
|
+
// A package.json beside the config makes Project.load resolve it as the
|
|
200
|
+
// sibling-of-nearest-package.json (the standard config resolution).
|
|
199
201
|
const cwd = mkdtempSync(join(process.cwd(), '.xle-imp-test-'));
|
|
200
202
|
try {
|
|
203
|
+
writeFileSync(join(cwd, 'package.json'), '{"name": "xle-imp-fixture"}\n');
|
|
201
204
|
writeFileSync(
|
|
202
205
|
join(cwd, 'astryx.config.mjs'),
|
|
203
|
-
`export default {
|
|
206
|
+
`export default {experimental: {xle: {components: {KpiCard: {from: '@/components/KpiCard'}, TimeRangePicker: {from: '@/components/TimeRangePicker'}}}}};\n`,
|
|
204
207
|
);
|
|
205
208
|
const result = await layoutExpand('S[p6] > (G[c4 g4] > {kpi-card}*4) + {time-range-picker}', {
|
|
206
209
|
name: 'Demo',
|