@astryxdesign/cli 0.1.0 → 0.1.1-canary.080d887
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 +68 -0
- package/README.md +117 -75
- package/bin/astryx.mjs +22 -7
- package/docs/getting-started.doc.mjs +11 -11
- package/docs/icons.doc.mjs +1 -1
- package/docs/migration.doc.mjs +2 -2
- package/docs/shape.doc.mjs +1 -1
- package/docs/styling.doc.mjs +3 -4
- package/docs/theme.doc.dense.mjs +2 -2
- package/docs/theme.doc.mjs +14 -0
- package/docs/theme.doc.zh.mjs +2 -2
- package/docs/working-with-ai.doc.mjs +4 -4
- package/package.json +20 -9
- package/src/api/discover.mjs +78 -26
- package/src/api/doctor.mjs +3 -3
- package/src/api/layout.mjs +301 -0
- package/src/api/layout.test.mjs +238 -0
- package/src/api/search.mjs +207 -13
- package/src/api/template.mjs +193 -51
- package/src/api/template.test.mjs +2 -0
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/runner.mjs +105 -51
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +116 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +51 -0
- package/src/codemods/transforms/v0.1.0/index.mjs +28 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +230 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +84 -0
- package/src/commands/agent-docs.mjs +119 -66
- package/src/commands/agent-docs.path-safety.test.mjs +1 -1
- package/src/commands/agent-docs.test.mjs +87 -31
- package/src/commands/build-theme.import-path.test.mjs +1 -1
- package/src/commands/build-theme.path-safety.test.mjs +1 -1
- package/src/commands/build-theme.prose.test.mjs +1 -1
- package/src/commands/build.mjs +196 -0
- package/src/commands/component-package.test.mjs +1 -1
- package/src/commands/component.test.mjs +1 -1
- package/src/commands/docs.test.mjs +1 -1
- package/src/commands/doctor.test.mjs +1 -1
- package/src/commands/external-showcase.test.mjs +1 -1
- package/src/commands/gap-report.mjs +17 -9
- package/src/commands/gap-report.test.mjs +21 -16
- package/src/commands/init.mjs +43 -9
- package/src/commands/init.next-steps.test.mjs +46 -0
- package/src/commands/interactive-guard.test.mjs +1 -1
- package/src/commands/json-contract.test.mjs +10 -3
- package/src/commands/layout.mjs +139 -0
- package/src/commands/swizzle-gap-safety.test.mjs +1 -1
- package/src/commands/swizzle.mjs +51 -23
- package/src/commands/swizzle.path-safety.test.mjs +1 -1
- package/src/commands/template.path-safety.test.mjs +1 -1
- package/src/commands/template.test.mjs +1 -1
- package/src/commands/upgrade.mjs +292 -177
- package/src/commands/upgrade.test.mjs +41 -27
- package/src/config.mjs +31 -0
- package/src/config.test.mjs +24 -0
- package/src/index.mjs +5 -0
- package/src/lib/config-schema.mjs +119 -0
- package/src/lib/config.mjs +45 -6
- package/src/lib/config.test.mjs +91 -0
- package/src/lib/error-codes.mjs +11 -0
- package/src/lib/integrations.mjs +155 -0
- package/src/lib/integrations.test.mjs +154 -0
- package/src/lib/levenshtein.mjs +29 -0
- package/src/lib/manifest.mjs +6 -0
- package/src/lib/package-scanner.mjs +31 -7
- package/src/lib/string-utils.mjs +5 -14
- package/src/lib/xle/browser.d.ts +91 -0
- package/src/lib/xle/browser.mjs +120 -0
- package/src/lib/xle/expand.mjs +622 -0
- package/src/lib/xle/parse.mjs +581 -0
- package/src/lib/xle/print.mjs +174 -0
- package/src/lib/xle/registry-core.mjs +170 -0
- package/src/lib/xle/registry.mjs +237 -0
- package/src/lib/xle/splice.mjs +137 -0
- package/src/lib/xle/validate.mjs +356 -0
- package/src/lib/xle/xle.test.mjs +333 -0
- package/src/types/config.d.ts +99 -0
- package/src/types/error-codes.d.ts +1 -0
- package/src/utils/github.mjs +12 -27
- package/src/utils/interactive.mjs +1 -1
- package/src/utils/interactive.test.mjs +2 -0
- package/src/utils/package-manager.mjs +1 -1
- package/src/utils/package-manager.test.mjs +1 -1
- package/src/utils/path-safety.test.mjs +1 -1
- package/src/utils/paths.test.mjs +8 -8
- package/src/utils/update-check.mjs +4 -26
- package/src/utils/update-check.test.mjs +2 -64
- package/templates/blocks/components/AppShell/AppShellContentOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellShowcase.tsx +1 -10
- package/templates/blocks/components/AppShell/AppShellSideNavOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellTopNavOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellTopNavWithSideNav.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellWithBanner.tsx +1 -9
- package/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +12 -19
- package/templates/blocks/components/Banner/BannerShowcase.tsx +1 -8
- package/templates/blocks/components/Blockquote/BlockquoteShowcase.tsx +1 -8
- package/templates/blocks/components/Carousel/CarouselShowcase.tsx +2 -12
- package/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerShowcase.tsx +6 -9
- package/templates/blocks/components/ChatLayout/ChatLayoutPanelChat.tsx +10 -12
- package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +1 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListShowcase.tsx +1 -9
- package/templates/blocks/components/ChatMessageMetadata/ChatMessageMetadataShowcase.tsx +1 -8
- package/templates/blocks/components/ChatSendButton/ChatSendButtonInComposer.tsx +1 -8
- package/templates/blocks/components/Citation/CitationInlineText.tsx +4 -4
- package/templates/blocks/components/Code/CodeInlineInParagraph.tsx +1 -8
- package/templates/blocks/components/CodeBlock/CodeBlockBashCommand.tsx +1 -1
- package/templates/blocks/components/CodeBlock/CodeBlockJSONConfig.tsx +1 -1
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.doc.mjs +15 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.tsx +26 -0
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemShowcase.tsx +9 -12
- package/templates/blocks/components/ContextMenu/ContextMenuShowcase.tsx +13 -15
- package/templates/blocks/components/DateInput/DateInputDateRange.doc.mjs +2 -2
- package/templates/blocks/components/Divider/DividerShowcase.tsx +1 -8
- package/templates/blocks/components/Divider/DividerVertical.tsx +7 -9
- package/templates/blocks/components/Field/FieldShowcase.tsx +1 -8
- package/templates/blocks/components/FormLayout/FormLayoutHorizontal.tsx +1 -6
- package/templates/blocks/components/Grid/GridResponsiveAutoFit.tsx +1 -9
- package/templates/blocks/components/HoverCard/HoverCardInlineTextHoverCard.tsx +4 -6
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +1 -6
- package/templates/blocks/components/HoverCard/HoverCardProfileHoverCard.tsx +2 -8
- package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -8
- package/templates/blocks/components/OverflowList/OverflowListOverflowBadges.tsx +8 -11
- package/templates/blocks/components/OverflowList/OverflowListOverflowDropdownActions.tsx +9 -12
- package/templates/blocks/components/Overlay/OverlayBottomStrip.tsx +4 -17
- package/templates/blocks/components/Overlay/OverlayHoverReveal.tsx +15 -16
- package/templates/blocks/components/Overlay/OverlayShowcase.tsx +5 -21
- package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -14
- package/templates/blocks/components/Pagination/PaginationPageSize.tsx +12 -14
- package/templates/blocks/components/Pagination/PaginationVariants.tsx +1 -8
- package/templates/blocks/components/Pagination/PaginationWithTable.tsx +2 -14
- package/templates/blocks/components/Slider/SliderShowcase.tsx +10 -1
- package/templates/blocks/components/ToggleButton/ToggleButtonGroup.doc.mjs +1 -1
- package/templates/blocks/components/Tokenizer/TokenizerClear.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerCreatable.tsx +2 -7
- package/templates/blocks/components/Tokenizer/TokenizerEndContent.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerIcon.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerMaxEntries.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerOverflow.tsx +2 -7
- package/templates/blocks/components/Tokenizer/TokenizerShowcase.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerStates.tsx +4 -9
- package/templates/blocks/components/Toolbar/ToolbarCardHeader.tsx +1 -10
- package/templates/blocks/components/Toolbar/ToolbarSizes.tsx +1 -8
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +1 -8
- package/templates/blocks/components/Toolbar/ToolbarThreeSlot.tsx +1 -10
- package/templates/blocks/components/Toolbar/ToolbarWithTabs.tsx +8 -11
- package/templates/pages/ai-chat/page.tsx +71 -64
- package/templates/pages/ai-chat-landing/page.tsx +8 -12
- package/templates/pages/centered-hero/page.tsx +13 -15
- package/templates/pages/classic-gallery/page.tsx +27 -34
- package/templates/pages/detail-page/page.tsx +18 -18
- package/templates/pages/documentation/page.tsx +42 -58
- package/templates/pages/documentation-design/page.tsx +82 -60
- package/templates/pages/documentation-technical/page.tsx +101 -60
- package/templates/pages/editor/page.tsx +42 -54
- package/templates/pages/file-explorer/page.tsx +13 -16
- package/templates/pages/form-two-column/page.tsx +13 -17
- package/templates/pages/gallery-hero/page.tsx +13 -15
- package/templates/pages/ide/page.tsx +188 -264
- package/templates/pages/library/page.tsx +16 -23
- package/templates/pages/login/page.tsx +14 -18
- package/templates/pages/login-card/page.tsx +14 -18
- package/templates/pages/login-split/page.tsx +50 -48
- package/templates/pages/login-sso/page.tsx +9 -13
- package/templates/pages/mixed-gallery/page.tsx +51 -45
- package/templates/pages/payment-form/page.tsx +56 -70
- package/templates/pages/product-detail/page.tsx +27 -33
- package/templates/pages/product-gallery/page.tsx +7 -13
- package/templates/pages/settings-dialog/page.tsx +35 -43
- package/templates/pages/settings-sidebar/page.tsx +39 -47
- package/templates/pages/side-gallery/page.tsx +6 -9
- package/templates/pages/table-grouped/page.tsx +11 -15
- package/templates/pages/theme-showcase/page.tsx +33 -37
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.doc.mjs +0 -14
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.tsx +0 -67
|
@@ -6,7 +6,6 @@ import * as path from 'node:path';
|
|
|
6
6
|
import * as os from 'node:os';
|
|
7
7
|
import {Command} from 'commander';
|
|
8
8
|
import {registerUpgrade} from './upgrade.mjs';
|
|
9
|
-
import {latestVersion} from '../codemods/registry.mjs';
|
|
10
9
|
|
|
11
10
|
let tmpDir;
|
|
12
11
|
let originalCwd;
|
|
@@ -15,7 +14,7 @@ let stdoutCalls;
|
|
|
15
14
|
let exitCode;
|
|
16
15
|
|
|
17
16
|
beforeEach(() => {
|
|
18
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), '
|
|
17
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-upgrade-test-'));
|
|
19
18
|
originalCwd = process.cwd();
|
|
20
19
|
process.chdir(tmpDir);
|
|
21
20
|
logCalls = [];
|
|
@@ -53,13 +52,28 @@ function createProgram() {
|
|
|
53
52
|
return program;
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
function writePkg(deps) {
|
|
55
|
+
function writePkg(deps = {}) {
|
|
57
56
|
fs.writeFileSync(
|
|
58
57
|
path.join(tmpDir, 'package.json'),
|
|
59
58
|
JSON.stringify({name: 'fixture', dependencies: deps}, null, 2),
|
|
60
59
|
);
|
|
61
60
|
}
|
|
62
61
|
|
|
62
|
+
function writeInstalledCore(version, packageName = '@astryxdesign/core') {
|
|
63
|
+
const parts = packageName.split('/');
|
|
64
|
+
const dir = path.join(tmpDir, 'node_modules', ...parts);
|
|
65
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
66
|
+
fs.writeFileSync(
|
|
67
|
+
path.join(dir, 'package.json'),
|
|
68
|
+
JSON.stringify({name: packageName, version}, null, 2),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function writeSourceFile() {
|
|
73
|
+
fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
|
|
74
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'index.ts'), 'const x = 1;\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
63
77
|
/** Run a command and capture the parsed JSON response (last printed JSON line). */
|
|
64
78
|
async function runJson(args) {
|
|
65
79
|
const program = createProgram();
|
|
@@ -83,53 +97,53 @@ async function runJson(args) {
|
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
describe('upgrade gate (semver comparison)', () => {
|
|
86
|
-
it('does NOT block an upgrade from 0.0.9 to 0.0.10 (regression)', async () => {
|
|
100
|
+
it('does NOT block an upgrade from 0.0.9 to installed 0.0.10 (regression)', async () => {
|
|
87
101
|
// The original bug: string compare said '0.0.9' >= '0.0.10', so the
|
|
88
102
|
// gate told users "Already up to date" without --force.
|
|
89
|
-
writePkg(
|
|
103
|
+
writePkg();
|
|
104
|
+
writeInstalledCore('0.0.10');
|
|
105
|
+
writeSourceFile();
|
|
90
106
|
|
|
91
|
-
const result = await runJson(['--json', 'upgrade', '--
|
|
92
|
-
// Either a real run or "no codemods available" — but never the
|
|
93
|
-
// up-to-date short-circuit (which has no `type` field).
|
|
107
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.9', '--path', 'src']);
|
|
94
108
|
expect(result).not.toBeNull();
|
|
95
|
-
// The receipt or "no codemods" path should not look like the
|
|
96
|
-
// up-to-date short-circuit (which would return without printing JSON).
|
|
97
109
|
expect(result.type === 'upgrade.run' || result.error || logCalls.some(l => l.includes('No codemods'))).toBeTruthy();
|
|
98
110
|
});
|
|
99
111
|
|
|
100
|
-
it('blocks when
|
|
101
|
-
writePkg(
|
|
112
|
+
it('blocks when --from >= installed target by semver (e.g. 0.0.10 → 0.0.9)', async () => {
|
|
113
|
+
writePkg();
|
|
114
|
+
writeInstalledCore('0.0.9');
|
|
102
115
|
const program = createProgram();
|
|
103
|
-
await program.parseAsync(['node', 'astryx', 'upgrade', '--
|
|
116
|
+
await program.parseAsync(['node', 'astryx', 'upgrade', '--from', '0.0.10']);
|
|
104
117
|
const output = stdoutCalls.join('') + logCalls.join('\n');
|
|
105
118
|
expect(output).toMatch(/up to date|Already/i);
|
|
106
119
|
});
|
|
107
120
|
});
|
|
108
121
|
|
|
109
|
-
describe('upgrade
|
|
110
|
-
it('
|
|
111
|
-
writePkg(
|
|
112
|
-
|
|
122
|
+
describe('upgrade argument validation', () => {
|
|
123
|
+
it('requires --from for upgrade runs', async () => {
|
|
124
|
+
writePkg();
|
|
125
|
+
writeInstalledCore('0.0.15');
|
|
126
|
+
const result = await runJson(['--json', 'upgrade']);
|
|
113
127
|
expect(result).not.toBeNull();
|
|
114
|
-
expect(result.error).toMatch(/
|
|
128
|
+
expect(result.error).toMatch(/Missing required --from/);
|
|
115
129
|
expect(exitCode).toBe(1);
|
|
116
130
|
});
|
|
117
131
|
|
|
118
132
|
it('rejects bogus --from values', async () => {
|
|
119
|
-
writePkg(
|
|
120
|
-
|
|
133
|
+
writePkg();
|
|
134
|
+
writeInstalledCore('0.0.15');
|
|
135
|
+
const result = await runJson(['--json', 'upgrade', '--from', 'not-a-version']);
|
|
121
136
|
expect(result).not.toBeNull();
|
|
122
137
|
expect(result.error).toMatch(/Invalid --from/);
|
|
123
138
|
expect(exitCode).toBe(1);
|
|
124
139
|
});
|
|
125
140
|
|
|
126
|
-
it('
|
|
127
|
-
writePkg(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const result = await runJson(['--json', 'upgrade', '--
|
|
131
|
-
|
|
132
|
-
expect(result?.error || '').not.toMatch(/Invalid --to/);
|
|
141
|
+
it('detects the installed target version from @astryxdesign/core', async () => {
|
|
142
|
+
writePkg();
|
|
143
|
+
writeInstalledCore('0.0.15');
|
|
144
|
+
writeSourceFile();
|
|
145
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.14', '--path', 'src']);
|
|
146
|
+
expect(result?.error || '').not.toMatch(/Could not find installed/);
|
|
133
147
|
});
|
|
134
148
|
});
|
|
135
149
|
|
package/src/config.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {validateConfig, validateIntegration} from './lib/config-schema.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type-preserving helpers for Astryx config and integration manifests.
|
|
7
|
+
*
|
|
8
|
+
* These are intentionally tiny runtime identity functions. Their value is the
|
|
9
|
+
* exported TypeScript surface from `@astryxdesign/cli/config`, so config files
|
|
10
|
+
* can get editor/type feedback without coupling to CLI internals.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @template {import('./types/config').AstryxConfig} T
|
|
15
|
+
* @param {T} config
|
|
16
|
+
* @returns {T}
|
|
17
|
+
*/
|
|
18
|
+
export function createConfig(config) {
|
|
19
|
+
validateConfig(config);
|
|
20
|
+
return config;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @template {import('./types/config').AstryxIntegration} T
|
|
25
|
+
* @param {T} integration
|
|
26
|
+
* @returns {T}
|
|
27
|
+
*/
|
|
28
|
+
export function createIntegration(integration) {
|
|
29
|
+
validateIntegration(integration);
|
|
30
|
+
return integration;
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it} from 'vitest';
|
|
4
|
+
import {createConfig, createIntegration} from './config.mjs';
|
|
5
|
+
|
|
6
|
+
describe('config helpers', () => {
|
|
7
|
+
it('return config and integration objects unchanged', () => {
|
|
8
|
+
const config = {integrations: ['@acme/widgets']};
|
|
9
|
+
const integration = {name: '@acme/widgets', docs: './docs'};
|
|
10
|
+
expect(createConfig(config)).toBe(config);
|
|
11
|
+
expect(createIntegration(integration)).toBe(integration);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('validates config and integration shapes', () => {
|
|
15
|
+
expect(() => createConfig({integrations: [42]})).toThrow(/integrations/);
|
|
16
|
+
expect(() => createIntegration({docs: './docs'})).toThrow(/name/);
|
|
17
|
+
expect(() =>
|
|
18
|
+
createIntegration({
|
|
19
|
+
name: '@acme/widgets',
|
|
20
|
+
postCodemod: [{name: 'empty'}],
|
|
21
|
+
}),
|
|
22
|
+
).toThrow(/postCodemod/);
|
|
23
|
+
});
|
|
24
|
+
});
|
package/src/index.mjs
CHANGED
|
@@ -63,6 +63,9 @@ export const JSON_SUPPORTED = new Set([
|
|
|
63
63
|
'upgrade',
|
|
64
64
|
'manifest',
|
|
65
65
|
'doctor',
|
|
66
|
+
'layout expand',
|
|
67
|
+
'layout check',
|
|
68
|
+
'layout grammar',
|
|
66
69
|
]);
|
|
67
70
|
|
|
68
71
|
program
|
|
@@ -243,12 +246,14 @@ const commands = [
|
|
|
243
246
|
{name: 'swizzle', path: './commands/swizzle.mjs', register: 'registerSwizzle'},
|
|
244
247
|
// agent-docs folded into init — functions still importable from agent-docs.mjs
|
|
245
248
|
{name: 'template', path: './commands/template.mjs', register: 'registerTemplate'},
|
|
249
|
+
{name: 'layout', path: './commands/layout.mjs', register: 'registerLayout'},
|
|
246
250
|
{name: 'gap-report', path: './commands/gap-report.mjs', register: 'registerGapReport'},
|
|
247
251
|
{name: 'upgrade', path: './commands/upgrade.mjs', register: 'registerUpgrade'},
|
|
248
252
|
{name: 'theme', path: './commands/build-theme.mjs', register: 'registerTheme'},
|
|
249
253
|
{name: 'hook', path: './commands/hook/index.mjs', register: 'registerHook'},
|
|
250
254
|
{name: 'discover', path: './commands/discover.mjs', register: 'registerDiscover'},
|
|
251
255
|
{name: 'search', path: './commands/search.mjs', register: 'registerSearch'},
|
|
256
|
+
{name: 'build', path: './commands/build.mjs', register: 'registerBuild'},
|
|
252
257
|
{name: 'doctor', path: './commands/doctor.mjs', register: 'registerDoctor'},
|
|
253
258
|
];
|
|
254
259
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** Runtime schemas for Astryx config and integration manifests. */
|
|
4
|
+
|
|
5
|
+
import {z} from 'zod';
|
|
6
|
+
|
|
7
|
+
const Fn = z.custom(value => typeof value === 'function', {
|
|
8
|
+
message: 'Expected function',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const StringOrStringArray = z.union([z.string(), z.array(z.string())]);
|
|
12
|
+
const GapReportSchema = z.union([
|
|
13
|
+
z.literal(false),
|
|
14
|
+
z.object({command: z.string()}).passthrough(),
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const AstryxConfigSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
packages: StringOrStringArray.optional(),
|
|
20
|
+
integrations: StringOrStringArray.optional(),
|
|
21
|
+
gapReport: GapReportSchema.optional(),
|
|
22
|
+
template: z
|
|
23
|
+
.object({
|
|
24
|
+
get: Fn.optional(),
|
|
25
|
+
})
|
|
26
|
+
.passthrough()
|
|
27
|
+
.optional(),
|
|
28
|
+
})
|
|
29
|
+
.passthrough();
|
|
30
|
+
|
|
31
|
+
export const AstryxIntegrationCodemodSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
name: z.string(),
|
|
34
|
+
from: z.string().optional(),
|
|
35
|
+
to: z.string().optional(),
|
|
36
|
+
title: z.string().optional(),
|
|
37
|
+
description: z.string().optional(),
|
|
38
|
+
pr: z.string().optional(),
|
|
39
|
+
optional: z.boolean().optional(),
|
|
40
|
+
fileExtensions: z.array(z.string()).optional(),
|
|
41
|
+
transform: z.union([z.string(), Fn]),
|
|
42
|
+
})
|
|
43
|
+
.passthrough();
|
|
44
|
+
|
|
45
|
+
export const AstryxPostCodemodHookSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
name: z.string().optional(),
|
|
48
|
+
run: Fn.optional(),
|
|
49
|
+
command: Fn.optional(),
|
|
50
|
+
})
|
|
51
|
+
.passthrough()
|
|
52
|
+
.refine(value => value.run || value.command, {
|
|
53
|
+
message: 'postCodemod hook must define run() or command()',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const AstryxIntegrationSchema = z
|
|
57
|
+
.object({
|
|
58
|
+
name: z.string(),
|
|
59
|
+
version: z.string().optional(),
|
|
60
|
+
displayName: z.string().optional(),
|
|
61
|
+
description: z.string().optional(),
|
|
62
|
+
docs: z.string().optional(),
|
|
63
|
+
category: z.string().optional(),
|
|
64
|
+
blocks: z.string().optional(),
|
|
65
|
+
gapReport: GapReportSchema.optional(),
|
|
66
|
+
template: z
|
|
67
|
+
.object({
|
|
68
|
+
get: z.union([z.string(), Fn]).optional(),
|
|
69
|
+
})
|
|
70
|
+
.passthrough()
|
|
71
|
+
.optional(),
|
|
72
|
+
codemods: z.array(AstryxIntegrationCodemodSchema).optional(),
|
|
73
|
+
postCodemod: z.array(AstryxPostCodemodHookSchema).optional(),
|
|
74
|
+
})
|
|
75
|
+
.passthrough();
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} label
|
|
79
|
+
* @param {import('zod').ZodError} error
|
|
80
|
+
*/
|
|
81
|
+
function formatZodError(label, error) {
|
|
82
|
+
const issues = error.issues
|
|
83
|
+
.map(issue => {
|
|
84
|
+
const path = issue.path.length ? issue.path.join('.') : '(root)';
|
|
85
|
+
return `${path}: ${issue.message}`;
|
|
86
|
+
})
|
|
87
|
+
.join('; ');
|
|
88
|
+
return `${label} is invalid: ${issues}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {unknown} config
|
|
93
|
+
* @returns {import('../types/config').AstryxConfig}
|
|
94
|
+
*/
|
|
95
|
+
export function validateConfig(config) {
|
|
96
|
+
const result = AstryxConfigSchema.safeParse(config);
|
|
97
|
+
if (!result.success) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
formatZodError('astryx.config.mjs default export', result.error),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return result.data;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {unknown} integration
|
|
107
|
+
* @param {string} [label]
|
|
108
|
+
* @returns {import('../types/config').AstryxIntegration}
|
|
109
|
+
*/
|
|
110
|
+
export function validateIntegration(
|
|
111
|
+
integration,
|
|
112
|
+
label = 'integration manifest',
|
|
113
|
+
) {
|
|
114
|
+
const result = AstryxIntegrationSchema.safeParse(integration);
|
|
115
|
+
if (!result.success) {
|
|
116
|
+
throw new Error(formatZodError(label, result.error));
|
|
117
|
+
}
|
|
118
|
+
return result.data;
|
|
119
|
+
}
|
package/src/lib/config.mjs
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
import * as fs from 'node:fs';
|
|
11
11
|
import * as path from 'node:path';
|
|
12
12
|
import {pathToFileURL} from 'node:url';
|
|
13
|
+
import {loadIntegrations} from './integrations.mjs';
|
|
14
|
+
import {validateConfig} from './config-schema.mjs';
|
|
13
15
|
|
|
14
16
|
const DEFAULTS = {
|
|
15
17
|
packages: [],
|
|
18
|
+
integrations: [],
|
|
16
19
|
};
|
|
17
20
|
|
|
18
21
|
/**
|
|
@@ -39,17 +42,30 @@ export async function loadConfig(startDir = process.cwd()) {
|
|
|
39
42
|
const configPath = findConfigPath(startDir);
|
|
40
43
|
if (!configPath) return {...DEFAULTS};
|
|
41
44
|
|
|
45
|
+
let rawConfig;
|
|
42
46
|
try {
|
|
43
47
|
const mod = await import(pathToFileURL(configPath).href);
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
...DEFAULTS,
|
|
47
|
-
...config,
|
|
48
|
-
packages: normalizePackages(config.packages, path.dirname(configPath)),
|
|
49
|
-
};
|
|
48
|
+
rawConfig = mod.default || {};
|
|
50
49
|
} catch {
|
|
51
50
|
return {...DEFAULTS};
|
|
52
51
|
}
|
|
52
|
+
|
|
53
|
+
const config = validateConfig(rawConfig);
|
|
54
|
+
const configDir = path.dirname(configPath);
|
|
55
|
+
const integrationSpecs = normalizeIntegrations(config.integrations);
|
|
56
|
+
const loadedIntegrations = await loadIntegrations(integrationSpecs, {
|
|
57
|
+
cwd: configDir,
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
...DEFAULTS,
|
|
61
|
+
...config,
|
|
62
|
+
packages: normalizePackages(config.packages, configDir),
|
|
63
|
+
integrations: integrationSpecs,
|
|
64
|
+
loadedIntegrations,
|
|
65
|
+
gapReport:
|
|
66
|
+
config.gapReport ?? firstDefined(loadedIntegrations, 'gapReport'),
|
|
67
|
+
template: mergeTemplateConfig(config.template, loadedIntegrations),
|
|
68
|
+
};
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
/**
|
|
@@ -72,3 +88,26 @@ function normalizePackages(packages, configDir) {
|
|
|
72
88
|
}
|
|
73
89
|
return result;
|
|
74
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalize integration specs to a string array. Integrations are package names
|
|
94
|
+
* or manifest paths consumed by `astryx upgrade --integration`.
|
|
95
|
+
*/
|
|
96
|
+
function normalizeIntegrations(integrations) {
|
|
97
|
+
if (!integrations) return [];
|
|
98
|
+
const arr = Array.isArray(integrations) ? integrations : [integrations];
|
|
99
|
+
return arr.filter(value => typeof value === 'string' && value !== '');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function firstDefined(integrations, key) {
|
|
103
|
+
for (const integration of integrations) {
|
|
104
|
+
if (integration[key] !== undefined) return integration[key];
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function mergeTemplateConfig(template, integrations) {
|
|
110
|
+
if (template?.get) return template;
|
|
111
|
+
const integrationTemplate = firstDefined(integrations, 'template');
|
|
112
|
+
return integrationTemplate ?? template;
|
|
113
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import {loadConfig} from './config.mjs';
|
|
7
|
+
|
|
8
|
+
let tmpDir;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-config-test-'));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function installIntegrationPackage(dir, name = '@nest/xds-meta') {
|
|
19
|
+
const packageDir = path.join(dir, 'node_modules', ...name.split('/'));
|
|
20
|
+
fs.mkdirSync(packageDir, {recursive: true});
|
|
21
|
+
fs.writeFileSync(
|
|
22
|
+
path.join(packageDir, 'package.json'),
|
|
23
|
+
JSON.stringify({name, astryx: {integration: './astryx.integration.mjs'}}),
|
|
24
|
+
);
|
|
25
|
+
fs.writeFileSync(
|
|
26
|
+
path.join(packageDir, 'astryx.integration.mjs'),
|
|
27
|
+
`export default { name: '${name}' };
|
|
28
|
+
`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('loadConfig', () => {
|
|
33
|
+
it('normalizes integrations from astryx.config.mjs', async () => {
|
|
34
|
+
const dir = path.join(tmpDir, 'one');
|
|
35
|
+
fs.mkdirSync(dir);
|
|
36
|
+
installIntegrationPackage(dir);
|
|
37
|
+
fs.writeFileSync(
|
|
38
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
39
|
+
`export default { integrations: '@nest/xds-meta' };\n`,
|
|
40
|
+
);
|
|
41
|
+
await expect(loadConfig(dir)).resolves.toMatchObject({
|
|
42
|
+
integrations: ['@nest/xds-meta'],
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('rejects invalid integration package manifests', async () => {
|
|
47
|
+
const dir = path.join(tmpDir, 'bad');
|
|
48
|
+
fs.mkdirSync(dir);
|
|
49
|
+
const packageDir = path.join(dir, 'node_modules', '@bad', 'widgets');
|
|
50
|
+
fs.mkdirSync(packageDir, {recursive: true});
|
|
51
|
+
fs.writeFileSync(
|
|
52
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
53
|
+
`export default { integrations: '@bad/widgets' };\n`,
|
|
54
|
+
);
|
|
55
|
+
fs.writeFileSync(
|
|
56
|
+
path.join(packageDir, 'package.json'),
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
name: '@bad/widgets',
|
|
59
|
+
astryx: {integration: './astryx.integration.mjs'},
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
fs.writeFileSync(
|
|
63
|
+
path.join(packageDir, 'astryx.integration.mjs'),
|
|
64
|
+
`export default { docs: './docs' };\n`,
|
|
65
|
+
);
|
|
66
|
+
await expect(loadConfig(dir)).rejects.toThrow(/name/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('normalizes integration arrays', async () => {
|
|
70
|
+
const dir = path.join(tmpDir, 'two');
|
|
71
|
+
fs.mkdirSync(dir);
|
|
72
|
+
installIntegrationPackage(dir);
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
75
|
+
`export default { integrations: ['@nest/xds-meta', ''] };\n`,
|
|
76
|
+
);
|
|
77
|
+
await expect(loadConfig(dir)).resolves.toMatchObject({
|
|
78
|
+
integrations: ['@nest/xds-meta'],
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('rejects invalid config shapes', async () => {
|
|
83
|
+
const dir = path.join(tmpDir, 'invalid');
|
|
84
|
+
fs.mkdirSync(dir);
|
|
85
|
+
fs.writeFileSync(
|
|
86
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
87
|
+
`export default { integrations: [42] };\n`,
|
|
88
|
+
);
|
|
89
|
+
await expect(loadConfig(dir)).rejects.toThrow(/integrations/);
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/lib/error-codes.mjs
CHANGED
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
* | 'ERR_UNKNOWN_AGENT'
|
|
57
57
|
* | 'ERR_UNKNOWN_FEATURE'
|
|
58
58
|
* | 'ERR_UNKNOWN_CODEMOD'
|
|
59
|
+
| 'ERR_CODEMOD_FAILED'
|
|
59
60
|
* | 'ERR_NOT_FOUND'
|
|
60
61
|
* | 'ERR_NO_DOC'
|
|
61
62
|
* | 'ERR_NO_SHOWCASE'
|
|
@@ -74,6 +75,8 @@
|
|
|
74
75
|
* | 'ERR_DEP_MISSING'
|
|
75
76
|
* | 'ERR_GH_CLI'
|
|
76
77
|
* | 'ERR_GAP_REPORT_FAILED'
|
|
78
|
+
* | 'ERR_LAYOUT_PARSE'
|
|
79
|
+
* | 'ERR_LAYOUT_INVALID'
|
|
77
80
|
* )} ErrorCode
|
|
78
81
|
*/
|
|
79
82
|
|
|
@@ -129,6 +132,8 @@ export const ERROR_CODES = Object.freeze({
|
|
|
129
132
|
ERR_UNKNOWN_FEATURE: 'ERR_UNKNOWN_FEATURE',
|
|
130
133
|
/** A `--codemod` value did not match any registered codemod (upgrade). */
|
|
131
134
|
ERR_UNKNOWN_CODEMOD: 'ERR_UNKNOWN_CODEMOD',
|
|
135
|
+
/** One or more codemods failed during an upgrade run. */
|
|
136
|
+
ERR_CODEMOD_FAILED: 'ERR_CODEMOD_FAILED',
|
|
132
137
|
/** A generic discover/lookup query matched nothing in any package. */
|
|
133
138
|
ERR_NOT_FOUND: 'ERR_NOT_FOUND',
|
|
134
139
|
|
|
@@ -177,6 +182,12 @@ export const ERROR_CODES = Object.freeze({
|
|
|
177
182
|
ERR_GH_CLI: 'ERR_GH_CLI',
|
|
178
183
|
/** Filing a gap report failed at the command/integration boundary. */
|
|
179
184
|
ERR_GAP_REPORT_FAILED: 'ERR_GAP_REPORT_FAILED',
|
|
185
|
+
|
|
186
|
+
// ── Layout expressions (XLE/XLO) ─────────────────────────────────
|
|
187
|
+
/** A layout expression failed to parse (syntax error, with line/col). */
|
|
188
|
+
ERR_LAYOUT_PARSE: 'ERR_LAYOUT_PARSE',
|
|
189
|
+
/** A layout expression parsed but failed validation (unknown component/prop/enum/block). */
|
|
190
|
+
ERR_LAYOUT_INVALID: 'ERR_LAYOUT_INVALID',
|
|
180
191
|
});
|
|
181
192
|
|
|
182
193
|
/**
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Integration manifest loading for Astryx config.
|
|
5
|
+
*
|
|
6
|
+
* Integrations are package names or direct manifest paths listed in
|
|
7
|
+
* astryx.config.mjs. A manifest can contribute docs/discovery metadata,
|
|
8
|
+
* gap-report/template hooks, upgrade codemods, and post-codemod hooks.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import {pathToFileURL} from 'node:url';
|
|
14
|
+
import {validateIntegration} from './config-schema.mjs';
|
|
15
|
+
|
|
16
|
+
export function isPathSpec(spec) {
|
|
17
|
+
return (
|
|
18
|
+
spec.startsWith('.') ||
|
|
19
|
+
spec.startsWith('/') ||
|
|
20
|
+
spec.endsWith('.mjs') ||
|
|
21
|
+
spec.endsWith('.js')
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolvePackageDir(packageName, cwd = process.cwd()) {
|
|
26
|
+
return path.resolve(cwd, 'node_modules', ...packageName.split('/'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveIntegrationFile(spec, cwd = process.cwd()) {
|
|
30
|
+
if (isPathSpec(spec)) {
|
|
31
|
+
return path.resolve(cwd, spec);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const packageDir = resolvePackageDir(spec, cwd);
|
|
35
|
+
const pkgPath = path.join(packageDir, 'package.json');
|
|
36
|
+
let pkg;
|
|
37
|
+
try {
|
|
38
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
39
|
+
} catch {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Could not find installed integration package "${spec}" at ${pkgPath}. Install it first or pass a direct integration file path.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const manifestPath = pkg.astryx?.integration ?? pkg.xds?.integration;
|
|
46
|
+
if (!manifestPath) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Package "${spec}" does not declare astryx.integration (or legacy xds.integration) in package.json.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return path.resolve(packageDir, manifestPath);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizePackageContribution(integration) {
|
|
55
|
+
const docs = integration.docs;
|
|
56
|
+
if (!docs) return null;
|
|
57
|
+
const packageDir = integration.__packageDir ?? integration.__dir;
|
|
58
|
+
const docsDir = path.resolve(packageDir, docs);
|
|
59
|
+
return {
|
|
60
|
+
name: integration.name ?? integration.__spec,
|
|
61
|
+
version: integration.version,
|
|
62
|
+
description: integration.description,
|
|
63
|
+
displayName: integration.displayName,
|
|
64
|
+
dir: packageDir,
|
|
65
|
+
astryx: {
|
|
66
|
+
docs,
|
|
67
|
+
category: integration.category,
|
|
68
|
+
blocks: integration.blocks,
|
|
69
|
+
},
|
|
70
|
+
category: integration.category ?? integration.name ?? integration.__spec,
|
|
71
|
+
docsDir,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveHookFunction(hook, integration) {
|
|
76
|
+
if (typeof hook === 'function') return hook;
|
|
77
|
+
if (typeof hook !== 'string') return hook;
|
|
78
|
+
const [moduleSpec, exportName = 'default'] = hook.split('#');
|
|
79
|
+
return async (...args) => {
|
|
80
|
+
const mod = isPathSpec(moduleSpec)
|
|
81
|
+
? await import(
|
|
82
|
+
pathToFileURL(path.resolve(integration.__dir, moduleSpec)).href
|
|
83
|
+
)
|
|
84
|
+
: await import(moduleSpec);
|
|
85
|
+
const fn = exportName === 'default' ? mod.default : mod[exportName];
|
|
86
|
+
if (typeof fn !== 'function') {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Integration hook ${hook} did not resolve to a function.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return fn(...args);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function loadIntegrations(specs = [], {cwd = process.cwd()} = {}) {
|
|
96
|
+
const integrations = [];
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
|
|
99
|
+
for (const spec of specs) {
|
|
100
|
+
if (!spec || seen.has(spec)) continue;
|
|
101
|
+
seen.add(spec);
|
|
102
|
+
|
|
103
|
+
const file = resolveIntegrationFile(spec, cwd);
|
|
104
|
+
const mod = await import(pathToFileURL(file).href);
|
|
105
|
+
const exported = mod.default ?? mod.integration ?? mod;
|
|
106
|
+
if (!exported || typeof exported !== 'object') {
|
|
107
|
+
throw new Error(`Integration ${spec} did not export an object.`);
|
|
108
|
+
}
|
|
109
|
+
const integration = validateIntegration(exported, `Integration ${spec}`);
|
|
110
|
+
|
|
111
|
+
const integrationDir = path.dirname(file);
|
|
112
|
+
const packageDir = isPathSpec(spec)
|
|
113
|
+
? integrationDir
|
|
114
|
+
: resolvePackageDir(spec, cwd);
|
|
115
|
+
const normalized = {
|
|
116
|
+
...integration,
|
|
117
|
+
__file: file,
|
|
118
|
+
__dir: integrationDir,
|
|
119
|
+
__packageDir: packageDir,
|
|
120
|
+
__spec: spec,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (Array.isArray(normalized.codemods)) {
|
|
124
|
+
for (const codemod of normalized.codemods) {
|
|
125
|
+
if (typeof codemod.transform === 'string') {
|
|
126
|
+
const transformPath = path.resolve(integrationDir, codemod.transform);
|
|
127
|
+
const transformMod = await import(pathToFileURL(transformPath).href);
|
|
128
|
+
codemod.transform =
|
|
129
|
+
transformMod.default ?? transformMod.transform ?? transformMod;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
normalized.package = normalizePackageContribution(normalized);
|
|
135
|
+
if (
|
|
136
|
+
normalized.gapReport?.command &&
|
|
137
|
+
isPathSpec(normalized.gapReport.command)
|
|
138
|
+
) {
|
|
139
|
+
normalized.gapReport = {
|
|
140
|
+
...normalized.gapReport,
|
|
141
|
+
command: path.resolve(packageDir, normalized.gapReport.command),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (normalized.template?.get) {
|
|
145
|
+
normalized.template = {
|
|
146
|
+
...normalized.template,
|
|
147
|
+
get: resolveHookFunction(normalized.template.get, normalized),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
integrations.push(normalized);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return integrations;
|
|
155
|
+
}
|