@astryxdesign/cli 0.1.0 → 0.1.1-canary.13763f6

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.
Files changed (141) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +117 -75
  3. package/bin/astryx.mjs +22 -7
  4. package/docs/getting-started.doc.mjs +11 -11
  5. package/docs/icons.doc.mjs +1 -1
  6. package/docs/migration.doc.mjs +2 -2
  7. package/docs/shape.doc.mjs +1 -1
  8. package/docs/styling.doc.mjs +3 -4
  9. package/docs/theme.doc.dense.mjs +2 -2
  10. package/docs/theme.doc.mjs +14 -0
  11. package/docs/theme.doc.zh.mjs +2 -2
  12. package/docs/working-with-ai.doc.mjs +4 -4
  13. package/package.json +8 -8
  14. package/src/api/doctor.mjs +3 -3
  15. package/src/api/search.mjs +207 -13
  16. package/src/api/template.mjs +62 -11
  17. package/src/api/template.test.mjs +2 -0
  18. package/src/codemods/__tests__/registry.test.mjs +1 -0
  19. package/src/codemods/registry.mjs +1 -0
  20. package/src/codemods/runner.mjs +105 -51
  21. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +116 -0
  22. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +51 -0
  23. package/src/codemods/transforms/v0.1.0/index.mjs +28 -0
  24. package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +230 -0
  25. package/src/codemods/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +84 -0
  26. package/src/commands/agent-docs.mjs +119 -66
  27. package/src/commands/agent-docs.path-safety.test.mjs +1 -1
  28. package/src/commands/agent-docs.test.mjs +87 -31
  29. package/src/commands/build-theme.import-path.test.mjs +1 -1
  30. package/src/commands/build-theme.path-safety.test.mjs +1 -1
  31. package/src/commands/build-theme.prose.test.mjs +1 -1
  32. package/src/commands/build.mjs +196 -0
  33. package/src/commands/component-package.test.mjs +1 -1
  34. package/src/commands/component.test.mjs +1 -1
  35. package/src/commands/docs.test.mjs +1 -1
  36. package/src/commands/doctor.test.mjs +1 -1
  37. package/src/commands/external-showcase.test.mjs +1 -1
  38. package/src/commands/init.mjs +10 -2
  39. package/src/commands/interactive-guard.test.mjs +1 -1
  40. package/src/commands/json-contract.test.mjs +10 -3
  41. package/src/commands/swizzle-gap-safety.test.mjs +1 -1
  42. package/src/commands/swizzle.path-safety.test.mjs +1 -1
  43. package/src/commands/template.path-safety.test.mjs +1 -1
  44. package/src/commands/template.test.mjs +1 -1
  45. package/src/commands/upgrade.mjs +353 -169
  46. package/src/commands/upgrade.test.mjs +41 -27
  47. package/src/index.mjs +1 -0
  48. package/src/lib/config.mjs +12 -0
  49. package/src/lib/config.test.mjs +42 -0
  50. package/src/lib/error-codes.mjs +3 -0
  51. package/src/types/error-codes.d.ts +1 -0
  52. package/src/utils/interactive.mjs +1 -1
  53. package/src/utils/interactive.test.mjs +2 -0
  54. package/src/utils/package-manager.mjs +1 -1
  55. package/src/utils/package-manager.test.mjs +1 -1
  56. package/src/utils/path-safety.test.mjs +1 -1
  57. package/src/utils/paths.test.mjs +8 -8
  58. package/src/utils/update-check.mjs +4 -26
  59. package/src/utils/update-check.test.mjs +2 -64
  60. package/templates/blocks/components/AppShell/AppShellContentOnly.tsx +1 -9
  61. package/templates/blocks/components/AppShell/AppShellShowcase.tsx +1 -10
  62. package/templates/blocks/components/AppShell/AppShellSideNavOnly.tsx +1 -9
  63. package/templates/blocks/components/AppShell/AppShellTopNavOnly.tsx +1 -9
  64. package/templates/blocks/components/AppShell/AppShellTopNavWithSideNav.tsx +1 -9
  65. package/templates/blocks/components/AppShell/AppShellWithBanner.tsx +1 -9
  66. package/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +12 -19
  67. package/templates/blocks/components/Banner/BannerShowcase.tsx +1 -8
  68. package/templates/blocks/components/Blockquote/BlockquoteShowcase.tsx +1 -8
  69. package/templates/blocks/components/Carousel/CarouselShowcase.tsx +2 -12
  70. package/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerShowcase.tsx +6 -9
  71. package/templates/blocks/components/ChatLayout/ChatLayoutPanelChat.tsx +10 -12
  72. package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +1 -9
  73. package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -9
  74. package/templates/blocks/components/ChatMessageList/ChatMessageListShowcase.tsx +1 -9
  75. package/templates/blocks/components/ChatMessageMetadata/ChatMessageMetadataShowcase.tsx +1 -8
  76. package/templates/blocks/components/ChatSendButton/ChatSendButtonInComposer.tsx +1 -8
  77. package/templates/blocks/components/Citation/CitationInlineText.tsx +4 -4
  78. package/templates/blocks/components/Code/CodeInlineInParagraph.tsx +1 -8
  79. package/templates/blocks/components/CodeBlock/CodeBlockBashCommand.tsx +1 -1
  80. package/templates/blocks/components/CodeBlock/CodeBlockJSONConfig.tsx +1 -1
  81. package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemShowcase.tsx +9 -12
  82. package/templates/blocks/components/ContextMenu/ContextMenuShowcase.tsx +13 -15
  83. package/templates/blocks/components/Divider/DividerShowcase.tsx +1 -8
  84. package/templates/blocks/components/Divider/DividerVertical.tsx +7 -9
  85. package/templates/blocks/components/Field/FieldShowcase.tsx +1 -8
  86. package/templates/blocks/components/FormLayout/FormLayoutHorizontal.tsx +1 -6
  87. package/templates/blocks/components/Grid/GridResponsiveAutoFit.tsx +1 -9
  88. package/templates/blocks/components/HoverCard/HoverCardInlineTextHoverCard.tsx +4 -6
  89. package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +1 -6
  90. package/templates/blocks/components/HoverCard/HoverCardProfileHoverCard.tsx +2 -8
  91. package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -8
  92. package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.tsx +2 -12
  93. package/templates/blocks/components/OverflowList/OverflowListOverflowBadges.tsx +8 -11
  94. package/templates/blocks/components/OverflowList/OverflowListOverflowDropdownActions.tsx +9 -12
  95. package/templates/blocks/components/Overlay/OverlayBottomStrip.tsx +4 -17
  96. package/templates/blocks/components/Overlay/OverlayHoverReveal.tsx +15 -16
  97. package/templates/blocks/components/Overlay/OverlayShowcase.tsx +5 -21
  98. package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -14
  99. package/templates/blocks/components/Pagination/PaginationPageSize.tsx +12 -14
  100. package/templates/blocks/components/Pagination/PaginationVariants.tsx +1 -8
  101. package/templates/blocks/components/Pagination/PaginationWithTable.tsx +2 -14
  102. package/templates/blocks/components/Tokenizer/TokenizerClear.tsx +1 -6
  103. package/templates/blocks/components/Tokenizer/TokenizerCreatable.tsx +2 -7
  104. package/templates/blocks/components/Tokenizer/TokenizerEndContent.tsx +1 -6
  105. package/templates/blocks/components/Tokenizer/TokenizerIcon.tsx +1 -6
  106. package/templates/blocks/components/Tokenizer/TokenizerMaxEntries.tsx +1 -6
  107. package/templates/blocks/components/Tokenizer/TokenizerOverflow.tsx +2 -7
  108. package/templates/blocks/components/Tokenizer/TokenizerShowcase.tsx +1 -6
  109. package/templates/blocks/components/Tokenizer/TokenizerStates.tsx +4 -9
  110. package/templates/blocks/components/Toolbar/ToolbarCardHeader.tsx +1 -10
  111. package/templates/blocks/components/Toolbar/ToolbarSizes.tsx +1 -8
  112. package/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +1 -8
  113. package/templates/blocks/components/Toolbar/ToolbarThreeSlot.tsx +1 -10
  114. package/templates/blocks/components/Toolbar/ToolbarWithTabs.tsx +8 -11
  115. package/templates/pages/ai-chat/page.tsx +71 -64
  116. package/templates/pages/ai-chat-landing/page.tsx +8 -12
  117. package/templates/pages/centered-hero/page.tsx +13 -15
  118. package/templates/pages/classic-gallery/page.tsx +27 -34
  119. package/templates/pages/detail-page/page.tsx +18 -18
  120. package/templates/pages/documentation/page.tsx +42 -58
  121. package/templates/pages/documentation-design/page.tsx +82 -60
  122. package/templates/pages/documentation-technical/page.tsx +101 -60
  123. package/templates/pages/editor/page.tsx +42 -54
  124. package/templates/pages/file-explorer/page.tsx +13 -16
  125. package/templates/pages/form-two-column/page.tsx +13 -17
  126. package/templates/pages/gallery-hero/page.tsx +13 -15
  127. package/templates/pages/ide/page.tsx +188 -264
  128. package/templates/pages/library/page.tsx +16 -23
  129. package/templates/pages/login/page.tsx +14 -18
  130. package/templates/pages/login-card/page.tsx +14 -18
  131. package/templates/pages/login-split/page.tsx +50 -48
  132. package/templates/pages/login-sso/page.tsx +9 -13
  133. package/templates/pages/mixed-gallery/page.tsx +51 -45
  134. package/templates/pages/payment-form/page.tsx +56 -70
  135. package/templates/pages/product-detail/page.tsx +27 -33
  136. package/templates/pages/product-gallery/page.tsx +7 -13
  137. package/templates/pages/settings-dialog/page.tsx +35 -43
  138. package/templates/pages/settings-sidebar/page.tsx +39 -47
  139. package/templates/pages/side-gallery/page.tsx +6 -9
  140. package/templates/pages/table-grouped/page.tsx +11 -15
  141. 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(), 'xds-upgrade-test-'));
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({'@astryxdesign/core': '^0.0.9'});
103
+ writePkg();
104
+ writeInstalledCore('0.0.10');
105
+ writeSourceFile();
90
106
 
91
- const result = await runJson(['--json', 'upgrade', '--to', '0.0.10', '--codemod-only']);
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 current >= target by semver (e.g. 0.0.10 → 0.0.9)', async () => {
101
- writePkg({'@astryxdesign/core': '^0.0.10'});
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', '--to', '0.0.9']);
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 --to validation', () => {
110
- it('rejects bogus --to values with a structured error', async () => {
111
- writePkg({'@astryxdesign/core': '^0.0.5'});
112
- const result = await runJson(['--json', 'upgrade', '--to', 'bogus']);
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(/Invalid --to/);
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({'@astryxdesign/core': '^0.0.5'});
120
- const result = await runJson(['--json', 'upgrade', '--from', 'not-a-version', '--to', '0.0.5']);
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('accepts a valid semver --to', async () => {
127
- writePkg({'@astryxdesign/core': '^0.0.5'});
128
- // Don't actually run codemods — codemod-only + a target with no
129
- // matching transforms is enough to confirm validation passed.
130
- const result = await runJson(['--json', 'upgrade', '--to', latestVersion, '--codemod-only']);
131
- // No "Invalid --to" error means validation passed.
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
 
@@ -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
+ });
@@ -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
 
@@ -29,6 +29,7 @@ export type ErrorCode =
29
29
  | 'ERR_UNKNOWN_AGENT'
30
30
  | 'ERR_UNKNOWN_FEATURE'
31
31
  | 'ERR_UNKNOWN_CODEMOD'
32
+ | 'ERR_CODEMOD_FAILED'
32
33
  | 'ERR_NOT_FOUND'
33
34
  | 'ERR_NO_DOC'
34
35
  | 'ERR_NO_SHOWCASE'
@@ -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 ? `xds ${command}` : 'this 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 xds', 'yarn xds', 'pnpm exec xds').
7
+ * (e.g. 'npx astryx', 'yarn astryx', 'pnpm exec astryx').
8
8
  */
9
9
 
10
10
  import * as fs from 'node:fs';
@@ -18,7 +18,7 @@ afterEach(() => {
18
18
  });
19
19
 
20
20
  function makeTmpDir() {
21
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'xds-pm-test-'));
21
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-pm-test-'));
22
22
  return tmpDir;
23
23
  }
24
24
 
@@ -13,7 +13,7 @@ import {
13
13
 
14
14
  let tmpDir;
15
15
  beforeEach(() => {
16
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'xds-path-safety-'));
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});
@@ -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(), 'xds-paths-test-'));
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, 'xds-charts');
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: 'xds-charts',
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: 'xds-charts',
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', 'xds-widgets');
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, 'xds-ext');
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: 'xds-ext',
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('xds-ext');
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 (set by previous CLI invocation)
8
- * 2. xds.versionFile in consumer's package.json (e.g. ../../libs/xds-common/LATEST_VERSION)
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(cwd = process.cwd()) {
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. You can upgrade with: astryx upgrade --apply --to ${latest}`;
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(), 'xds-update-check-'));
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 --apply --to 0.0.8');
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} xstyle={styles.fit}>
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
- xstyle={styles.fit}
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
- xstyle={styles.fit}
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
- xstyle={styles.fit}
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
- xstyle={styles.fit}
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
- xstyle={styles.fit}
25
+ style={{height: '100%', minHeight: 0}}
34
26
  topNav={
35
27
  <TopNav
36
28
  label="Main navigation"