@astryxdesign/cli 0.7.1 → 0.7.2-canary.c68f5e8
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.
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Regression coverage for the table-filter page's compact view-options tabs.
|
|
5
|
+
* @input The shipped table-filter page template source.
|
|
6
|
+
* @output Assertions that the shared tabpanel is labelled by its selected tab.
|
|
7
|
+
* @position Template contract test for the tabs relationship in the narrow-view sheet.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import {fileURLToPath} from 'node:url';
|
|
13
|
+
import {describe, expect, it} from 'vitest';
|
|
14
|
+
|
|
15
|
+
const sourcePath = path.resolve(
|
|
16
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
17
|
+
'../../assets/templates/pages/table-filter/page.tsx',
|
|
18
|
+
);
|
|
19
|
+
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
20
|
+
const sheet = source.match(
|
|
21
|
+
/const viewOptionsSheet = \(([\s\S]*?)\n {2}\);/,
|
|
22
|
+
)?.[1];
|
|
23
|
+
const tab = sheet?.match(/<Tab\b([\s\S]*?)\/>/)?.[1];
|
|
24
|
+
const panel = sheet?.match(/<VStack\b([^>]*?role="tabpanel"[^>]*?)>/)?.[1];
|
|
25
|
+
|
|
26
|
+
describe('table-filter view-options tabs', () => {
|
|
27
|
+
it('finds the compact sheet, its tab and its panel', () => {
|
|
28
|
+
expect(sheet).toBeDefined();
|
|
29
|
+
expect(tab).toBeDefined();
|
|
30
|
+
expect(panel).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('gives every tab an id derived from the panel id and its section', () => {
|
|
34
|
+
expect(tab).toContain('id={`${viewPanelId}-tab-${section.key}`}');
|
|
35
|
+
expect(tab).toContain('panelId={viewPanelId}');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('labels the shared panel by the selected tab, not a fixed string', () => {
|
|
39
|
+
expect(panel).toContain('id={viewPanelId}');
|
|
40
|
+
expect(panel).toContain(
|
|
41
|
+
'aria-labelledby={`${viewPanelId}-tab-${viewSection}`}',
|
|
42
|
+
);
|
|
43
|
+
expect(panel).not.toContain('aria-label=');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The theme build imports Core when a build runs, not when the CLI starts.
|
|
5
|
+
*
|
|
6
|
+
* The CLI loads the theme build module for every command, and Core's root
|
|
7
|
+
* entry pulls in all of Core and React. So a command that builds no theme must
|
|
8
|
+
* not ask for either, or `astryx --version` pays for them whenever a Core is
|
|
9
|
+
* installed. A resolve hook in a real subprocess records every request for
|
|
10
|
+
* Core or React and the module that made it, whether or not the request
|
|
11
|
+
* resolves.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {afterAll, beforeAll, describe, expect, it} from 'vitest';
|
|
15
|
+
import {spawnSync} from 'node:child_process';
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as os from 'node:os';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import {fileURLToPath, pathToFileURL} from 'node:url';
|
|
20
|
+
|
|
21
|
+
const BIN = fileURLToPath(
|
|
22
|
+
new URL('../../../clients/cli/bin/astryx.mjs', import.meta.url),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/** @type {string} */
|
|
26
|
+
let dir;
|
|
27
|
+
/** @type {string} */
|
|
28
|
+
let register;
|
|
29
|
+
/** @type {string} */
|
|
30
|
+
let log;
|
|
31
|
+
|
|
32
|
+
beforeAll(() => {
|
|
33
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-core-on-demand-'));
|
|
34
|
+
log = path.join(dir, 'requests.jsonl');
|
|
35
|
+
const hooks = path.join(dir, 'hooks.mjs');
|
|
36
|
+
fs.writeFileSync(
|
|
37
|
+
hooks,
|
|
38
|
+
[
|
|
39
|
+
"import {appendFileSync} from 'node:fs';",
|
|
40
|
+
`const LOG = ${JSON.stringify(log)};`,
|
|
41
|
+
'const WATCHED = /^(?:@astryxdesign\\/core|react|react-dom)(?:\\/|$)/;',
|
|
42
|
+
'export async function resolve(specifier, context, nextResolve) {',
|
|
43
|
+
' if (WATCHED.test(specifier)) {',
|
|
44
|
+
' const parent = context.parentURL ?? null;',
|
|
45
|
+
" appendFileSync(LOG, JSON.stringify({specifier, parent}) + '\\n');",
|
|
46
|
+
' }',
|
|
47
|
+
' return nextResolve(specifier, context);',
|
|
48
|
+
'}',
|
|
49
|
+
'',
|
|
50
|
+
].join('\n'),
|
|
51
|
+
);
|
|
52
|
+
register = path.join(dir, 'register.mjs');
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
register,
|
|
55
|
+
`import {register} from 'node:module';\nregister(${JSON.stringify(pathToFileURL(hooks).href)});\n`,
|
|
56
|
+
);
|
|
57
|
+
// A plain theme object: the file itself never imports Core, so a request for
|
|
58
|
+
// Core can only come from the build.
|
|
59
|
+
fs.writeFileSync(
|
|
60
|
+
path.join(dir, 'ocean.mjs'),
|
|
61
|
+
"export default {name: 'ocean', tokens: {'--color-bg': '#0a0a0a'}};\n",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterAll(() => {
|
|
66
|
+
fs.rmSync(dir, {recursive: true, force: true});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run the CLI and return each Core or React specifier it asked for, mapped to
|
|
71
|
+
* the first module that asked.
|
|
72
|
+
* @param {string[]} args
|
|
73
|
+
* @returns {{status: number | null, requested: Record<string, string | null>}}
|
|
74
|
+
*/
|
|
75
|
+
function run(args) {
|
|
76
|
+
fs.rmSync(log, {force: true});
|
|
77
|
+
const res = spawnSync(
|
|
78
|
+
process.execPath,
|
|
79
|
+
['--import', pathToFileURL(register).href, BIN, ...args],
|
|
80
|
+
{cwd: dir, encoding: 'utf-8', timeout: 20_000},
|
|
81
|
+
);
|
|
82
|
+
/** @type {Record<string, string | null>} */
|
|
83
|
+
const requested = {};
|
|
84
|
+
const lines = fs.existsSync(log)
|
|
85
|
+
? fs.readFileSync(log, 'utf-8').split('\n').filter(Boolean)
|
|
86
|
+
: [];
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
const {specifier, parent} = JSON.parse(line);
|
|
89
|
+
if (!(specifier in requested)) requested[specifier] = parent;
|
|
90
|
+
}
|
|
91
|
+
return {status: res.status, requested};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe('theme build imports Core on demand', () => {
|
|
95
|
+
it('astryx --version asks for neither Core nor React', () => {
|
|
96
|
+
const {status, requested} = run(['--version']);
|
|
97
|
+
expect(status).toBe(0);
|
|
98
|
+
expect(requested).toEqual({});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('theme build imports Core from the theme build module', () => {
|
|
102
|
+
const {requested} = run([
|
|
103
|
+
'theme',
|
|
104
|
+
'build',
|
|
105
|
+
'ocean.mjs',
|
|
106
|
+
'--check',
|
|
107
|
+
'--json',
|
|
108
|
+
]);
|
|
109
|
+
expect(requested['@astryxdesign/core/theme']).toMatch(
|
|
110
|
+
/\/api\/theme\/build\/build\.mjs$/,
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -163,6 +163,19 @@ export function themeBuildFamily(files: string[], options: {
|
|
|
163
163
|
checked?: undefined;
|
|
164
164
|
};
|
|
165
165
|
}>;
|
|
166
|
+
/**
|
|
167
|
+
* The bindings one Core provides: the namespaces interception spreads, the
|
|
168
|
+
* functions the build calls, and why the import failed, if it did.
|
|
169
|
+
*/
|
|
170
|
+
export type CoreBindings = {
|
|
171
|
+
themeModule: any;
|
|
172
|
+
rootModule: any;
|
|
173
|
+
importError: any;
|
|
174
|
+
defineTheme: any;
|
|
175
|
+
generateThemeRulesSplit: any;
|
|
176
|
+
generateOnMediaCSS: any;
|
|
177
|
+
generateAdaptationCSS: any;
|
|
178
|
+
};
|
|
166
179
|
/**
|
|
167
180
|
* How one `prop:value` pair on a component target is classified — the single
|
|
168
181
|
* vocabulary both the root and adaptation paths reason in.
|
|
@@ -210,16 +223,3 @@ export type ThemeBuildTrailer = {
|
|
|
210
223
|
*/
|
|
211
224
|
fontNotices: string[];
|
|
212
225
|
};
|
|
213
|
-
/**
|
|
214
|
-
* The bindings one Core provides: the namespaces interception spreads, the
|
|
215
|
-
* functions the build calls, and why the import failed, if it did.
|
|
216
|
-
*/
|
|
217
|
-
export type CoreBindings = {
|
|
218
|
-
themeModule: any;
|
|
219
|
-
rootModule: any;
|
|
220
|
-
importError: any;
|
|
221
|
-
defineTheme: any;
|
|
222
|
-
generateThemeRulesSplit: any;
|
|
223
|
-
generateOnMediaCSS: any;
|
|
224
|
-
generateAdaptationCSS: any;
|
|
225
|
-
};
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
* general compatibility scheme for every export an arbitrary older core might
|
|
38
38
|
* lack.
|
|
39
39
|
*
|
|
40
|
+
* Core is imported on first use, never when this module loads: the CLI loads
|
|
41
|
+
* this module for every command, and Core's root entry pulls in all of Core
|
|
42
|
+
* and React.
|
|
43
|
+
*
|
|
40
44
|
* With `{check: true}`, it compiles the same outputs in memory but writes
|
|
41
45
|
* nothing: it compares each generated file against what is on disk (ignoring
|
|
42
46
|
* only the volatile `@generated` `Command:` line) and returns a
|
|
@@ -91,6 +95,8 @@ import {resolveIconImports} from './icon-imports.mjs';
|
|
|
91
95
|
// action). A built, resolvable `@astryxdesign/core` is a hard requirement.
|
|
92
96
|
// A built, resolvable `@astryxdesign/core` is a hard requirement. These are
|
|
93
97
|
// populated from a dynamic import (a runtime boundary), so `any` is intentional.
|
|
98
|
+
// That import runs the first time a build needs Core (see `loadCliCore`), not
|
|
99
|
+
// when this module loads.
|
|
94
100
|
//
|
|
95
101
|
// The CLI and core are independently versioned optional peers, so this import
|
|
96
102
|
// can succeed against a core OLDER than the CLI. Only the exports every theme
|
|
@@ -120,27 +126,6 @@ import {resolveIconImports} from './icon-imports.mjs';
|
|
|
120
126
|
*/
|
|
121
127
|
/** @type {any} */ let _coreRootModule = null;
|
|
122
128
|
/** @type {any} */ let _coreImportError = null;
|
|
123
|
-
try {
|
|
124
|
-
const coreTheme = await import('@astryxdesign/core/theme');
|
|
125
|
-
_coreThemeModule = coreTheme;
|
|
126
|
-
_defineTheme = coreTheme.defineTheme;
|
|
127
|
-
_generateThemeRulesSplit = coreTheme.generateThemeRulesSplit;
|
|
128
|
-
_generateOnMediaCSS = coreTheme.generateOnMediaCSS;
|
|
129
|
-
_generateAdaptationCSS = coreTheme.generateAdaptationCSS;
|
|
130
|
-
try {
|
|
131
|
-
_coreRootModule = await import('@astryxdesign/core');
|
|
132
|
-
} catch {
|
|
133
|
-
// A core without a usable root entry still builds themes; interception
|
|
134
|
-
// falls back to the theme namespace for the root specifier.
|
|
135
|
-
}
|
|
136
|
-
} catch (e) {
|
|
137
|
-
// Capture the reason so the theme action can surface a precise, actionable
|
|
138
|
-
// error. We don't throw here: this module is imported eagerly by the CLI
|
|
139
|
-
// entrypoint for every command, and a throw at load time would break
|
|
140
|
-
// unrelated commands (the entry wraps loads in try/catch and degrades the
|
|
141
|
-
// command to a stub). The hard failure happens when `theme build` runs.
|
|
142
|
-
_coreImportError = e;
|
|
143
|
-
}
|
|
144
129
|
|
|
145
130
|
/**
|
|
146
131
|
* The bindings one Core provides: the namespaces interception spreads, the
|
|
@@ -157,19 +142,65 @@ try {
|
|
|
157
142
|
*/
|
|
158
143
|
|
|
159
144
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
145
|
+
* Import the CLI's own Core: the `@astryxdesign/core` this module resolves.
|
|
146
|
+
* Never throws. A failed import is kept as `importError`, so the build can
|
|
147
|
+
* surface a precise, actionable error when it runs.
|
|
148
|
+
* @returns {Promise<CoreBindings>}
|
|
163
149
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
150
|
+
async function importCliCore() {
|
|
151
|
+
/** @type {CoreBindings} */
|
|
152
|
+
const core = {
|
|
153
|
+
themeModule: null,
|
|
154
|
+
rootModule: null,
|
|
155
|
+
importError: null,
|
|
156
|
+
defineTheme: null,
|
|
157
|
+
generateThemeRulesSplit: null,
|
|
158
|
+
generateOnMediaCSS: null,
|
|
159
|
+
generateAdaptationCSS: null,
|
|
160
|
+
};
|
|
161
|
+
try {
|
|
162
|
+
const coreTheme = await import('@astryxdesign/core/theme');
|
|
163
|
+
core.themeModule = coreTheme;
|
|
164
|
+
core.defineTheme = coreTheme.defineTheme;
|
|
165
|
+
core.generateThemeRulesSplit = coreTheme.generateThemeRulesSplit;
|
|
166
|
+
core.generateOnMediaCSS = coreTheme.generateOnMediaCSS;
|
|
167
|
+
core.generateAdaptationCSS = coreTheme.generateAdaptationCSS;
|
|
168
|
+
try {
|
|
169
|
+
core.rootModule = await import('@astryxdesign/core');
|
|
170
|
+
} catch {
|
|
171
|
+
// A core without a usable root entry still builds themes; interception
|
|
172
|
+
// falls back to the theme namespace for the root specifier.
|
|
173
|
+
}
|
|
174
|
+
} catch (e) {
|
|
175
|
+
// Capture the reason so the theme action can surface a precise, actionable
|
|
176
|
+
// error. The hard failure happens when `theme build` runs.
|
|
177
|
+
core.importError = e;
|
|
178
|
+
}
|
|
179
|
+
return core;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The CLI's own Core, imported once, the first time a build needs it. The CLI
|
|
184
|
+
* loads this module for every command, and Core's root entry loads all of Core
|
|
185
|
+
* and React, so an import at module load would make every command
|
|
186
|
+
* (`--version` included) pay for that whenever a Core resolves.
|
|
187
|
+
* @type {Promise<CoreBindings> | null}
|
|
188
|
+
*/
|
|
189
|
+
let _cliCorePromise = null;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The CLI's own Core, kept so each build can choose between it and the
|
|
193
|
+
* project's installed Core. The first call also points the build at it, so a
|
|
194
|
+
* step that runs before any build has chosen a Core reads the CLI's own.
|
|
195
|
+
* @returns {Promise<CoreBindings>}
|
|
196
|
+
*/
|
|
197
|
+
function loadCliCore() {
|
|
198
|
+
_cliCorePromise ??= importCliCore().then(core => {
|
|
199
|
+
setCore(core);
|
|
200
|
+
return core;
|
|
201
|
+
});
|
|
202
|
+
return _cliCorePromise;
|
|
203
|
+
}
|
|
173
204
|
|
|
174
205
|
/**
|
|
175
206
|
* Point the build at one Core: the module-level bindings every step reads.
|
|
@@ -248,13 +279,14 @@ function realOrSelf(file) {
|
|
|
248
279
|
* repository, a bare directory), the same Core, or one that does not load.
|
|
249
280
|
*
|
|
250
281
|
* @param {string} cwd
|
|
282
|
+
* @param {CoreBindings} cliCore - The CLI's own Core, from {@link loadCliCore}.
|
|
251
283
|
* @returns {Promise<CoreBindings | null>}
|
|
252
284
|
*/
|
|
253
|
-
async function loadProjectCore(cwd) {
|
|
285
|
+
async function loadProjectCore(cwd, cliCore) {
|
|
254
286
|
const dir = findInstalledPackage(cwd, '@astryxdesign/core');
|
|
255
287
|
const themeFile = dir ? importTarget(dir, './theme') : null;
|
|
256
288
|
if (!dir || !themeFile) return null;
|
|
257
|
-
if (
|
|
289
|
+
if (cliCore.themeModule) {
|
|
258
290
|
try {
|
|
259
291
|
const own = fileURLToPath(
|
|
260
292
|
import.meta.resolve('@astryxdesign/core/theme'),
|
|
@@ -301,9 +333,10 @@ const _projectCores = new Map();
|
|
|
301
333
|
* @param {string} cwd
|
|
302
334
|
*/
|
|
303
335
|
async function selectCore(cwd) {
|
|
336
|
+
const cliCore = await loadCliCore();
|
|
304
337
|
if (!_projectCores.has(cwd))
|
|
305
|
-
_projectCores.set(cwd, await loadProjectCore(cwd));
|
|
306
|
-
setCore(_projectCores.get(cwd) ??
|
|
338
|
+
_projectCores.set(cwd, await loadProjectCore(cwd, cliCore));
|
|
339
|
+
setCore(_projectCores.get(cwd) ?? cliCore);
|
|
307
340
|
}
|
|
308
341
|
|
|
309
342
|
/**
|
|
@@ -1694,6 +1727,8 @@ export async function validateThemePrivateInputs(
|
|
|
1694
1727
|
);
|
|
1695
1728
|
}
|
|
1696
1729
|
|
|
1730
|
+
// The CLI's own Core, unless a build in this process already chose one.
|
|
1731
|
+
await loadCliCore();
|
|
1697
1732
|
const interception = interceptCore(_coreThemeModule, _coreRootModule);
|
|
1698
1733
|
let theme;
|
|
1699
1734
|
try {
|
|
@@ -4099,7 +4099,7 @@ export default function TableFilterTemplate() {
|
|
|
4099
4099
|
restates the selected tab word for word, and the strip already
|
|
4100
4100
|
marks which one that is. On a phone that is a line of vertical
|
|
4101
4101
|
space spent saying it twice. The panel keeps the name for anyone
|
|
4102
|
-
not seeing the strip
|
|
4102
|
+
not seeing the strip: its selected tab labels it. */}
|
|
4103
4103
|
<TabList
|
|
4104
4104
|
value={viewSection}
|
|
4105
4105
|
onChange={value => setViewSection(value as ViewSection)}
|
|
@@ -4109,6 +4109,7 @@ export default function TableFilterTemplate() {
|
|
|
4109
4109
|
{VIEW_SECTIONS.map(section => (
|
|
4110
4110
|
<Tab
|
|
4111
4111
|
key={section.key}
|
|
4112
|
+
id={`${viewPanelId}-tab-${section.key}`}
|
|
4112
4113
|
value={section.key}
|
|
4113
4114
|
label={section.label}
|
|
4114
4115
|
icon={section.icon}
|
|
@@ -4126,7 +4127,7 @@ export default function TableFilterTemplate() {
|
|
|
4126
4127
|
gap={0}
|
|
4127
4128
|
id={viewPanelId}
|
|
4128
4129
|
role="tabpanel"
|
|
4129
|
-
aria-
|
|
4130
|
+
aria-labelledby={`${viewPanelId}-tab-${viewSection}`}
|
|
4130
4131
|
paddingBlockStart={3}>
|
|
4131
4132
|
{viewPanelBody()}
|
|
4132
4133
|
</VStack>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2-canary.c68f5e8",
|
|
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",
|
|
@@ -101,10 +101,10 @@
|
|
|
101
101
|
"zod": "^4.4.3"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
|
-
"@astryxdesign/charts": "
|
|
105
|
-
"@astryxdesign/core": "
|
|
106
|
-
"@astryxdesign/lab": "
|
|
107
|
-
"@astryxdesign/theme-neutral": "
|
|
104
|
+
"@astryxdesign/charts": "0.7.2-canary.c68f5e8",
|
|
105
|
+
"@astryxdesign/core": "0.7.2-canary.c68f5e8",
|
|
106
|
+
"@astryxdesign/lab": "0.7.2-canary.c68f5e8",
|
|
107
|
+
"@astryxdesign/theme-neutral": "0.7.2-canary.c68f5e8"
|
|
108
108
|
},
|
|
109
109
|
"peerDependenciesMeta": {
|
|
110
110
|
"@astryxdesign/charts": {
|
|
@@ -121,10 +121,10 @@
|
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
|
-
"@astryxdesign/charts": "
|
|
125
|
-
"@astryxdesign/core": "
|
|
126
|
-
"@astryxdesign/lab": "
|
|
127
|
-
"@astryxdesign/theme-neutral": "
|
|
124
|
+
"@astryxdesign/charts": "0.7.2-canary.c68f5e8",
|
|
125
|
+
"@astryxdesign/core": "0.7.2-canary.c68f5e8",
|
|
126
|
+
"@astryxdesign/lab": "0.7.2-canary.c68f5e8",
|
|
127
|
+
"@astryxdesign/theme-neutral": "0.7.2-canary.c68f5e8",
|
|
128
128
|
"@heroicons/react": "^2.2.0",
|
|
129
129
|
"@stylexjs/stylex": "^0.19.0",
|
|
130
130
|
"@types/babel__core": "^7.20.5",
|