@astryxdesign/cli 0.1.0 → 0.1.1-canary.129bf0e
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 +8 -8
- package/src/api/doctor.mjs +3 -3
- package/src/api/search.mjs +207 -13
- package/src/api/template.mjs +62 -11
- 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/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/swizzle-gap-safety.test.mjs +1 -1
- 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 +353 -169
- package/src/commands/upgrade.test.mjs +41 -27
- package/src/index.mjs +1 -0
- package/src/lib/config.mjs +12 -0
- package/src/lib/config.test.mjs +42 -0
- package/src/lib/error-codes.mjs +3 -0
- package/src/types/error-codes.d.ts +1 -0
- 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/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/MoreMenu/MoreMenuInToolbar.tsx +2 -12
- 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/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
|
@@ -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/index.mjs
CHANGED
|
@@ -249,6 +249,7 @@ const commands = [
|
|
|
249
249
|
{name: 'hook', path: './commands/hook/index.mjs', register: 'registerHook'},
|
|
250
250
|
{name: 'discover', path: './commands/discover.mjs', register: 'registerDiscover'},
|
|
251
251
|
{name: 'search', path: './commands/search.mjs', register: 'registerSearch'},
|
|
252
|
+
{name: 'build', path: './commands/build.mjs', register: 'registerBuild'},
|
|
252
253
|
{name: 'doctor', path: './commands/doctor.mjs', register: 'registerDoctor'},
|
|
253
254
|
];
|
|
254
255
|
|
package/src/lib/config.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {pathToFileURL} from 'node:url';
|
|
|
13
13
|
|
|
14
14
|
const DEFAULTS = {
|
|
15
15
|
packages: [],
|
|
16
|
+
integrations: [],
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -46,6 +47,7 @@ export async function loadConfig(startDir = process.cwd()) {
|
|
|
46
47
|
...DEFAULTS,
|
|
47
48
|
...config,
|
|
48
49
|
packages: normalizePackages(config.packages, path.dirname(configPath)),
|
|
50
|
+
integrations: normalizeIntegrations(config.integrations),
|
|
49
51
|
};
|
|
50
52
|
} catch {
|
|
51
53
|
return {...DEFAULTS};
|
|
@@ -72,3 +74,13 @@ function normalizePackages(packages, configDir) {
|
|
|
72
74
|
}
|
|
73
75
|
return result;
|
|
74
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Normalize integration specs to a string array. Integrations are package names
|
|
80
|
+
* or manifest paths consumed by `astryx upgrade --integration`.
|
|
81
|
+
*/
|
|
82
|
+
function normalizeIntegrations(integrations) {
|
|
83
|
+
if (!integrations) return [];
|
|
84
|
+
const arr = Array.isArray(integrations) ? integrations : [integrations];
|
|
85
|
+
return arr.filter(value => typeof value === 'string' && value !== '');
|
|
86
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
describe('loadConfig', () => {
|
|
19
|
+
it('normalizes integrations from astryx.config.mjs', async () => {
|
|
20
|
+
const dir = path.join(tmpDir, 'one');
|
|
21
|
+
fs.mkdirSync(dir);
|
|
22
|
+
fs.writeFileSync(
|
|
23
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
24
|
+
`export default { integrations: '@nest/xds-meta' };\n`,
|
|
25
|
+
);
|
|
26
|
+
await expect(loadConfig(dir)).resolves.toMatchObject({
|
|
27
|
+
integrations: ['@nest/xds-meta'],
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('normalizes integration arrays and filters non-strings', async () => {
|
|
32
|
+
const dir = path.join(tmpDir, 'two');
|
|
33
|
+
fs.mkdirSync(dir);
|
|
34
|
+
fs.writeFileSync(
|
|
35
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
36
|
+
`export default { integrations: ['@nest/xds-meta', '', 42] };\n`,
|
|
37
|
+
);
|
|
38
|
+
await expect(loadConfig(dir)).resolves.toMatchObject({
|
|
39
|
+
integrations: ['@nest/xds-meta'],
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
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'
|
|
@@ -129,6 +130,8 @@ export const ERROR_CODES = Object.freeze({
|
|
|
129
130
|
ERR_UNKNOWN_FEATURE: 'ERR_UNKNOWN_FEATURE',
|
|
130
131
|
/** A `--codemod` value did not match any registered codemod (upgrade). */
|
|
131
132
|
ERR_UNKNOWN_CODEMOD: 'ERR_UNKNOWN_CODEMOD',
|
|
133
|
+
/** One or more codemods failed during an upgrade run. */
|
|
134
|
+
ERR_CODEMOD_FAILED: 'ERR_CODEMOD_FAILED',
|
|
132
135
|
/** A generic discover/lookup query matched nothing in any package. */
|
|
133
136
|
ERR_NOT_FOUND: 'ERR_NOT_FOUND',
|
|
134
137
|
|
|
@@ -60,7 +60,7 @@ export function isInteractive({
|
|
|
60
60
|
*/
|
|
61
61
|
export function requireInteractive({command, hint, json = false} = {}, env) {
|
|
62
62
|
if (isInteractive(env)) return;
|
|
63
|
-
const name = command ? `
|
|
63
|
+
const name = command ? `astryx ${command}` : 'this command';
|
|
64
64
|
console.error(
|
|
65
65
|
`Error: \`${name}\` with no flags is interactive and requires a TTY.`,
|
|
66
66
|
);
|
|
@@ -64,5 +64,7 @@ describe('requireInteractive', () => {
|
|
|
64
64
|
const output = err.mock.calls.map(c => c.join(' ')).join('\n');
|
|
65
65
|
expect(output).toMatch(/requires a TTY/i);
|
|
66
66
|
expect(output).toMatch(/astryx theme <preset>/);
|
|
67
|
+
expect(output).toMatch(/`astryx theme`/);
|
|
68
|
+
expect(output).not.toMatch(/\bxds\b/);
|
|
67
69
|
});
|
|
68
70
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file Detect the project's package manager from lockfiles.
|
|
5
5
|
*
|
|
6
6
|
* Returns the correct command prefix for running package binaries
|
|
7
|
-
* (e.g. 'npx
|
|
7
|
+
* (e.g. 'npx astryx', 'yarn astryx', 'pnpm exec astryx').
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as fs from 'node:fs';
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
let tmpDir;
|
|
15
15
|
beforeEach(() => {
|
|
16
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), '
|
|
16
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-path-safety-'));
|
|
17
17
|
});
|
|
18
18
|
afterEach(() => {
|
|
19
19
|
fs.rmSync(tmpDir, {recursive: true, force: true});
|
package/src/utils/paths.test.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import {findCoreDir, findProjectRoot, listComponents, discoverExternalPackages}
|
|
|
9
9
|
let tmpDir;
|
|
10
10
|
|
|
11
11
|
function makeTmpDir() {
|
|
12
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), '
|
|
12
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-paths-test-'));
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
@@ -69,12 +69,12 @@ describe('findProjectRoot', () => {
|
|
|
69
69
|
describe('discoverExternalPackages', () => {
|
|
70
70
|
it('finds packages with an "astryx" field in package.json', () => {
|
|
71
71
|
const nm = path.join(tmpDir, 'node_modules');
|
|
72
|
-
const extDir = path.join(nm, '
|
|
72
|
+
const extDir = path.join(nm, 'astryx-charts');
|
|
73
73
|
fs.mkdirSync(extDir, {recursive: true});
|
|
74
74
|
fs.writeFileSync(
|
|
75
75
|
path.join(extDir, 'package.json'),
|
|
76
76
|
JSON.stringify({
|
|
77
|
-
name: '
|
|
77
|
+
name: 'astryx-charts',
|
|
78
78
|
astryx: {docs: './src', category: 'Data Viz'},
|
|
79
79
|
}),
|
|
80
80
|
);
|
|
@@ -82,7 +82,7 @@ describe('discoverExternalPackages', () => {
|
|
|
82
82
|
const result = discoverExternalPackages(tmpDir);
|
|
83
83
|
expect(result).toEqual([
|
|
84
84
|
{
|
|
85
|
-
name: '
|
|
85
|
+
name: 'astryx-charts',
|
|
86
86
|
category: 'Data Viz',
|
|
87
87
|
docsDir: path.join(extDir, 'src'),
|
|
88
88
|
blocksDir: null,
|
|
@@ -92,7 +92,7 @@ describe('discoverExternalPackages', () => {
|
|
|
92
92
|
|
|
93
93
|
it('handles scoped packages (@org/pkg)', () => {
|
|
94
94
|
const nm = path.join(tmpDir, 'node_modules');
|
|
95
|
-
const scopedDir = path.join(nm, '@acme', '
|
|
95
|
+
const scopedDir = path.join(nm, '@acme', 'astryx-widgets');
|
|
96
96
|
fs.mkdirSync(scopedDir, {recursive: true});
|
|
97
97
|
fs.writeFileSync(
|
|
98
98
|
path.join(scopedDir, 'package.json'),
|
|
@@ -165,12 +165,12 @@ describe('discoverExternalPackages', () => {
|
|
|
165
165
|
|
|
166
166
|
it('walks up directories to find node_modules', () => {
|
|
167
167
|
const nm = path.join(tmpDir, 'node_modules');
|
|
168
|
-
const extDir = path.join(nm, '
|
|
168
|
+
const extDir = path.join(nm, 'astryx-ext');
|
|
169
169
|
fs.mkdirSync(extDir, {recursive: true});
|
|
170
170
|
fs.writeFileSync(
|
|
171
171
|
path.join(extDir, 'package.json'),
|
|
172
172
|
JSON.stringify({
|
|
173
|
-
name: '
|
|
173
|
+
name: 'astryx-ext',
|
|
174
174
|
astryx: {docs: './src'},
|
|
175
175
|
}),
|
|
176
176
|
);
|
|
@@ -180,7 +180,7 @@ describe('discoverExternalPackages', () => {
|
|
|
180
180
|
|
|
181
181
|
const result = discoverExternalPackages(nested);
|
|
182
182
|
expect(result).toHaveLength(1);
|
|
183
|
-
expect(result[0].name).toBe('
|
|
183
|
+
expect(result[0].name).toBe('astryx-ext');
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
it('handles invalid JSON in package.json gracefully', () => {
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* @file Check for newer @astryxdesign/core versions via local signals.
|
|
5
5
|
*
|
|
6
6
|
* Resolution order:
|
|
7
|
-
* 1. $ASTRYX_LATEST_VERSION env var
|
|
8
|
-
* 2.
|
|
9
|
-
* 3. If neither: no hint (no network calls from this module)
|
|
7
|
+
* 1. $ASTRYX_LATEST_VERSION env var
|
|
8
|
+
* 2. If unset: no hint (no network calls from this module)
|
|
10
9
|
*
|
|
11
10
|
* When a newer version is detected, returns a hint string for CLI output.
|
|
12
11
|
* Also sets $ASTRYX_LATEST_VERSION for subsequent commands in the same shell.
|
|
@@ -23,34 +22,13 @@ import {semverGt} from './semver.mjs';
|
|
|
23
22
|
* @param {string} [cwd] - Project directory (default: process.cwd())
|
|
24
23
|
* @returns {string|null} Latest version string, or null if unknown
|
|
25
24
|
*/
|
|
26
|
-
export function getLatestVersion(
|
|
25
|
+
export function getLatestVersion() {
|
|
27
26
|
// 1. Check env var (fastest — set by previous CLI run)
|
|
28
27
|
const envVersion = process.env.ASTRYX_LATEST_VERSION;
|
|
29
28
|
if (envVersion && /^\d+\.\d+\.\d+/.test(envVersion)) {
|
|
30
29
|
return envVersion;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
// 2. Check versionFile from package.json config
|
|
34
|
-
try {
|
|
35
|
-
const pkgPath = path.resolve(cwd, 'package.json');
|
|
36
|
-
if (fs.existsSync(pkgPath)) {
|
|
37
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
38
|
-
const versionFile = pkg.astryx?.versionFile;
|
|
39
|
-
|
|
40
|
-
if (versionFile) {
|
|
41
|
-
const filePath = path.resolve(cwd, versionFile);
|
|
42
|
-
if (fs.existsSync(filePath)) {
|
|
43
|
-
const version = fs.readFileSync(filePath, 'utf-8').trim();
|
|
44
|
-
if (/^\d+\.\d+\.\d+/.test(version)) {
|
|
45
|
-
return version;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
// Silently fail — version check should never break the CLI
|
|
52
|
-
}
|
|
53
|
-
|
|
54
32
|
return null;
|
|
55
33
|
}
|
|
56
34
|
|
|
@@ -97,7 +75,7 @@ export function checkForUpdate(cwd = process.cwd()) {
|
|
|
97
75
|
// Use semver-aware comparison so '0.0.20' is correctly treated as greater
|
|
98
76
|
// than '0.0.5' (lexicographic compare gets that backwards).
|
|
99
77
|
if (semverGt(latest, installed)) {
|
|
100
|
-
return `FYI: A newer version of @astryxdesign/core (${latest}) is available.
|
|
78
|
+
return `FYI: A newer version of @astryxdesign/core (${latest}) is available. Install the new package version, then run: astryx upgrade --from <old-version> --apply`;
|
|
101
79
|
}
|
|
102
80
|
|
|
103
81
|
return null;
|
|
@@ -14,7 +14,7 @@ let tmpDir;
|
|
|
14
14
|
const originalEnv = {};
|
|
15
15
|
|
|
16
16
|
beforeEach(() => {
|
|
17
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), '
|
|
17
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-update-check-'));
|
|
18
18
|
// Save and clear env
|
|
19
19
|
originalEnv.ASTRYX_LATEST_VERSION = process.env.ASTRYX_LATEST_VERSION;
|
|
20
20
|
delete process.env.ASTRYX_LATEST_VERSION;
|
|
@@ -41,17 +41,6 @@ describe('getLatestVersion', () => {
|
|
|
41
41
|
expect(getLatestVersion(tmpDir)).toBeNull();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
it('reads from versionFile in package.json', () => {
|
|
45
|
-
const versionFilePath = path.join(tmpDir, 'LATEST_VERSION');
|
|
46
|
-
fs.writeFileSync(versionFilePath, '0.0.9\n');
|
|
47
|
-
fs.writeFileSync(
|
|
48
|
-
path.join(tmpDir, 'package.json'),
|
|
49
|
-
JSON.stringify({astryx: {versionFile: './LATEST_VERSION'}}),
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
expect(getLatestVersion(tmpDir)).toBe('0.0.9');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
44
|
it('returns null when no signals exist', () => {
|
|
56
45
|
fs.writeFileSync(
|
|
57
46
|
path.join(tmpDir, 'package.json'),
|
|
@@ -59,26 +48,6 @@ describe('getLatestVersion', () => {
|
|
|
59
48
|
);
|
|
60
49
|
expect(getLatestVersion(tmpDir)).toBeNull();
|
|
61
50
|
});
|
|
62
|
-
|
|
63
|
-
it('returns null when versionFile path does not exist', () => {
|
|
64
|
-
fs.writeFileSync(
|
|
65
|
-
path.join(tmpDir, 'package.json'),
|
|
66
|
-
JSON.stringify({astryx: {versionFile: './missing/LATEST_VERSION'}}),
|
|
67
|
-
);
|
|
68
|
-
expect(getLatestVersion(tmpDir)).toBeNull();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('env var takes priority over versionFile', () => {
|
|
72
|
-
process.env.ASTRYX_LATEST_VERSION = '1.0.0';
|
|
73
|
-
const versionFilePath = path.join(tmpDir, 'LATEST_VERSION');
|
|
74
|
-
fs.writeFileSync(versionFilePath, '0.0.9\n');
|
|
75
|
-
fs.writeFileSync(
|
|
76
|
-
path.join(tmpDir, 'package.json'),
|
|
77
|
-
JSON.stringify({astryx: {versionFile: './LATEST_VERSION'}}),
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
expect(getLatestVersion(tmpDir)).toBe('1.0.0');
|
|
81
|
-
});
|
|
82
51
|
});
|
|
83
52
|
|
|
84
53
|
describe('getInstalledVersion', () => {
|
|
@@ -117,7 +86,7 @@ describe('checkForUpdate', () => {
|
|
|
117
86
|
|
|
118
87
|
const hint = checkForUpdate(tmpDir);
|
|
119
88
|
expect(hint).toContain('0.0.8');
|
|
120
|
-
expect(hint).toContain('astryx upgrade --
|
|
89
|
+
expect(hint).toContain('astryx upgrade --from <old-version> --apply');
|
|
121
90
|
expect(hint).toContain('FYI');
|
|
122
91
|
});
|
|
123
92
|
|
|
@@ -140,37 +109,6 @@ describe('checkForUpdate', () => {
|
|
|
140
109
|
expect(checkForUpdate(tmpDir)).toBeNull();
|
|
141
110
|
});
|
|
142
111
|
|
|
143
|
-
it('sets env var for subsequent calls', () => {
|
|
144
|
-
const versionFilePath = path.join(tmpDir, 'LATEST_VERSION');
|
|
145
|
-
fs.writeFileSync(versionFilePath, '0.0.8\n');
|
|
146
|
-
fs.writeFileSync(
|
|
147
|
-
path.join(tmpDir, 'package.json'),
|
|
148
|
-
JSON.stringify({
|
|
149
|
-
dependencies: {'@astryxdesign/core': '^0.0.7'},
|
|
150
|
-
astryx: {versionFile: './LATEST_VERSION'},
|
|
151
|
-
}),
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
checkForUpdate(tmpDir);
|
|
155
|
-
expect(process.env.ASTRYX_LATEST_VERSION).toBe('0.0.8');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('reads from versionFile and produces hint', () => {
|
|
159
|
-
const versionFilePath = path.join(tmpDir, 'LATEST_VERSION');
|
|
160
|
-
fs.writeFileSync(versionFilePath, '0.0.9\n');
|
|
161
|
-
fs.writeFileSync(
|
|
162
|
-
path.join(tmpDir, 'package.json'),
|
|
163
|
-
JSON.stringify({
|
|
164
|
-
dependencies: {'@astryxdesign/core': '^0.0.7'},
|
|
165
|
-
astryx: {versionFile: './LATEST_VERSION'},
|
|
166
|
-
}),
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
const hint = checkForUpdate(tmpDir);
|
|
170
|
-
expect(hint).toContain('0.0.9');
|
|
171
|
-
expect(hint).toContain('FYI');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
112
|
it('uses semver (not lexicographic) comparison for double-digit patches', () => {
|
|
175
113
|
// Regression: with string compare, '0.0.20' > '0.0.5' is false because
|
|
176
114
|
// '2' < '5' lexicographically. Users on 0.0.5 would never be told that
|
|
@@ -5,18 +5,10 @@
|
|
|
5
5
|
import {AppShell} from '@astryxdesign/core/AppShell';
|
|
6
6
|
import {VStack} from '@astryxdesign/core/Stack';
|
|
7
7
|
import {Heading, Text} from '@astryxdesign/core/Text';
|
|
8
|
-
import stylex from '@stylexjs/stylex';
|
|
9
|
-
|
|
10
|
-
const styles = stylex.create({
|
|
11
|
-
fit: {
|
|
12
|
-
height: '100%',
|
|
13
|
-
minHeight: 0,
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
8
|
|
|
17
9
|
export default function AppShellContentOnly() {
|
|
18
10
|
return (
|
|
19
|
-
<AppShell contentPadding={6}
|
|
11
|
+
<AppShell contentPadding={6} style={{height: '100%', minHeight: 0}}>
|
|
20
12
|
<VStack gap={4}>
|
|
21
13
|
<Heading level={3}>Page Content</Heading>
|
|
22
14
|
<Text type="body">
|
|
@@ -19,21 +19,12 @@ import {
|
|
|
19
19
|
} from '@heroicons/react/24/outline';
|
|
20
20
|
import {HomeIcon} from '@heroicons/react/24/solid';
|
|
21
21
|
import {CubeIcon} from '@heroicons/react/24/outline';
|
|
22
|
-
import stylex from '@stylexjs/stylex';
|
|
23
|
-
|
|
24
|
-
const styles = stylex.create({
|
|
25
|
-
fit: {
|
|
26
|
-
height: '100%',
|
|
27
|
-
minHeight: 0,
|
|
28
|
-
width: '100%',
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
22
|
|
|
32
23
|
export default function AppShellShowcase() {
|
|
33
24
|
return (
|
|
34
25
|
<AppShell
|
|
35
26
|
contentPadding={6}
|
|
36
|
-
|
|
27
|
+
style={{height: '100%', minHeight: 0, width: '100%'}}
|
|
37
28
|
sideNav={
|
|
38
29
|
<SideNav
|
|
39
30
|
header={
|
|
@@ -20,20 +20,12 @@ import {
|
|
|
20
20
|
} from '@heroicons/react/24/outline';
|
|
21
21
|
import {HomeIcon} from '@heroicons/react/24/solid';
|
|
22
22
|
import {CubeIcon} from '@heroicons/react/24/outline';
|
|
23
|
-
import stylex from '@stylexjs/stylex';
|
|
24
|
-
|
|
25
|
-
const styles = stylex.create({
|
|
26
|
-
fit: {
|
|
27
|
-
height: '100%',
|
|
28
|
-
minHeight: 0,
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
23
|
|
|
32
24
|
export default function AppShellSideNavOnly() {
|
|
33
25
|
return (
|
|
34
26
|
<AppShell
|
|
35
27
|
contentPadding={6}
|
|
36
|
-
|
|
28
|
+
style={{height: '100%', minHeight: 0}}
|
|
37
29
|
sideNav={
|
|
38
30
|
<SideNav
|
|
39
31
|
header={
|
|
@@ -8,20 +8,12 @@ import {Heading, Text} from '@astryxdesign/core/Text';
|
|
|
8
8
|
import {TopNav, TopNavHeading, TopNavItem} from '@astryxdesign/core/TopNav';
|
|
9
9
|
import {NavIcon} from '@astryxdesign/core/NavIcon';
|
|
10
10
|
import {CubeIcon} from '@heroicons/react/24/outline';
|
|
11
|
-
import stylex from '@stylexjs/stylex';
|
|
12
|
-
|
|
13
|
-
const styles = stylex.create({
|
|
14
|
-
fit: {
|
|
15
|
-
height: '100%',
|
|
16
|
-
minHeight: 0,
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
11
|
|
|
20
12
|
export default function AppShellTopNavOnly() {
|
|
21
13
|
return (
|
|
22
14
|
<AppShell
|
|
23
15
|
contentPadding={6}
|
|
24
|
-
|
|
16
|
+
style={{height: '100%', minHeight: 0}}
|
|
25
17
|
topNav={
|
|
26
18
|
<TopNav
|
|
27
19
|
label="Main navigation"
|
|
@@ -16,20 +16,12 @@ import {
|
|
|
16
16
|
} from '@heroicons/react/24/outline';
|
|
17
17
|
import {HomeIcon} from '@heroicons/react/24/solid';
|
|
18
18
|
import {CubeIcon} from '@heroicons/react/24/outline';
|
|
19
|
-
import stylex from '@stylexjs/stylex';
|
|
20
|
-
|
|
21
|
-
const styles = stylex.create({
|
|
22
|
-
fit: {
|
|
23
|
-
height: '100%',
|
|
24
|
-
minHeight: 0,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
19
|
|
|
28
20
|
export default function AppShellTopNavWithSideNav() {
|
|
29
21
|
return (
|
|
30
22
|
<AppShell
|
|
31
23
|
contentPadding={6}
|
|
32
|
-
|
|
24
|
+
style={{height: '100%', minHeight: 0}}
|
|
33
25
|
topNav={
|
|
34
26
|
<TopNav
|
|
35
27
|
label="Main navigation"
|
|
@@ -17,20 +17,12 @@ import {
|
|
|
17
17
|
} from '@heroicons/react/24/outline';
|
|
18
18
|
import {HomeIcon} from '@heroicons/react/24/solid';
|
|
19
19
|
import {CubeIcon} from '@heroicons/react/24/outline';
|
|
20
|
-
import stylex from '@stylexjs/stylex';
|
|
21
|
-
|
|
22
|
-
const styles = stylex.create({
|
|
23
|
-
fit: {
|
|
24
|
-
height: '100%',
|
|
25
|
-
minHeight: 0,
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
20
|
|
|
29
21
|
export default function AppShellWithBanner() {
|
|
30
22
|
return (
|
|
31
23
|
<AppShell
|
|
32
24
|
contentPadding={6}
|
|
33
|
-
|
|
25
|
+
style={{height: '100%', minHeight: 0}}
|
|
34
26
|
topNav={
|
|
35
27
|
<TopNav
|
|
36
28
|
label="Main navigation"
|