@astryxdesign/cli 0.1.0-canary.ec1f71f → 0.1.0-canary.f54a06f

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.
@@ -5,8 +5,8 @@
5
5
  export const docsDense = {
6
6
  description: 'Theme provider, custom themes, light/dark, component overrides',
7
7
  sections: [
8
- { title: 'Quick Start', content: [null, null, null, null, { type: 'prose', text: 'default import = runtime injection. /built import = pre-compiled CSS (pair with theme.css).' }] },
9
- { title: 'Themes', content: [null, null, { type: 'prose', text: 'published: neutral (start here), butter, chocolate, gothic (dark-only), matcha, stone, y2k. @astryxdesign/theme-{name} = source (runtime). @astryxdesign/theme-{name}/built = optimized (+ theme.css).' }] },
8
+ { title: 'Quick Start', content: [null, null, { type: 'prose', text: 'default import = runtime injection. /built import = pre-compiled CSS (pair with theme.css).' }] },
9
+ { title: 'Themes', content: [null, { type: 'prose', text: 'published: neutral (start here), butter, chocolate, gothic (dark-only), matcha, stone, y2k. @astryxdesign/theme-{name} = source (runtime). @astryxdesign/theme-{name}/built = optimized (+ theme.css).' }] },
10
10
  { title: 'Props', content: [null] },
11
11
  { title: 'Custom Theme', content: [{ type: 'prose', text: 'CLI wizard or manual defineTheme. only override tokens that differ.' }, null] },
12
12
  { title: 'defineTheme', content: [{ type: 'prose', text: 'scale configs (color, typography, radius, motion) + explicit token overrides + component overrides. color derives full palette from accent hex via HCT.' }, null, null] },
@@ -14,12 +14,6 @@ export const docs = {
14
14
  title: 'Quick Start',
15
15
  category: 'guide',
16
16
  content: [
17
- {
18
- type: 'code',
19
- lang: 'bash',
20
- label: 'Install a theme package',
21
- code: 'npm install @astryxdesign/theme-neutral',
22
- },
23
17
  {
24
18
  type: 'code',
25
19
  lang: 'tsx',
@@ -51,10 +45,6 @@ function App() {
51
45
  );
52
46
  }`,
53
47
  },
54
- {
55
- type: 'prose',
56
- text: 'Each theme ships as its own npm package. Install the one you want, then wrap your app in `<Theme>` — the same pattern works for every theme; just swap the package and import name.',
57
- },
58
48
  {
59
49
  type: 'prose',
60
50
  text: 'The default import uses runtime style injection, which works everywhere with no build step. The `/built` import skips injection and relies on the pre-compiled CSS file for better performance and SSR support.',
@@ -65,10 +55,6 @@ function App() {
65
55
  title: 'Available Themes',
66
56
  category: 'guide',
67
57
  content: [
68
- {
69
- type: 'prose',
70
- text: 'Install the theme package you want with `npm install @astryxdesign/theme-{name}`, then import its theme object as shown below.',
71
- },
72
58
  {
73
59
  type: 'table',
74
60
  headers: ['Theme', 'Import', 'Description'],
@@ -5,8 +5,8 @@
5
5
  export const docsZh = {
6
6
  description: 'Theme 提供者、自定义主题、亮/暗模式和组件样式覆盖。',
7
7
  sections: [
8
- { title: '快速开始', content: [null, null, null, null, { type: 'prose', text: '默认导入使用运行时样式注入。/built 导入使用预编译 CSS(需配合 theme.css)。' }] },
9
- { title: '可用主题', content: [null, null, { type: 'prose', text: '已发布主题:neutral(推荐起点)、butter、chocolate、gothic(仅暗色)、matcha、stone、y2k。@astryxdesign/theme-{name} = 源码版(运行时注入)。@astryxdesign/theme-{name}/built = 优化版(配合 theme.css)。' }] },
8
+ { title: '快速开始', content: [null, null, { type: 'prose', text: '默认导入使用运行时样式注入。/built 导入使用预编译 CSS(需配合 theme.css)。' }] },
9
+ { title: '可用主题', content: [null, { type: 'prose', text: '已发布主题:neutral(推荐起点)、butter、chocolate、gothic(仅暗色)、matcha、stone、y2k。@astryxdesign/theme-{name} = 源码版(运行时注入)。@astryxdesign/theme-{name}/built = 优化版(配合 theme.css)。' }] },
10
10
  { title: 'Theme 属性', content: [null] },
11
11
  { title: '创建自定义主题', content: [{ type: 'prose', text: '使用 CLI 向导(推荐)或手动 defineTheme。只覆盖与默认值不同的令牌。' }, null] },
12
12
  { title: 'defineTheme', content: [{ type: 'prose', text: '支持比例配置(typography、radius、motion)+ 显式令牌覆盖 + 组件覆盖。' }, null, null] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.1.0-canary.ec1f71f",
3
+ "version": "0.1.0-canary.f54a06f",
4
4
  "displayName": "CLI",
5
5
  "description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
6
6
  "author": "Meta Open Source",
@@ -15,7 +15,7 @@
15
15
  "url": "https://github.com/facebook/astryx/issues"
16
16
  },
17
17
  "keywords": [
18
- "astryx",
18
+ "xds",
19
19
  "cli",
20
20
  "design-system",
21
21
  "init",
@@ -54,9 +54,9 @@
54
54
  "jscodeshift": "^17.3.0"
55
55
  },
56
56
  "peerDependencies": {
57
- "@astryxdesign/core": "0.1.0-canary.ec1f71f",
58
- "@astryxdesign/lab": "0.1.0-canary.ec1f71f",
59
- "@astryxdesign/theme-neutral": "0.1.0-canary.ec1f71f"
57
+ "@astryxdesign/core": "0.1.0-canary.f54a06f",
58
+ "@astryxdesign/lab": "0.1.0-canary.f54a06f",
59
+ "@astryxdesign/theme-neutral": "0.1.0-canary.f54a06f"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "@astryxdesign/core": {
@@ -70,9 +70,9 @@
70
70
  }
71
71
  },
72
72
  "devDependencies": {
73
- "@astryxdesign/core": "0.1.0-canary.ec1f71f",
74
- "@astryxdesign/lab": "0.1.0-canary.ec1f71f",
75
- "@astryxdesign/theme-neutral": "0.1.0-canary.ec1f71f"
73
+ "@astryxdesign/core": "0.1.0-canary.f54a06f",
74
+ "@astryxdesign/lab": "0.1.0-canary.f54a06f",
75
+ "@astryxdesign/theme-neutral": "0.1.0-canary.f54a06f"
76
76
  },
77
77
  "scripts": {
78
78
  "astryx": "node bin/astryx.mjs",
@@ -93,55 +93,14 @@ export function resolveAgentPaths(targetDir, agent) {
93
93
  return {inject: [], create: [searchPaths[searchPaths.length - 1]]};
94
94
  }
95
95
 
96
- /**
97
- * Detect which styling system the consumer project has wired up, so the agent
98
- * docs recommend a path that actually compiles in THIS project.
99
- *
100
- * `xstyle`/StyleX needs the StyleX compiler (the `@stylexjs/stylex` runtime
101
- * alone throws at runtime → blank page); Tailwind utilities need Tailwind.
102
- * Recommending either when it isn't configured yields unstyled or blank output.
103
- * Plain CSS variables (via `style`/`className`) always work, so they're the
104
- * safe default. Precedence: stylex (compiler wired) → tailwind → css.
105
- *
106
- * @param {string} targetDir
107
- * @returns {'stylex' | 'tailwind' | 'css'}
108
- */
109
- export function detectStylingSystem(targetDir) {
110
- try {
111
- const pkgPath = path.join(targetDir, 'package.json');
112
- if (!fs.existsSync(pkgPath)) return 'css';
113
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
114
- const deps = {...pkg.dependencies, ...pkg.devDependencies};
115
- // Key off a StyleX *compiler* plugin — the runtime alone won't render.
116
- const stylexCompilers = [
117
- '@stylexjs/babel-plugin',
118
- 'vite-plugin-stylex',
119
- 'unplugin-stylex',
120
- '@stylexswc/unplugin',
121
- '@stylexswc/nextjs-plugin',
122
- 'stylex-webpack',
123
- ];
124
- if (stylexCompilers.some(d => d in deps)) return 'stylex';
125
- if ('tailwindcss' in deps) return 'tailwind';
126
- return 'css';
127
- } catch {
128
- // Best-effort: default to the universally-safe CSS-variable path.
129
- return 'css';
130
- }
131
- }
132
-
133
96
  /**
134
97
  * Generate the agent cheat sheet from live CLI metadata.
135
98
  *
136
99
  * Structured as: workflow (behavioral) → rules (error prevention) → CLI reference.
137
100
  * Templates are positioned first in the workflow to teach agents the
138
101
  * "look at reference code" reflex before writing any UI.
139
- *
140
- * `stylingSystem` tailors the custom-styling guidance to what the project has
141
- * configured (see {@link detectStylingSystem}) so the agent never reaches for a
142
- * styling path that isn't compiled here.
143
102
  */
144
- export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPrefix(), stylingSystem = 'css'} = {}) {
103
+ export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPrefix()} = {}) {
145
104
  const run = `${runPrefix} astryx`;
146
105
  const lines = [MARKER_START];
147
106
 
@@ -158,57 +117,86 @@ export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPre
158
117
  }
159
118
  }
160
119
 
161
- // Header — state the CLI prefix once; commands below are shown as `astryx <cmd>`.
162
- lines.push(`Astryx v${version} · ${componentCount} components`);
163
- lines.push(`CLI: run every command as \`${run} <cmd>\` (shown below as \`astryx ...\`).`);
120
+ // Header
121
+ lines.push(`Astryx v${version} ${componentCount} components`);
164
122
  lines.push('');
165
123
 
166
- // Required setup — components ship precompiled CSS; without these imports
167
- // everything renders unstyled. Theme is optional (a default ships in astryx.css).
168
- lines.push('SETUP (once, in your app entry e.g. main.tsx) without these, components render unstyled:');
124
+ // One-time project setup — components ship precompiled CSS that MUST be
125
+ // imported, or everything renders unstyled. Stated as text (the CLI does not
126
+ // edit your files). The Theme provider is OPTIONAL (a default theme ships in
127
+ // astryx.css); only the CSS imports are required.
128
+ lines.push('SETUP (required, once) — in your app entry (e.g. main.tsx):');
169
129
  lines.push(' import "@astryxdesign/core/reset.css";');
170
130
  lines.push(' import "@astryxdesign/core/astryx.css";');
131
+ lines.push('Without these CSS imports, components render completely unstyled.');
132
+ lines.push(`Optional: apply a specific theme with <Theme> — see \`${run} docs theme\`.`);
171
133
  lines.push('');
172
134
 
173
- // Workflow — `build` is the front door; discover before writing UI.
174
- lines.push("WORKFLOW discover, don't guess. Before writing UI:");
175
- lines.push('1. `astryx build "<idea>"` — START HERE: returns a kit (closest [page] + [block]s + [component]s). No args = full playbook.');
176
- lines.push('2. `astryx template <name> [--skeleton]` — scaffold the [page]/[block]s it named, or study their layout. Templates are reference code.');
177
- lines.push('3. `astryx component <Name>` props + examples for every component you use.');
135
+ // Behavioral workflow — `build` is the front door for making pages: it returns
136
+ // a composition kit, and `build` with no args prints the full how-to playbook.
137
+ lines.push('Before writing any UI code:');
138
+ lines.push(
139
+ `1. \`${run} build "<what you're building>"\` — START HERE. Returns a composition kit: the closest [page] recipe (scaffold it, or use as a layout reference), the [block]s that cover parts, and the [component]s to fill gaps. (Run \`${run} build\` with no args for the full how-to-build playbook.)`,
140
+ );
141
+ lines.push(`2. \`${run} template <name> --skeleton\` — scaffold a matched [page], or study its layout (gap, padding, nesting)`);
142
+ lines.push(`3. \`${run} template <BlockName>\` — drop in each [block] the kit surfaced (ready-made multi-component patterns) instead of hand-building`);
143
+ lines.push(`4. \`${run} component <Name>\` — read props + examples for EVERY component you use`);
144
+ lines.push('');
145
+ lines.push('Templates and blocks are reference code — read them for composition patterns, not just scaffolding.');
146
+ lines.push(`(\`${run} search <query>\` is a neutral find across components/hooks/docs/templates when you just need to look something up.)`);
178
147
  lines.push('');
179
148
 
180
- // Rules — the top error-preventers.
181
- lines.push('RULES:');
182
- lines.push('- No <div> components do all layout/spacing. Full pageAppShell; sidebar nav → SideNav.');
183
- // Styling guidance tailored to the project's configured system never
184
- // recommend a path that isn't compiled here (xstyle needs the StyleX compiler;
185
- // utilities need Tailwind). Tokens are always the source of truth.
186
- if (stylingSystem === 'stylex') {
187
- lines.push('- Custom styling: component props first; else the xstyle prop / StyleX tokens (@astryxdesign/core/theme/tokens.stylex). No raw hex/px.');
188
- } else if (stylingSystem === 'tailwind') {
189
- lines.push('- Custom styling: component props first; else Tailwind utilities backed by tokens (bg-surface, text-primary, rounded-lg) via tailwind-theme.css. No raw hex/px.');
190
- } else {
191
- lines.push("- Custom styling: component props first; else style/className with tokens — var(--color-*|--spacing-*|--radius-*). No raw hex/px. (No StyleX/Tailwind compiler here — don't use xstyle/utility classes.)");
192
- }
193
- lines.push('- Tokens for every value (`astryx docs tokens`). Brand/accent via `astryx theme` — never override --color-* in :root.');
149
+ // Rules — inline, compact, prevents the top error categories
150
+ lines.push('No <div> anywhere — not for layout, not for wrappers, not for spacing. Use components.');
151
+ lines.push('Full-page shells AppShell (not Layout). Sidebar navSideNav (not List).');
152
+ lines.push('No style={{}} use the xstyle prop on components for custom styling.');
153
+ lines.push('If a component prop does what you need, use it never replicate with CSS/stylex.');
154
+ lines.push(`No magic values run \`${run} docs tokens\` for spacing/color/radius.`);
155
+ lines.push(`To change accent/brand colors: \`${run} theme\` — never override --astryx-color-* in :root.`);
194
156
  lines.push('');
195
157
 
196
- // Command reference — build/template/component are covered in WORKFLOW above.
197
- lines.push('MORE CLI:');
198
- lines.push(' search "<query>" find any component / hook / doc / template / block');
199
- lines.push(` component --list ${componentCount} components by category`);
200
- lines.push(' template --list page + block recipes');
158
+ // CLI quick reference
159
+ lines.push(`${run} build "<idea>" START HERE for pages — kit: closest page + blocks + components (\`build\` = how-to playbook)`);
160
+ lines.push(`${run} search "<query>" find anything: components, hooks, docs, templates, blocks`);
161
+ lines.push(`${run} component --list ${componentCount} components by category`);
162
+ lines.push(`${run} component <Name> props, types, examples`);
163
+
164
+ // Doc topics from live discovery
201
165
  const docsDir = path.join(CLI_ROOT, 'docs');
202
166
  if (fs.existsSync(docsDir)) {
203
- const topics = fs.readdirSync(docsDir)
204
- .map(f => f.match(/^(\w+)\.doc\.mjs$/))
205
- .filter(Boolean)
206
- .map(m => m[1])
167
+ for (const file of fs.readdirSync(docsDir).sort()) {
168
+ const match = file.match(/^(\w+)\.doc\.mjs$/);
169
+ if (!match) continue;
170
+ const topic = match[1];
171
+ let desc = topic;
172
+ try {
173
+ const fileContent = fs.readFileSync(path.join(docsDir, file), 'utf-8');
174
+ const descMatch = fileContent.match(/description:\s*['"](.+?)['"]/);
175
+ if (descMatch) desc = descMatch[1];
176
+ } catch {
177
+ // Best-effort: fall back to the topic name if the file is unreadable.
178
+ }
179
+ if (desc.length > 50) desc = desc.slice(0, 47) + '...';
180
+ lines.push(`${run} docs ${topic.padEnd(20)} ${desc}`);
181
+ }
182
+ }
183
+
184
+ // Templates
185
+ const templatesDir = path.join(CLI_ROOT, 'templates');
186
+ if (fs.existsSync(templatesDir)) {
187
+ const templates = fs.readdirSync(templatesDir, {withFileTypes: true})
188
+ .filter(e => e.isDirectory())
189
+ .map(e => e.name)
207
190
  .sort();
208
- if (topics.length > 0) lines.push(` docs <topic> ${topics.join(', ')}`);
191
+ if (templates.length > 0) {
192
+ lines.push(`${run} template --list page recipes with component lists`);
193
+ lines.push(`${run} template <name> [path] scaffold from template`);
194
+ }
209
195
  }
210
- lines.push(' swizzle <Name> eject component source (--gap reports why)');
211
- lines.push(' upgrade --apply run after any @astryxdesign/core bump');
196
+
197
+ lines.push(`${run} swizzle <Name> eject source (--gap to report why)`);
198
+ lines.push(`${run} upgrade --apply codemods after version bump`);
199
+ lines.push(`after @astryxdesign/core bump, always run ${run} upgrade --apply`);
212
200
  lines.push(MARKER_END);
213
201
 
214
202
  return lines.join('\n');
@@ -287,10 +275,7 @@ export function injectXdsBlock(filePath, compressedIndex, {createIfMissing = fal
287
275
  */
288
276
  export function injectAgentsMd(targetDir, version) {
289
277
  const agentsPath = path.join(targetDir, AGENTS_MD);
290
- const compressedIndex = generateCompressedIndex(version, {
291
- coreDir: findCoreDir(targetDir),
292
- stylingSystem: detectStylingSystem(targetDir),
293
- });
278
+ const compressedIndex = generateCompressedIndex(version, {coreDir: findCoreDir(targetDir)});
294
279
  injectXdsBlock(agentsPath, compressedIndex, {
295
280
  createIfMissing: true,
296
281
  header: `# AGENTS.md\n\nProject-specific guidance for AI coding agents.`,
@@ -305,10 +290,7 @@ export function injectAgentsMd(targetDir, version) {
305
290
  */
306
291
  export function injectClaudeMd(targetDir, version) {
307
292
  const claudePath = path.join(targetDir, CLAUDE_MD);
308
- const compressedIndex = generateCompressedIndex(version, {
309
- coreDir: findCoreDir(targetDir),
310
- stylingSystem: detectStylingSystem(targetDir),
311
- });
293
+ const compressedIndex = generateCompressedIndex(version, {coreDir: findCoreDir(targetDir)});
312
294
  return injectXdsBlock(claudePath, compressedIndex);
313
295
  }
314
296
 
@@ -391,8 +373,7 @@ export function installAgentDocs(targetDir, {zh = false, lang, agent, paths, onl
391
373
  const coreDir = findCoreDir(targetDir);
392
374
  const version = getXdsVersion(coreDir);
393
375
  const runPrefix = getRunPrefix(targetDir);
394
- const stylingSystem = detectStylingSystem(targetDir);
395
- const compressedIndex = generateCompressedIndex(version, {coreDir, zh, lang, runPrefix, stylingSystem});
376
+ const compressedIndex = generateCompressedIndex(version, {coreDir, zh, lang, runPrefix});
396
377
  const written = [];
397
378
 
398
379
  // Explicit paths override everything
@@ -6,7 +6,6 @@ import * as path from 'node:path';
6
6
  import * as os from 'node:os';
7
7
  import {
8
8
  generateCompressedIndex,
9
- detectStylingSystem,
10
9
  getXdsVersion,
11
10
  installAgentDocs,
12
11
  injectAgentsMd,
@@ -40,86 +39,31 @@ describe('generateCompressedIndex', () => {
40
39
  it('includes theme nudge rule', () => {
41
40
  const result = generateCompressedIndex('1.0.0');
42
41
  expect(result).toMatch(/astryx theme/);
43
- expect(result).toMatch(/never override --color-/);
44
- });
45
-
46
- it('defaults to the CSS-variable styling path (no compiler)', () => {
47
- const result = generateCompressedIndex('1.0.0');
48
- expect(result).toMatch(/style\/className with tokens/);
49
- expect(result).toMatch(/var\(--color-\*/);
50
- // Must NOT push xstyle when no StyleX compiler is present.
51
- expect(result).not.toMatch(/xstyle prop/);
52
- });
53
-
54
- it('recommends xstyle when StyleX is configured', () => {
55
- const result = generateCompressedIndex('1.0.0', {stylingSystem: 'stylex'});
56
- expect(result).toMatch(/xstyle prop \/ StyleX tokens/);
57
- });
58
-
59
- it('recommends Tailwind utilities when Tailwind is configured', () => {
60
- const result = generateCompressedIndex('1.0.0', {stylingSystem: 'tailwind'});
61
- expect(result).toMatch(/Tailwind utilities backed by tokens/);
62
- expect(result).toMatch(/tailwind-theme\.css/);
42
+ expect(result).toMatch(/never override --astryx-color/);
63
43
  });
64
44
 
65
45
  it('includes upgrade command and migration rule', () => {
66
46
  const result = generateCompressedIndex('1.0.0');
67
- expect(result).toContain('upgrade --apply');
68
- expect(result).toMatch(/after any @astryxdesign\/core bump/);
47
+ expect(result).toContain('astryx upgrade');
48
+ expect(result).toContain('astryx upgrade --apply');
49
+ expect(result).toMatch(/always run .+ astryx upgrade --apply/);
69
50
  });
70
51
 
71
- it('states the runPrefix once in the CLI header', () => {
52
+ it('uses custom runPrefix when provided', () => {
72
53
  const result = generateCompressedIndex('1.0.0', {runPrefix: 'yarn'});
73
- expect(result).toContain('yarn astryx <cmd>');
54
+ expect(result).toContain('yarn astryx component <Name>');
55
+ expect(result).toContain('yarn astryx upgrade --apply');
56
+ expect(result).toContain('after @astryxdesign/core bump, always run yarn astryx upgrade --apply');
74
57
  expect(result).not.toContain('npx astryx');
75
58
  });
76
59
 
77
60
  it('uses pnpm exec prefix', () => {
78
61
  const result = generateCompressedIndex('1.0.0', {runPrefix: 'pnpm exec'});
79
- expect(result).toContain('pnpm exec astryx <cmd>');
62
+ expect(result).toContain('pnpm exec astryx component <Name>');
80
63
  expect(result).not.toContain('npx astryx');
81
64
  });
82
65
  });
83
66
 
84
- describe('detectStylingSystem', () => {
85
- function writePkg(deps) {
86
- fs.writeFileSync(
87
- path.join(tmpDir, 'package.json'),
88
- JSON.stringify({name: 'x', devDependencies: deps}),
89
- );
90
- }
91
-
92
- it('defaults to css when no package.json', () => {
93
- expect(detectStylingSystem(tmpDir)).toBe('css');
94
- });
95
-
96
- it('returns css for a plain project', () => {
97
- writePkg({react: '19.0.0', vite: '6.0.0'});
98
- expect(detectStylingSystem(tmpDir)).toBe('css');
99
- });
100
-
101
- it('detects stylex when the compiler plugin is present', () => {
102
- writePkg({'@stylexjs/babel-plugin': '0.0.1'});
103
- expect(detectStylingSystem(tmpDir)).toBe('stylex');
104
- });
105
-
106
- it('detects tailwind when tailwindcss is present', () => {
107
- writePkg({tailwindcss: '4.0.0'});
108
- expect(detectStylingSystem(tmpDir)).toBe('tailwind');
109
- });
110
-
111
- it('does NOT treat the StyleX runtime alone as a compiler', () => {
112
- // Only the runtime, no compiler plugin → must stay on the safe css path.
113
- writePkg({'@stylexjs/stylex': '0.0.1'});
114
- expect(detectStylingSystem(tmpDir)).toBe('css');
115
- });
116
-
117
- it('prefers stylex over tailwind when both are configured', () => {
118
- writePkg({'@stylexjs/babel-plugin': '0.0.1', tailwindcss: '4.0.0'});
119
- expect(detectStylingSystem(tmpDir)).toBe('stylex');
120
- });
121
- });
122
-
123
67
  describe('getXdsVersion', () => {
124
68
  it('reads version from core package.json', () => {
125
69
  const coreDir = path.join(tmpDir, 'core');