@astryxdesign/cli 0.1.1 → 0.1.2-canary.043f3fd
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 +50 -0
- package/docs/theme.doc.mjs +2 -2
- package/docs/working-with-ai.doc.mjs +6 -6
- package/package.json +31 -8
- package/src/api/component.mjs +253 -12
- package/src/api/discover.mjs +87 -28
- package/src/api/doctor.mjs +7 -22
- package/src/api/index.mjs +1 -0
- package/src/api/layout.mjs +302 -0
- package/src/api/layout.test.mjs +241 -0
- package/src/api/template-integration.test.mjs +225 -0
- package/src/api/template.mjs +421 -122
- package/src/api/template.test.mjs +2 -0
- package/src/api/theme-add.mjs +182 -0
- 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 +2 -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 +2 -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__/v0.1.0-ordering.test.mjs +81 -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 +15 -6
- package/src/codemods/transforms/v0.1.2/__tests__/rename-text-color-active-to-accent.test.mjs +120 -0
- package/src/codemods/transforms/v0.1.2/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.2/rename-text-color-active-to-accent.mjs +136 -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/build-theme.import-path.test.mjs +22 -2
- package/src/commands/build-theme.mjs +185 -16
- 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/init.mjs +34 -8
- package/src/commands/init.next-steps.test.mjs +46 -0
- package/src/commands/json-contract.test.mjs +0 -32
- package/src/commands/layout.mjs +139 -0
- package/src/commands/swizzle.mjs +241 -224
- 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 -239
- package/src/commands/validate-integration.mjs +110 -0
- package/src/commands/validate-integration.test.mjs +124 -0
- package/src/config.mjs +18 -0
- package/src/config.test.mjs +100 -0
- package/src/index.mjs +13 -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 +105 -0
- package/src/lib/error-codes.mjs +18 -12
- package/src/lib/integration-warnings.mjs +62 -0
- package/src/lib/integration-warnings.test.mjs +102 -0
- package/src/lib/integrations.mjs +132 -0
- package/src/lib/integrations.test.mjs +135 -0
- package/src/lib/levenshtein.mjs +29 -0
- package/src/lib/manifest.mjs +15 -2
- package/src/lib/module-loader.mjs +80 -0
- package/src/lib/module-loader.test.mjs +106 -0
- package/src/lib/package-scanner.mjs +31 -7
- package/src/lib/project.mjs +502 -0
- package/src/lib/project.test.mjs +308 -0
- package/src/lib/string-utils.mjs +5 -14
- package/src/lib/xle/browser.d.ts +100 -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/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 +70 -0
- 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 -252
- package/src/utils/interactive.mjs +2 -2
- package/templates/blocks/components/Card/ClickableCardWithNestedButton.doc.mjs +1 -1
- package/templates/blocks/components/Card/SelectableCardMulti.doc.mjs +1 -1
- package/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerWithProgress.tsx +1 -1
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonShowcase.tsx +3 -3
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.doc.mjs +15 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.tsx +26 -0
- package/templates/blocks/components/DateInput/DateInputDateRange.doc.mjs +2 -2
- 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/FileInput/FileInputShowcase.tsx +4 -2
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +14 -0
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.tsx +43 -0
- package/templates/blocks/components/MultiSelector/MultiSelectorShowcase.tsx +6 -2
- package/templates/blocks/components/NumberInput/NumberInputShowcase.tsx +6 -2
- package/templates/blocks/components/Outline/OutlineShowcase.doc.mjs +15 -0
- package/templates/blocks/components/Outline/OutlineShowcase.tsx +22 -0
- package/templates/blocks/components/RadioList/RadioListShowcase.tsx +8 -1
- package/templates/blocks/components/SegmentedControl/SegmentedControlShowcase.tsx +3 -1
- package/templates/blocks/components/Selector/SelectorShowcase.tsx +4 -1
- package/templates/blocks/components/Slider/SliderShowcase.tsx +10 -1
- package/templates/blocks/components/Tab/TabShowcase.tsx +3 -1
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
- package/templates/blocks/components/TabList/TabListShowcase.tsx +3 -1
- package/templates/blocks/components/TabMenu/TabMenuShowcase.tsx +3 -1
- package/templates/blocks/components/Table/ColumnResizeHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Table/ColumnResizeHookUsage.tsx +59 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.tsx +104 -0
- package/templates/blocks/components/Text/TextColors.tsx +20 -5
- package/templates/blocks/components/TextArea/TextAreaShowcase.tsx +4 -2
- package/templates/blocks/components/TextInput/TextInputShowcase.tsx +4 -2
- package/templates/blocks/components/Thumbnail/ThumbnailDisabled.tsx +11 -6
- package/templates/blocks/components/Thumbnail/ThumbnailGallery.tsx +43 -7
- package/templates/blocks/components/Thumbnail/ThumbnailRemovable.tsx +40 -3
- package/templates/blocks/components/Thumbnail/ThumbnailShowcase.tsx +6 -5
- package/templates/blocks/components/Thumbnail/ThumbnailStates.tsx +11 -6
- package/templates/blocks/components/Timestamp/TimestampColors.tsx +2 -2
- package/templates/blocks/components/ToggleButton/ToggleButtonGroup.doc.mjs +1 -1
- package/templates/blocks/components/Tokenizer/TokenizerShowcase.tsx +8 -6
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +2 -2
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +23 -14
- package/templates/blocks/components/Typeahead/TypeaheadShowcase.tsx +6 -2
- 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 +242 -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/templates/pages/theme-showcase/page.tsx +8 -19
- package/templates/themes/butter/butterTheme.ts +916 -0
- package/templates/themes/butter/icons.tsx +77 -0
- package/templates/themes/chocolate/chocolateTheme.ts +230 -0
- package/templates/themes/chocolate/icons.tsx +77 -0
- package/templates/themes/gothic/gothicTheme.ts +657 -0
- package/templates/themes/gothic/icons.tsx +77 -0
- package/templates/themes/manifest.json +90 -0
- package/templates/themes/matcha/icons.tsx +67 -0
- package/templates/themes/matcha/matchaTheme.ts +247 -0
- package/templates/themes/neutral/icons.tsx +77 -0
- package/templates/themes/neutral/neutralTheme.ts +603 -0
- package/templates/themes/stone/icons.tsx +77 -0
- package/templates/themes/stone/stoneTheme.ts +652 -0
- package/templates/themes/y2k/icons.tsx +67 -0
- package/templates/themes/y2k/y2kTheme.ts +617 -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 -456
- package/src/commands/gap-report.test.mjs +0 -163
- package/src/commands/swizzle-gap-safety.test.mjs +0 -273
- package/src/lib/config.mjs +0 -86
- package/src/lib/config.test.mjs +0 -42
- package/src/types/gap-report.d.ts +0 -29
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.doc.mjs +0 -14
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.tsx +0 -57
- /package/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Pluggable config/discovery cache for the Project API.
|
|
5
|
+
*
|
|
6
|
+
* The Project class memoizes resolved discovery (components, templates,
|
|
7
|
+
* codemods, config) behind a small key/value cache so the same work is never
|
|
8
|
+
* repeated within a single Project instance. The cache interface is
|
|
9
|
+
* intentionally tiny — `get(key)` / `set(key, value)` — so a future
|
|
10
|
+
* file-backed, cross-process cache can drop in with NO API change.
|
|
11
|
+
*
|
|
12
|
+
* Cache keys are derived from a CONTENT HASH of the project's config file
|
|
13
|
+
* (see {@link configContentHash}) combined with the cwd and the
|
|
14
|
+
* discovery-kind (+ args). The in-memory implementation never busts on a
|
|
15
|
+
* content change, but the hash is computed regardless so a hash-busting,
|
|
16
|
+
* file-backed cache works against the exact same key path.
|
|
17
|
+
*
|
|
18
|
+
* @typedef {Object} ConfigCache
|
|
19
|
+
* @property {(key: string) => unknown} get
|
|
20
|
+
* @property {(key: string, value: unknown) => void} set
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import * as fs from 'node:fs';
|
|
24
|
+
import * as crypto from 'node:crypto';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Stable sentinel hash used when there is no config file (or it can't be
|
|
28
|
+
* read). Keeps key derivation total — every cwd still produces a key.
|
|
29
|
+
*/
|
|
30
|
+
export const NO_CONFIG_HASH = 'no-config';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compute a stable content hash for a config file. Reads the file bytes and
|
|
34
|
+
* returns their sha256 hex digest; a missing/unreadable file yields
|
|
35
|
+
* {@link NO_CONFIG_HASH}. The hash is the bust signal a future file-backed
|
|
36
|
+
* cache would key on.
|
|
37
|
+
*
|
|
38
|
+
* @param {string | null | undefined} configPath absolute config path, or null
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function configContentHash(configPath) {
|
|
42
|
+
if (!configPath) return NO_CONFIG_HASH;
|
|
43
|
+
try {
|
|
44
|
+
const bytes = fs.readFileSync(configPath);
|
|
45
|
+
return crypto.createHash('sha256').update(bytes).digest('hex');
|
|
46
|
+
} catch {
|
|
47
|
+
return NO_CONFIG_HASH;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build a cache key from a config content hash, a cwd, and a discovery kind
|
|
53
|
+
* (already including any args, e.g. `codemods:0.1.0..0.2.0`). The shape is
|
|
54
|
+
* stable so the same inputs always map to the same key.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} hash config content hash
|
|
57
|
+
* @param {string} cwd
|
|
58
|
+
* @param {string} kind discovery kind (+args)
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
export function cacheKey(hash, cwd, kind) {
|
|
62
|
+
return `${hash}\u0000${cwd}\u0000${kind}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Default in-memory cache. Per-instance by default (the Project creates one in
|
|
67
|
+
* {@link Project.load} when none is supplied), so it behaves like simple
|
|
68
|
+
* memoization. A shared instance can be passed to reuse results across
|
|
69
|
+
* Projects. Never busts — a content change produces a different KEY (via the
|
|
70
|
+
* hash), not an eviction, which is exactly the behavior a future file-backed
|
|
71
|
+
* cache also relies on.
|
|
72
|
+
*
|
|
73
|
+
* @implements {ConfigCache}
|
|
74
|
+
*/
|
|
75
|
+
export class InMemoryConfigCache {
|
|
76
|
+
/** @type {Map<string, unknown>} */
|
|
77
|
+
#m = new Map();
|
|
78
|
+
|
|
79
|
+
/** @param {string} key */
|
|
80
|
+
get(key) {
|
|
81
|
+
return this.#m.get(key);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} key
|
|
86
|
+
* @param {unknown} value
|
|
87
|
+
*/
|
|
88
|
+
set(key, value) {
|
|
89
|
+
this.#m.set(key, value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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 {
|
|
7
|
+
InMemoryConfigCache,
|
|
8
|
+
cacheKey,
|
|
9
|
+
configContentHash,
|
|
10
|
+
NO_CONFIG_HASH,
|
|
11
|
+
} from './config-cache.mjs';
|
|
12
|
+
|
|
13
|
+
let tmpDir;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-cache-test-'));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('InMemoryConfigCache', () => {
|
|
24
|
+
it('stores and returns values by key', () => {
|
|
25
|
+
const cache = new InMemoryConfigCache();
|
|
26
|
+
expect(cache.get('a')).toBeUndefined();
|
|
27
|
+
cache.set('a', 42);
|
|
28
|
+
expect(cache.get('a')).toBe(42);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('can store falsy values distinctly from a miss', () => {
|
|
32
|
+
const cache = new InMemoryConfigCache();
|
|
33
|
+
cache.set('zero', {value: 0});
|
|
34
|
+
expect(cache.get('zero')).toEqual({value: 0});
|
|
35
|
+
expect(cache.get('missing')).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('configContentHash', () => {
|
|
40
|
+
it('returns the sentinel when there is no config path', () => {
|
|
41
|
+
expect(configContentHash(null)).toBe(NO_CONFIG_HASH);
|
|
42
|
+
expect(configContentHash(undefined)).toBe(NO_CONFIG_HASH);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('returns the sentinel for a missing file', () => {
|
|
46
|
+
expect(configContentHash(path.join(tmpDir, 'nope.mjs'))).toBe(
|
|
47
|
+
NO_CONFIG_HASH,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('is stable for the same bytes', () => {
|
|
52
|
+
const file = path.join(tmpDir, 'astryx.config.mjs');
|
|
53
|
+
fs.writeFileSync(file, 'export default { integrations: [] };\n');
|
|
54
|
+
const a = configContentHash(file);
|
|
55
|
+
const b = configContentHash(file);
|
|
56
|
+
expect(a).toBe(b);
|
|
57
|
+
expect(a).not.toBe(NO_CONFIG_HASH);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('differs when the config content changes', () => {
|
|
61
|
+
const file = path.join(tmpDir, 'astryx.config.mjs');
|
|
62
|
+
fs.writeFileSync(file, 'export default { integrations: [] };\n');
|
|
63
|
+
const before = configContentHash(file);
|
|
64
|
+
fs.writeFileSync(file, 'export default { integrations: ["@x/y"] };\n');
|
|
65
|
+
const after = configContentHash(file);
|
|
66
|
+
expect(before).not.toBe(after);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('cacheKey', () => {
|
|
71
|
+
it('is stable for identical inputs and varies by kind', () => {
|
|
72
|
+
const k1 = cacheKey('hash', '/cwd', 'components');
|
|
73
|
+
const k2 = cacheKey('hash', '/cwd', 'components');
|
|
74
|
+
const k3 = cacheKey('hash', '/cwd', 'templates');
|
|
75
|
+
expect(k1).toBe(k2);
|
|
76
|
+
expect(k1).not.toBe(k3);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('varies by hash and cwd', () => {
|
|
80
|
+
expect(cacheKey('h1', '/cwd', 'k')).not.toBe(cacheKey('h2', '/cwd', 'k'));
|
|
81
|
+
expect(cacheKey('h', '/a', 'k')).not.toBe(cacheKey('h', '/b', 'k'));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
export const PostCodemodHookSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
name: z.string().optional(),
|
|
14
|
+
buildCommand: Fn,
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
|
|
18
|
+
export const XleComponentSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
from: z.string(),
|
|
21
|
+
description: z.string().optional(),
|
|
22
|
+
default: z.boolean().optional(),
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
|
|
26
|
+
export const AstryxConfigSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
integrations: z.array(z.string()).optional(),
|
|
29
|
+
issuesUrl: z.string().url().optional(),
|
|
30
|
+
hooks: z
|
|
31
|
+
.object({
|
|
32
|
+
postCodemod: z.array(PostCodemodHookSchema).optional(),
|
|
33
|
+
})
|
|
34
|
+
.strict()
|
|
35
|
+
.optional(),
|
|
36
|
+
experimental: z
|
|
37
|
+
.object({
|
|
38
|
+
xle: z
|
|
39
|
+
.object({
|
|
40
|
+
components: z.record(z.string(), XleComponentSchema).optional(),
|
|
41
|
+
})
|
|
42
|
+
.strict()
|
|
43
|
+
.optional(),
|
|
44
|
+
})
|
|
45
|
+
.strict()
|
|
46
|
+
.optional(),
|
|
47
|
+
})
|
|
48
|
+
.strict();
|
|
49
|
+
|
|
50
|
+
export const AstryxIntegrationSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
components: z.string().optional(),
|
|
53
|
+
templates: z.string().optional(),
|
|
54
|
+
codemods: z.string().optional(),
|
|
55
|
+
issuesUrl: z.string().url().optional(),
|
|
56
|
+
})
|
|
57
|
+
.strict();
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Format a zod error into a single readable line, mirroring the issue-joining
|
|
61
|
+
* convention used across the CLI (path: message; path: message). Exported so
|
|
62
|
+
* the shared module loader and other validators stay in lockstep.
|
|
63
|
+
* @param {string} label
|
|
64
|
+
* @param {import('zod').ZodError} error
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
export function formatZodError(label, error) {
|
|
68
|
+
const issues = error.issues
|
|
69
|
+
.map(issue => {
|
|
70
|
+
const path = issue.path.length ? issue.path.join('.') : '(root)';
|
|
71
|
+
return `${path}: ${issue.message}`;
|
|
72
|
+
})
|
|
73
|
+
.join('; ');
|
|
74
|
+
return `${label} is invalid: ${issues}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {unknown} config
|
|
79
|
+
* @returns {import('../types/config').AstryxConfig}
|
|
80
|
+
*/
|
|
81
|
+
export function validateConfig(config) {
|
|
82
|
+
const result = AstryxConfigSchema.safeParse(config);
|
|
83
|
+
if (!result.success) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
formatZodError('astryx.config default export', result.error),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return result.data;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {unknown} integration
|
|
93
|
+
* @param {string} [label]
|
|
94
|
+
* @returns {import('../types/integration').AstryxIntegration}
|
|
95
|
+
*/
|
|
96
|
+
export function validateIntegration(
|
|
97
|
+
integration,
|
|
98
|
+
label = 'integration manifest',
|
|
99
|
+
) {
|
|
100
|
+
const result = AstryxIntegrationSchema.safeParse(integration);
|
|
101
|
+
if (!result.success) {
|
|
102
|
+
throw new Error(formatZodError(label, result.error));
|
|
103
|
+
}
|
|
104
|
+
return result.data;
|
|
105
|
+
}
|
package/src/lib/error-codes.mjs
CHANGED
|
@@ -52,6 +52,9 @@
|
|
|
52
52
|
* | 'ERR_UNKNOWN_SECTION'
|
|
53
53
|
* | 'ERR_UNKNOWN_CATEGORY'
|
|
54
54
|
* | 'ERR_UNKNOWN_TEMPLATE'
|
|
55
|
+
* | 'ERR_AMBIGUOUS_TEMPLATE'
|
|
56
|
+
* | 'ERR_AMBIGUOUS_COMPONENT'
|
|
57
|
+
* | 'ERR_UNKNOWN_THEME'
|
|
55
58
|
* | 'ERR_UNKNOWN_PACKAGE'
|
|
56
59
|
* | 'ERR_UNKNOWN_AGENT'
|
|
57
60
|
* | 'ERR_UNKNOWN_FEATURE'
|
|
@@ -68,13 +71,12 @@
|
|
|
68
71
|
* | 'ERR_WRITE_FAILED'
|
|
69
72
|
* | 'ERR_THEME_INVALID'
|
|
70
73
|
* | 'ERR_THEME_LOAD'
|
|
71
|
-
* | 'ERR_TEMPLATE_CONFIG'
|
|
72
|
-
* | 'ERR_TEMPLATE_GET'
|
|
73
74
|
* | 'ERR_VERSION_DETECT'
|
|
74
75
|
* | 'ERR_INVALID_VERSION'
|
|
75
76
|
* | 'ERR_DEP_MISSING'
|
|
76
77
|
* | 'ERR_GH_CLI'
|
|
77
|
-
* | '
|
|
78
|
+
* | 'ERR_LAYOUT_PARSE'
|
|
79
|
+
* | 'ERR_LAYOUT_INVALID'
|
|
78
80
|
* )} ErrorCode
|
|
79
81
|
*/
|
|
80
82
|
|
|
@@ -122,6 +124,12 @@ export const ERROR_CODES = Object.freeze({
|
|
|
122
124
|
ERR_UNKNOWN_CATEGORY: 'ERR_UNKNOWN_CATEGORY',
|
|
123
125
|
/** No template matched the requested name. */
|
|
124
126
|
ERR_UNKNOWN_TEMPLATE: 'ERR_UNKNOWN_TEMPLATE',
|
|
127
|
+
/** A template id matched more than one template (narrow with --type/--package). */
|
|
128
|
+
ERR_AMBIGUOUS_TEMPLATE: 'ERR_AMBIGUOUS_TEMPLATE',
|
|
129
|
+
/** A component name is owned by more than one package (narrow with --package). */
|
|
130
|
+
ERR_AMBIGUOUS_COMPONENT: 'ERR_AMBIGUOUS_COMPONENT',
|
|
131
|
+
/** No theme matched the requested slug (theme add). */
|
|
132
|
+
ERR_UNKNOWN_THEME: 'ERR_UNKNOWN_THEME',
|
|
125
133
|
/** No package matched the requested name (discover). */
|
|
126
134
|
ERR_UNKNOWN_PACKAGE: 'ERR_UNKNOWN_PACKAGE',
|
|
127
135
|
/** An unrecognized `--agent` value was passed to agent-docs/init. */
|
|
@@ -161,12 +169,6 @@ export const ERROR_CODES = Object.freeze({
|
|
|
161
169
|
/** A theme file could not be loaded / parsed into a defineTheme result. */
|
|
162
170
|
ERR_THEME_LOAD: 'ERR_THEME_LOAD',
|
|
163
171
|
|
|
164
|
-
// ── Template config ──────────────────────────────────────────────
|
|
165
|
-
/** `template.get` is not configured in astryx.config.mjs (fetch-by-id). */
|
|
166
|
-
ERR_TEMPLATE_CONFIG: 'ERR_TEMPLATE_CONFIG',
|
|
167
|
-
/** A configured `template.get` threw or returned an invalid value. */
|
|
168
|
-
ERR_TEMPLATE_GET: 'ERR_TEMPLATE_GET',
|
|
169
|
-
|
|
170
172
|
// ── Upgrade ──────────────────────────────────────────────────────
|
|
171
173
|
/** The current `@astryxdesign/core` version could not be detected. */
|
|
172
174
|
ERR_VERSION_DETECT: 'ERR_VERSION_DETECT',
|
|
@@ -175,11 +177,15 @@ export const ERROR_CODES = Object.freeze({
|
|
|
175
177
|
/** A required external dependency (e.g. jscodeshift) is missing. */
|
|
176
178
|
ERR_DEP_MISSING: 'ERR_DEP_MISSING',
|
|
177
179
|
|
|
178
|
-
// ──
|
|
180
|
+
// ── GitHub CLI ───────────────────────────────────────────────────
|
|
179
181
|
/** GitHub CLI (`gh`) is not installed or not authenticated. */
|
|
180
182
|
ERR_GH_CLI: 'ERR_GH_CLI',
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
|
|
184
|
+
// ── Layout expressions (XLE/XLO) ─────────────────────────────────
|
|
185
|
+
/** A layout expression failed to parse (syntax error, with line/col). */
|
|
186
|
+
ERR_LAYOUT_PARSE: 'ERR_LAYOUT_PARSE',
|
|
187
|
+
/** A layout expression parsed but failed validation (unknown component/prop/enum/block). */
|
|
188
|
+
ERR_LAYOUT_INVALID: 'ERR_LAYOUT_INVALID',
|
|
183
189
|
});
|
|
184
190
|
|
|
185
191
|
/**
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Compact, non-blocking integration-issue nudge for everyday commands.
|
|
5
|
+
*
|
|
6
|
+
* When a CONFIGURED integration (from the Project's `loadedIntegrations`) has
|
|
7
|
+
* validation issues, the everyday commands (component / template / upgrade)
|
|
8
|
+
* should print ONE compact, non-blocking line per integration telling the user
|
|
9
|
+
* to run `validate-integration` — instead of silently skipping broken
|
|
10
|
+
* contributions or spamming per-contribution diagnostics.
|
|
11
|
+
*
|
|
12
|
+
* Design constraints (all enforced here):
|
|
13
|
+
* - Reuses the validate-integration validators (validateLoadedIntegration);
|
|
14
|
+
* no validation logic is duplicated.
|
|
15
|
+
* - Writes to STDERR only, so it never corrupts a --json stdout envelope.
|
|
16
|
+
* - Suppressed entirely in --json mode.
|
|
17
|
+
* - Best-effort: never throws, never changes the exit code. Broken
|
|
18
|
+
* contributions are still skipped downstream exactly as before; this only
|
|
19
|
+
* ADDS a one-line nudge.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {validateLoadedIntegration} from '../api/validate-integration.mjs';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* For each configured (already-loaded) integration, compute its issues using
|
|
26
|
+
* the shared validate-integration validators and, if any exist, print exactly
|
|
27
|
+
* ONE line per integration to stderr:
|
|
28
|
+
*
|
|
29
|
+
* Warning: <pkg> has N integration issue(s). Run: astryx validate-integration <pkg>
|
|
30
|
+
*
|
|
31
|
+
* @param {Array<object>} loadedIntegrations the Project's loaded integrations
|
|
32
|
+
* @param {{json?: boolean}} [options]
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
export async function warnOnIntegrationIssues(loadedIntegrations, {json = false} = {}) {
|
|
36
|
+
try {
|
|
37
|
+
// Cheap guard: only do work when integrations are actually configured.
|
|
38
|
+
if (json) return;
|
|
39
|
+
if (!Array.isArray(loadedIntegrations) || loadedIntegrations.length === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (const integration of loadedIntegrations) {
|
|
43
|
+
if (!integration || typeof integration !== 'object') continue;
|
|
44
|
+
let issues;
|
|
45
|
+
try {
|
|
46
|
+
issues = await validateLoadedIntegration(integration);
|
|
47
|
+
} catch {
|
|
48
|
+
// Best-effort: a validator throwing must not break the host command.
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(issues) || issues.length === 0) continue;
|
|
52
|
+
const pkg = integration.name ?? integration.__spec ?? '(integration)';
|
|
53
|
+
// Stderr only — keeps stdout (and any --json envelope) clean.
|
|
54
|
+
console.error(
|
|
55
|
+
`Warning: ${pkg} has ${issues.length} integration issue(s). ` +
|
|
56
|
+
`Run: astryx validate-integration ${pkg}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Never throw, never change the exit code.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Hermetic tests for the compact integration-issue nudge.
|
|
5
|
+
*
|
|
6
|
+
* Builds loaded-integration-shaped objects (as produced by
|
|
7
|
+
* lib/integrations.mjs) with absolute contribution roots under the repo root
|
|
8
|
+
* (process.cwd()) so any node_modules-style path stays within Vite's allowed
|
|
9
|
+
* fs roots. We never load configs from /tmp. The helper is exercised directly;
|
|
10
|
+
* stderr is captured to assert exactly one warning line per broken integration
|
|
11
|
+
* and zero output in --json mode / for clean integrations.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import {warnOnIntegrationIssues} from './integration-warnings.mjs';
|
|
18
|
+
|
|
19
|
+
let tmpDir;
|
|
20
|
+
let errSpy;
|
|
21
|
+
let errLines;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-warn-it-'));
|
|
25
|
+
errLines = [];
|
|
26
|
+
errSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
27
|
+
errLines.push(args.join(' '));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
errSpy.mockRestore();
|
|
33
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build a loaded-integration-shaped object. Roots are absolute (mirrors
|
|
38
|
+
* loadIntegrations' resolveRoot).
|
|
39
|
+
*/
|
|
40
|
+
function loaded({name = '@acme/widgets', components, templates, codemods} = {}) {
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
components: components == null ? undefined : path.join(tmpDir, components),
|
|
45
|
+
templates: templates == null ? undefined : path.join(tmpDir, templates),
|
|
46
|
+
codemods: codemods == null ? undefined : path.join(tmpDir, codemods),
|
|
47
|
+
__spec: name,
|
|
48
|
+
__packageDir: tmpDir,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('warnOnIntegrationIssues', () => {
|
|
53
|
+
it('emits exactly one stderr line for an integration with issues', async () => {
|
|
54
|
+
// Declared components root that does not exist on disk → missing_root.
|
|
55
|
+
const integration = loaded({components: 'does-not-exist'});
|
|
56
|
+
await warnOnIntegrationIssues([integration], {json: false});
|
|
57
|
+
|
|
58
|
+
expect(errLines).toHaveLength(1);
|
|
59
|
+
expect(errLines[0]).toBe(
|
|
60
|
+
'Warning: @acme/widgets has 1 integration issue(s). ' +
|
|
61
|
+
'Run: astryx validate-integration @acme/widgets',
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('emits nothing in --json mode (keeps stdout/JSON clean)', async () => {
|
|
66
|
+
const integration = loaded({components: 'does-not-exist'});
|
|
67
|
+
await warnOnIntegrationIssues([integration], {json: true});
|
|
68
|
+
expect(errLines).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('emits nothing for an integration with no issues', async () => {
|
|
72
|
+
// Existing components root with no broken contributions → no issues.
|
|
73
|
+
const componentsRoot = path.join(tmpDir, 'components');
|
|
74
|
+
fs.mkdirSync(componentsRoot, {recursive: true});
|
|
75
|
+
const integration = loaded({components: 'components'});
|
|
76
|
+
await warnOnIntegrationIssues([integration], {json: false});
|
|
77
|
+
expect(errLines).toHaveLength(0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('emits nothing when there are no configured integrations', async () => {
|
|
81
|
+
await warnOnIntegrationIssues([], {json: false});
|
|
82
|
+
await warnOnIntegrationIssues(undefined, {json: false});
|
|
83
|
+
expect(errLines).toHaveLength(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('emits one line per broken integration', async () => {
|
|
87
|
+
const a = loaded({name: '@acme/a', components: 'missing-a'});
|
|
88
|
+
const b = loaded({name: '@acme/b', templates: 'missing-b'});
|
|
89
|
+
await warnOnIntegrationIssues([a, b], {json: false});
|
|
90
|
+
expect(errLines).toHaveLength(2);
|
|
91
|
+
expect(errLines[0]).toContain('@acme/a');
|
|
92
|
+
expect(errLines[1]).toContain('@acme/b');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('never throws and never emits when a validator misbehaves', async () => {
|
|
96
|
+
// A non-object entry must be skipped silently.
|
|
97
|
+
await expect(
|
|
98
|
+
warnOnIntegrationIssues([null, 'nope', 42], {json: false}),
|
|
99
|
+
).resolves.toBeUndefined();
|
|
100
|
+
expect(errLines).toHaveLength(0);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Integration manifest loading for Astryx config.
|
|
5
|
+
*
|
|
6
|
+
* Integrations are PACKAGE NAMES listed in astryx.config.{ts,mjs,js}. Each
|
|
7
|
+
* package declares a single conventional root manifest sibling to its
|
|
8
|
+
* package.json — astryx.integration.{ts,mjs,js} — which contributes
|
|
9
|
+
* components/templates/codemods roots and an optional issuesUrl. Identity
|
|
10
|
+
* (name, version) comes from the package's package.json, not the manifest.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import {AstryxIntegrationSchema} from './config-schema.mjs';
|
|
16
|
+
import {loadModuleWithSchema, findPresentFiles} from './module-loader.mjs';
|
|
17
|
+
|
|
18
|
+
/** Conventional manifest basenames, in load-precedence order. */
|
|
19
|
+
export const MANIFEST_BASENAMES = [
|
|
20
|
+
'astryx.integration.ts',
|
|
21
|
+
'astryx.integration.mjs',
|
|
22
|
+
'astryx.integration.js',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Return the conventional root manifest paths present in `dir`, in
|
|
27
|
+
* load-precedence order. Unlike {@link resolveManifestPath} this never throws —
|
|
28
|
+
* callers (e.g. validate-integration) decide how to treat zero / multiple.
|
|
29
|
+
* @param {string} dir
|
|
30
|
+
* @returns {string[]} absolute manifest paths
|
|
31
|
+
*/
|
|
32
|
+
export function findManifestPaths(dir) {
|
|
33
|
+
return findPresentFiles(dir, MANIFEST_BASENAMES);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load and validate a manifest module's default export against the integration
|
|
38
|
+
* schema. Default export only — `.ts` is loaded via jiti; `.mjs`/`.js` via
|
|
39
|
+
* dynamic import. Throws if the default export is missing or invalid. Exposed
|
|
40
|
+
* for validate-integration.
|
|
41
|
+
* @param {string} file absolute manifest path
|
|
42
|
+
* @param {string} [label] used in error messages
|
|
43
|
+
* @returns {Promise<import('../types/integration').AstryxIntegration>}
|
|
44
|
+
*/
|
|
45
|
+
export async function loadManifestObject(file, label = 'integration manifest') {
|
|
46
|
+
return loadModuleWithSchema(file, AstryxIntegrationSchema, {label});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the directory of an installed integration package from the consumer's
|
|
51
|
+
* node_modules.
|
|
52
|
+
* @param {string} packageName
|
|
53
|
+
* @param {string} [cwd]
|
|
54
|
+
*/
|
|
55
|
+
export function resolvePackageDir(packageName, cwd = process.cwd()) {
|
|
56
|
+
return path.resolve(cwd, 'node_modules', ...packageName.split('/'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Find the single conventional root manifest sibling of the package's
|
|
61
|
+
* package.json. Multiple present is a hard error; missing is a hard error.
|
|
62
|
+
* @param {string} packageDir
|
|
63
|
+
* @param {string} spec
|
|
64
|
+
* @returns {string} absolute manifest path
|
|
65
|
+
*/
|
|
66
|
+
function resolveManifestPath(packageDir, spec) {
|
|
67
|
+
const present = findPresentFiles(packageDir, MANIFEST_BASENAMES);
|
|
68
|
+
if (present.length === 0) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Integration package "${spec}" has no conventional root manifest. Add one of: ${MANIFEST_BASENAMES.join(', ')} next to its package.json.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (present.length > 1) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Integration package "${spec}" has multiple root manifests (${present
|
|
76
|
+
.map(file => path.basename(file))
|
|
77
|
+
.join(', ')}). Keep exactly one.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return present[0];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Load configured integrations.
|
|
85
|
+
*
|
|
86
|
+
* @param {string[]} [specs] package names
|
|
87
|
+
* @param {{cwd?: string}} [options]
|
|
88
|
+
*/
|
|
89
|
+
export async function loadIntegrations(specs = [], {cwd = process.cwd()} = {}) {
|
|
90
|
+
const integrations = [];
|
|
91
|
+
const seen = new Set();
|
|
92
|
+
|
|
93
|
+
for (const spec of specs) {
|
|
94
|
+
if (!spec || seen.has(spec)) continue;
|
|
95
|
+
seen.add(spec);
|
|
96
|
+
|
|
97
|
+
const packageDir = resolvePackageDir(spec, cwd);
|
|
98
|
+
const pkgPath = path.join(packageDir, 'package.json');
|
|
99
|
+
let pkg;
|
|
100
|
+
try {
|
|
101
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
102
|
+
} catch {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Could not find installed integration package "${spec}" at ${pkgPath}. Install it first.`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const manifestFile = resolveManifestPath(packageDir, spec);
|
|
109
|
+
const manifest = await loadModuleWithSchema(
|
|
110
|
+
manifestFile,
|
|
111
|
+
AstryxIntegrationSchema,
|
|
112
|
+
{label: `Integration ${spec}`},
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const resolveRoot = value =>
|
|
116
|
+
value == null ? undefined : path.resolve(packageDir, value);
|
|
117
|
+
|
|
118
|
+
integrations.push({
|
|
119
|
+
name: pkg.name ?? spec,
|
|
120
|
+
version: pkg.version,
|
|
121
|
+
components: resolveRoot(manifest.components),
|
|
122
|
+
templates: resolveRoot(manifest.templates),
|
|
123
|
+
codemods: resolveRoot(manifest.codemods),
|
|
124
|
+
issuesUrl: manifest.issuesUrl,
|
|
125
|
+
__spec: spec,
|
|
126
|
+
__packageDir: packageDir,
|
|
127
|
+
__manifestFile: manifestFile,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return integrations;
|
|
132
|
+
}
|