@rs-x/cli 2.0.0-next.2 → 2.0.0-next.21
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/README.md +5 -0
- package/bin/rsx.cjs +2868 -595
- package/package.json +5 -1
- package/{rs-x-vscode-extension-2.0.0-next.2.vsix → rs-x-vscode-extension-2.0.0-next.21.vsix} +0 -0
- package/scripts/prepare-local-rsx-packages.sh +20 -0
- package/scripts/verify-rsx-cli-mutations.sh +296 -0
- package/scripts/verify-rsx-projects.sh +220 -0
- package/scripts/verify-rsx-setup.sh +190 -0
- package/templates/angular-demo/README.md +115 -0
- package/templates/angular-demo/src/app/app.component.css +97 -0
- package/templates/angular-demo/src/app/app.component.html +58 -0
- package/templates/angular-demo/src/app/app.component.ts +52 -0
- package/templates/angular-demo/src/app/virtual-table/row-data.ts +35 -0
- package/templates/angular-demo/src/app/virtual-table/row-model.ts +45 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table-data.service.ts +136 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table-model.ts +224 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table.component.css +174 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table.component.html +50 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table.component.ts +83 -0
- package/templates/angular-demo/src/index.html +11 -0
- package/templates/angular-demo/src/main.ts +16 -0
- package/templates/angular-demo/src/styles.css +261 -0
- package/templates/next-demo/README.md +26 -0
- package/templates/next-demo/app/globals.css +431 -0
- package/templates/next-demo/app/layout.tsx +22 -0
- package/templates/next-demo/app/page.tsx +5 -0
- package/templates/next-demo/components/demo-app.tsx +114 -0
- package/templates/next-demo/components/virtual-table-row.tsx +40 -0
- package/templates/next-demo/components/virtual-table-shell.tsx +86 -0
- package/templates/next-demo/hooks/use-virtual-table-controller.ts +26 -0
- package/templates/next-demo/hooks/use-virtual-table-viewport.ts +41 -0
- package/templates/next-demo/lib/row-data.ts +35 -0
- package/templates/next-demo/lib/row-model.ts +45 -0
- package/templates/next-demo/lib/rsx-bootstrap.ts +46 -0
- package/templates/next-demo/lib/virtual-table-controller.ts +259 -0
- package/templates/next-demo/lib/virtual-table-data.service.ts +132 -0
- package/templates/react-demo/README.md +113 -0
- package/templates/react-demo/index.html +12 -0
- package/templates/react-demo/src/app/app.tsx +87 -0
- package/templates/react-demo/src/app/hooks/use-virtual-table-controller.ts +24 -0
- package/templates/react-demo/src/app/hooks/use-virtual-table-viewport.ts +39 -0
- package/templates/react-demo/src/app/virtual-table/row-data.ts +35 -0
- package/templates/react-demo/src/app/virtual-table/row-model.ts +45 -0
- package/templates/react-demo/src/app/virtual-table/virtual-table-controller.ts +259 -0
- package/templates/react-demo/src/app/virtual-table/virtual-table-data.service.ts +132 -0
- package/templates/react-demo/src/app/virtual-table/virtual-table-row.tsx +38 -0
- package/templates/react-demo/src/app/virtual-table/virtual-table-shell.tsx +84 -0
- package/templates/react-demo/src/main.tsx +24 -0
- package/templates/react-demo/src/rsx-bootstrap.ts +48 -0
- package/templates/react-demo/src/styles.css +422 -0
- package/templates/react-demo/tsconfig.json +17 -0
- package/templates/react-demo/vite.config.ts +6 -0
- package/templates/vue-demo/README.md +27 -0
- package/templates/vue-demo/src/App.vue +89 -0
- package/templates/vue-demo/src/components/VirtualTableRow.vue +33 -0
- package/templates/vue-demo/src/components/VirtualTableShell.vue +71 -0
- package/templates/vue-demo/src/composables/use-virtual-table-controller.ts +33 -0
- package/templates/vue-demo/src/composables/use-virtual-table-viewport.ts +40 -0
- package/templates/vue-demo/src/env.d.ts +10 -0
- package/templates/vue-demo/src/lib/row-data.ts +35 -0
- package/templates/vue-demo/src/lib/row-model.ts +45 -0
- package/templates/vue-demo/src/lib/rsx-bootstrap.ts +46 -0
- package/templates/vue-demo/src/lib/virtual-table-controller.ts +259 -0
- package/templates/vue-demo/src/lib/virtual-table-data.service.ts +132 -0
- package/templates/vue-demo/src/main.ts +13 -0
- package/templates/vue-demo/src/style.css +440 -0
package/bin/rsx.cjs
CHANGED
|
@@ -5,8 +5,40 @@ const path = require('node:path');
|
|
|
5
5
|
const readline = require('node:readline/promises');
|
|
6
6
|
const { spawnSync } = require('node:child_process');
|
|
7
7
|
|
|
8
|
-
const CLI_VERSION =
|
|
8
|
+
const CLI_VERSION = (() => {
|
|
9
|
+
try {
|
|
10
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
11
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
12
|
+
return packageJson.version ?? '0.0.0';
|
|
13
|
+
} catch {
|
|
14
|
+
return '0.0.0';
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
9
17
|
const VS_CODE_EXTENSION_ID = 'rs-x.rs-x-vscode-extension';
|
|
18
|
+
const ANGULAR_DEMO_TEMPLATE_DIR = path.join(
|
|
19
|
+
__dirname,
|
|
20
|
+
'..',
|
|
21
|
+
'templates',
|
|
22
|
+
'angular-demo',
|
|
23
|
+
);
|
|
24
|
+
const REACT_DEMO_TEMPLATE_DIR = path.join(
|
|
25
|
+
__dirname,
|
|
26
|
+
'..',
|
|
27
|
+
'templates',
|
|
28
|
+
'react-demo',
|
|
29
|
+
);
|
|
30
|
+
const VUE_DEMO_TEMPLATE_DIR = path.join(
|
|
31
|
+
__dirname,
|
|
32
|
+
'..',
|
|
33
|
+
'templates',
|
|
34
|
+
'vue-demo',
|
|
35
|
+
);
|
|
36
|
+
const NEXT_DEMO_TEMPLATE_DIR = path.join(
|
|
37
|
+
__dirname,
|
|
38
|
+
'..',
|
|
39
|
+
'templates',
|
|
40
|
+
'next-demo',
|
|
41
|
+
);
|
|
10
42
|
const RUNTIME_PACKAGES = [
|
|
11
43
|
'@rs-x/core',
|
|
12
44
|
'@rs-x/state-manager',
|
|
@@ -119,7 +151,7 @@ function parseArgs(argv) {
|
|
|
119
151
|
}
|
|
120
152
|
|
|
121
153
|
function run(command, args, options = {}) {
|
|
122
|
-
const { dryRun, cwd = process.cwd() } = options;
|
|
154
|
+
const { dryRun, cwd = process.cwd(), env } = options;
|
|
123
155
|
const printable = [command, ...args].join(' ');
|
|
124
156
|
|
|
125
157
|
if (dryRun) {
|
|
@@ -129,6 +161,7 @@ function run(command, args, options = {}) {
|
|
|
129
161
|
|
|
130
162
|
const result = spawnSync(command, args, {
|
|
131
163
|
cwd,
|
|
164
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
132
165
|
stdio: 'inherit',
|
|
133
166
|
});
|
|
134
167
|
|
|
@@ -178,6 +211,22 @@ function detectPackageManager(explicitPm) {
|
|
|
178
211
|
return 'npm';
|
|
179
212
|
}
|
|
180
213
|
|
|
214
|
+
function resolveCliPackageManager(projectRoot, explicitPm) {
|
|
215
|
+
if (explicitPm) {
|
|
216
|
+
return explicitPm;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const cliConfig = resolveRsxCliConfig(projectRoot);
|
|
220
|
+
if (
|
|
221
|
+
typeof cliConfig.packageManager === 'string' &&
|
|
222
|
+
['pnpm', 'npm', 'yarn', 'bun'].includes(cliConfig.packageManager)
|
|
223
|
+
) {
|
|
224
|
+
return cliConfig.packageManager;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return detectPackageManager(undefined);
|
|
228
|
+
}
|
|
229
|
+
|
|
181
230
|
function applyTagToPackages(packages, tag) {
|
|
182
231
|
return packages.map((pkg) => {
|
|
183
232
|
const lastAt = pkg.lastIndexOf('@');
|
|
@@ -191,11 +240,68 @@ function applyTagToPackages(packages, tag) {
|
|
|
191
240
|
}
|
|
192
241
|
|
|
193
242
|
function resolveInstallTag(flags) {
|
|
194
|
-
|
|
243
|
+
if (parseBooleanFlag(flags.next, false)) {
|
|
244
|
+
return 'next';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (CLI_VERSION.includes('-')) {
|
|
248
|
+
return 'next';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const checkoutRoot = findRepoRoot(__dirname);
|
|
252
|
+
if (!checkoutRoot) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const branchResult = spawnSync('git', ['branch', '--show-current'], {
|
|
257
|
+
cwd: checkoutRoot,
|
|
258
|
+
encoding: 'utf8',
|
|
259
|
+
});
|
|
260
|
+
const branch = branchResult.status === 0 ? branchResult.stdout.trim() : '';
|
|
261
|
+
if (branch && branch !== 'main') {
|
|
262
|
+
return 'next';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function resolveConfiguredInstallTag(projectRoot, flags) {
|
|
269
|
+
const explicitTag = resolveInstallTag(flags);
|
|
270
|
+
if (explicitTag) {
|
|
271
|
+
return explicitTag;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const cliConfig = resolveRsxCliConfig(projectRoot);
|
|
275
|
+
if (
|
|
276
|
+
typeof cliConfig.installTag === 'string' &&
|
|
277
|
+
['latest', 'next'].includes(cliConfig.installTag)
|
|
278
|
+
) {
|
|
279
|
+
return cliConfig.installTag;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveCliVerifyFlag(projectRoot, flags, sectionName) {
|
|
286
|
+
if (flags.verify !== undefined) {
|
|
287
|
+
return parseBooleanFlag(flags.verify, false);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const cliConfig = resolveRsxCliConfig(projectRoot);
|
|
291
|
+
const sectionConfig = cliConfig[sectionName];
|
|
292
|
+
if (
|
|
293
|
+
typeof sectionConfig === 'object' &&
|
|
294
|
+
sectionConfig &&
|
|
295
|
+
typeof sectionConfig.verify === 'boolean'
|
|
296
|
+
) {
|
|
297
|
+
return sectionConfig.verify;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return false;
|
|
195
301
|
}
|
|
196
302
|
|
|
197
303
|
function installPackages(pm, packages, options = {}) {
|
|
198
|
-
const { dev = false, dryRun = false, label = 'packages', tag } = options;
|
|
304
|
+
const { dev = false, dryRun = false, label = 'packages', tag, cwd } = options;
|
|
199
305
|
const resolvedPackages = tag ? applyTagToPackages(packages, tag) : packages;
|
|
200
306
|
const argsByPm = {
|
|
201
307
|
pnpm: dev
|
|
@@ -220,24 +326,74 @@ function installPackages(pm, packages, options = {}) {
|
|
|
220
326
|
|
|
221
327
|
const tagInfo = tag ? ` (tag: ${tag})` : '';
|
|
222
328
|
logInfo(`Installing ${label} with ${pm}${tagInfo}...`);
|
|
223
|
-
run(pm, installArgs, { dryRun });
|
|
329
|
+
run(pm, installArgs, { dryRun, cwd });
|
|
224
330
|
logOk(`Installed ${label}.`);
|
|
225
331
|
}
|
|
226
332
|
|
|
227
|
-
function
|
|
228
|
-
|
|
333
|
+
function resolveLocalRsxSpecs(projectRoot, flags, options = {}) {
|
|
334
|
+
const tarballsDir =
|
|
335
|
+
typeof flags?.['tarballs-dir'] === 'string'
|
|
336
|
+
? path.resolve(projectRoot, flags['tarballs-dir'])
|
|
337
|
+
: typeof process.env.RSX_TARBALLS_DIR === 'string' &&
|
|
338
|
+
process.env.RSX_TARBALLS_DIR.trim().length > 0
|
|
339
|
+
? path.resolve(projectRoot, process.env.RSX_TARBALLS_DIR)
|
|
340
|
+
: null;
|
|
341
|
+
const workspaceRoot = findRepoRoot(projectRoot);
|
|
342
|
+
return resolveProjectRsxSpecs(
|
|
343
|
+
projectRoot,
|
|
344
|
+
workspaceRoot,
|
|
345
|
+
tarballsDir,
|
|
346
|
+
options,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function installResolvedPackages(pm, packageNames, options = {}) {
|
|
351
|
+
const {
|
|
352
|
+
dryRun = false,
|
|
353
|
+
label = 'packages',
|
|
354
|
+
tag,
|
|
355
|
+
cwd,
|
|
356
|
+
specs,
|
|
357
|
+
dev = false,
|
|
358
|
+
} = options;
|
|
359
|
+
const resolvedPackages = packageNames.map((packageName) => {
|
|
360
|
+
const spec = specs?.[packageName];
|
|
361
|
+
return spec ? `${packageName}@${spec}` : packageName;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
installPackages(pm, resolvedPackages, {
|
|
365
|
+
dev,
|
|
366
|
+
dryRun,
|
|
367
|
+
label,
|
|
368
|
+
tag: specs ? undefined : tag,
|
|
369
|
+
cwd,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function installRuntimePackages(pm, dryRun, tag, projectRoot, flags) {
|
|
374
|
+
const specs = resolveLocalRsxSpecs(projectRoot ?? process.cwd(), flags, {
|
|
375
|
+
tag,
|
|
376
|
+
});
|
|
377
|
+
installResolvedPackages(pm, RUNTIME_PACKAGES, {
|
|
229
378
|
dev: false,
|
|
230
379
|
dryRun,
|
|
231
380
|
tag,
|
|
381
|
+
specs,
|
|
382
|
+
cwd: projectRoot,
|
|
232
383
|
label: 'runtime RS-X packages',
|
|
233
384
|
});
|
|
234
385
|
}
|
|
235
386
|
|
|
236
|
-
function installCompilerPackages(pm, dryRun, tag) {
|
|
237
|
-
|
|
387
|
+
function installCompilerPackages(pm, dryRun, tag, projectRoot, flags) {
|
|
388
|
+
const specs = resolveLocalRsxSpecs(projectRoot ?? process.cwd(), flags, {
|
|
389
|
+
tag,
|
|
390
|
+
});
|
|
391
|
+
installResolvedPackages(pm, COMPILER_PACKAGES, {
|
|
238
392
|
dev: true,
|
|
239
393
|
dryRun,
|
|
240
394
|
tag,
|
|
395
|
+
specs,
|
|
396
|
+
cwd: projectRoot,
|
|
241
397
|
label: 'compiler tooling',
|
|
242
398
|
});
|
|
243
399
|
}
|
|
@@ -262,14 +418,50 @@ function installVsCodeExtension(flags) {
|
|
|
262
418
|
return;
|
|
263
419
|
}
|
|
264
420
|
|
|
265
|
-
|
|
421
|
+
installBundledVsix(dryRun, force);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function resolveBundledVsix() {
|
|
425
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
426
|
+
const candidates = fs
|
|
427
|
+
.readdirSync(packageRoot)
|
|
428
|
+
.filter((name) => /^rs-x-vscode-extension-.*\.vsix$/u.test(name))
|
|
429
|
+
.map((name) => path.join(packageRoot, name));
|
|
430
|
+
|
|
431
|
+
if (candidates.length === 0) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const latest = candidates
|
|
436
|
+
.map((fullPath) => ({
|
|
437
|
+
fullPath,
|
|
438
|
+
mtimeMs: fs.statSync(fullPath).mtimeMs,
|
|
439
|
+
}))
|
|
440
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
|
|
441
|
+
|
|
442
|
+
return latest?.fullPath ?? null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function installBundledVsix(dryRun, force) {
|
|
446
|
+
const bundledVsix = resolveBundledVsix();
|
|
447
|
+
if (!bundledVsix) {
|
|
448
|
+
logWarn(
|
|
449
|
+
'No bundled VSIX found in @rs-x/cli. Skipping VS Code extension install.',
|
|
450
|
+
);
|
|
451
|
+
logInfo(
|
|
452
|
+
'If you are developing in the rs-x repo, use `rsx install vscode --local` instead.',
|
|
453
|
+
);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const args = ['--install-extension', bundledVsix];
|
|
266
458
|
if (force) {
|
|
267
459
|
args.push('--force');
|
|
268
460
|
}
|
|
269
461
|
|
|
270
|
-
logInfo(`Installing ${
|
|
462
|
+
logInfo(`Installing bundled VSIX from ${bundledVsix}...`);
|
|
271
463
|
run('code', args, { dryRun });
|
|
272
|
-
logOk('VS Code extension installed.');
|
|
464
|
+
logOk('VS Code extension installed from bundled VSIX.');
|
|
273
465
|
}
|
|
274
466
|
|
|
275
467
|
function installLocalVsix(dryRun, force) {
|
|
@@ -343,6 +535,13 @@ function findRepoRoot(startDir) {
|
|
|
343
535
|
function runDoctor() {
|
|
344
536
|
const nodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
345
537
|
const hasCode = hasCommand('code');
|
|
538
|
+
const availablePackageManagers = ['pnpm', 'npm', 'yarn', 'bun'].filter((pm) =>
|
|
539
|
+
hasCommand(pm),
|
|
540
|
+
);
|
|
541
|
+
const packageRoot = findNearestPackageRoot(process.cwd());
|
|
542
|
+
const duplicateRsxPackages = packageRoot
|
|
543
|
+
? findDuplicateRsxPackages(packageRoot)
|
|
544
|
+
: [];
|
|
346
545
|
const checks = [
|
|
347
546
|
{
|
|
348
547
|
name: 'Node.js >= 20',
|
|
@@ -356,12 +555,25 @@ function runDoctor() {
|
|
|
356
555
|
},
|
|
357
556
|
{
|
|
358
557
|
name: 'Package manager (pnpm/npm/yarn/bun)',
|
|
359
|
-
ok:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
558
|
+
ok: availablePackageManagers.length > 0,
|
|
559
|
+
details:
|
|
560
|
+
availablePackageManagers.length > 0
|
|
561
|
+
? `available: ${availablePackageManagers.join(', ')}`
|
|
562
|
+
: 'required for compiler package installation',
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: 'Duplicate @rs-x package versions',
|
|
566
|
+
ok: duplicateRsxPackages.length === 0,
|
|
567
|
+
details: packageRoot
|
|
568
|
+
? duplicateRsxPackages.length === 0
|
|
569
|
+
? `not detected in ${path.relative(process.cwd(), packageRoot) || '.'}`
|
|
570
|
+
: duplicateRsxPackages
|
|
571
|
+
.map(
|
|
572
|
+
({ name, versions }) =>
|
|
573
|
+
`${name} (${Array.from(versions).join(', ')})`,
|
|
574
|
+
)
|
|
575
|
+
.join('; ')
|
|
576
|
+
: 'no package.json found in current directory tree',
|
|
365
577
|
},
|
|
366
578
|
];
|
|
367
579
|
|
|
@@ -369,6 +581,149 @@ function runDoctor() {
|
|
|
369
581
|
const tag = check.ok ? '[OK]' : '[WARN]';
|
|
370
582
|
console.log(`${tag} ${check.name} - ${check.details}`);
|
|
371
583
|
}
|
|
584
|
+
|
|
585
|
+
const failingChecks = checks.filter((check) => !check.ok);
|
|
586
|
+
if (failingChecks.length > 0) {
|
|
587
|
+
console.log('');
|
|
588
|
+
console.log('Suggested next steps:');
|
|
589
|
+
for (const check of failingChecks) {
|
|
590
|
+
if (check.name === 'Node.js >= 20') {
|
|
591
|
+
console.log(
|
|
592
|
+
' - Install Node.js 20 or newer before running `rsx setup` or `rsx project`.',
|
|
593
|
+
);
|
|
594
|
+
} else if (check.name === 'VS Code CLI (code)') {
|
|
595
|
+
console.log(
|
|
596
|
+
' - Install the VS Code shell command or use `rsx install vscode --force` later.',
|
|
597
|
+
);
|
|
598
|
+
} else if (check.name === 'Package manager (pnpm/npm/yarn/bun)') {
|
|
599
|
+
console.log(
|
|
600
|
+
' - Install npm, pnpm, yarn, or bun so the CLI can add RS-X packages.',
|
|
601
|
+
);
|
|
602
|
+
} else if (check.name === 'Duplicate @rs-x package versions') {
|
|
603
|
+
console.log(
|
|
604
|
+
' - Reinstall dependencies so all `@rs-x/*` packages resolve to a single version, then rerun `rsx doctor`.',
|
|
605
|
+
);
|
|
606
|
+
console.log(
|
|
607
|
+
' - If you are linking local packages, remove nested `node_modules` inside linked RS-X packages.',
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
console.log('');
|
|
615
|
+
console.log('Suggested next steps:');
|
|
616
|
+
console.log(
|
|
617
|
+
' - Run `rsx project <framework>` to scaffold a starter, or `rsx setup` inside an existing app.',
|
|
618
|
+
);
|
|
619
|
+
console.log(' - Use `rsx add` to create your first expression file.');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function findNearestPackageRoot(startDirectory) {
|
|
623
|
+
let currentDirectory = path.resolve(startDirectory);
|
|
624
|
+
while (true) {
|
|
625
|
+
if (fs.existsSync(path.join(currentDirectory, 'package.json'))) {
|
|
626
|
+
return currentDirectory;
|
|
627
|
+
}
|
|
628
|
+
const parentDirectory = path.dirname(currentDirectory);
|
|
629
|
+
if (parentDirectory === currentDirectory) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
currentDirectory = parentDirectory;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function readRsxPackageVersionsFromNodeModules(
|
|
637
|
+
nodeModulesDirectory,
|
|
638
|
+
versionsByPackage,
|
|
639
|
+
) {
|
|
640
|
+
const scopeDirectory = path.join(nodeModulesDirectory, '@rs-x');
|
|
641
|
+
if (!fs.existsSync(scopeDirectory)) {
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const packageDirectories = fs
|
|
646
|
+
.readdirSync(scopeDirectory, { withFileTypes: true })
|
|
647
|
+
.filter((entry) => entry.isDirectory())
|
|
648
|
+
.map((entry) => path.join(scopeDirectory, entry.name));
|
|
649
|
+
|
|
650
|
+
for (const packageDirectory of packageDirectories) {
|
|
651
|
+
const packageJsonPath = path.join(packageDirectory, 'package.json');
|
|
652
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
658
|
+
const packageName = packageJson.name;
|
|
659
|
+
const packageVersion = packageJson.version;
|
|
660
|
+
if (
|
|
661
|
+
typeof packageName === 'string' &&
|
|
662
|
+
typeof packageVersion === 'string'
|
|
663
|
+
) {
|
|
664
|
+
if (!versionsByPackage.has(packageName)) {
|
|
665
|
+
versionsByPackage.set(packageName, new Set());
|
|
666
|
+
}
|
|
667
|
+
versionsByPackage.get(packageName).add(packageVersion);
|
|
668
|
+
}
|
|
669
|
+
} catch {
|
|
670
|
+
// Ignore malformed package.json files during doctor output.
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return packageDirectories;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function collectNestedRsxPackageVersions(
|
|
678
|
+
packageDirectory,
|
|
679
|
+
versionsByPackage,
|
|
680
|
+
depth,
|
|
681
|
+
) {
|
|
682
|
+
if (depth <= 0) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const nestedNodeModulesDirectory = path.join(
|
|
687
|
+
packageDirectory,
|
|
688
|
+
'node_modules',
|
|
689
|
+
);
|
|
690
|
+
if (!fs.existsSync(nestedNodeModulesDirectory)) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const nestedPackageDirectories = readRsxPackageVersionsFromNodeModules(
|
|
695
|
+
nestedNodeModulesDirectory,
|
|
696
|
+
versionsByPackage,
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
for (const nestedPackageDirectory of nestedPackageDirectories) {
|
|
700
|
+
collectNestedRsxPackageVersions(
|
|
701
|
+
nestedPackageDirectory,
|
|
702
|
+
versionsByPackage,
|
|
703
|
+
depth - 1,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function findDuplicateRsxPackages(projectRoot) {
|
|
709
|
+
const rootNodeModulesDirectory = path.join(projectRoot, 'node_modules');
|
|
710
|
+
if (!fs.existsSync(rootNodeModulesDirectory)) {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const versionsByPackage = new Map();
|
|
715
|
+
const rootPackageDirectories = readRsxPackageVersionsFromNodeModules(
|
|
716
|
+
rootNodeModulesDirectory,
|
|
717
|
+
versionsByPackage,
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
for (const packageDirectory of rootPackageDirectories) {
|
|
721
|
+
collectNestedRsxPackageVersions(packageDirectory, versionsByPackage, 3);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return Array.from(versionsByPackage.entries())
|
|
725
|
+
.filter(([, versions]) => versions.size > 1)
|
|
726
|
+
.map(([name, versions]) => ({ name, versions }));
|
|
372
727
|
}
|
|
373
728
|
|
|
374
729
|
function isValidTsIdentifier(input) {
|
|
@@ -446,104 +801,540 @@ function stripTsLikeExtension(fileName) {
|
|
|
446
801
|
return fileName.replace(/\.[cm]?[jt]sx?$/u, '');
|
|
447
802
|
}
|
|
448
803
|
|
|
449
|
-
function
|
|
804
|
+
function inferModelKeysFromExpression(expressionSource) {
|
|
805
|
+
const matches = expressionSource.matchAll(
|
|
806
|
+
/(?<![\w$.])([A-Za-z_$][A-Za-z0-9_$]*)(?![\w$])/gu,
|
|
807
|
+
);
|
|
808
|
+
const identifiers = [];
|
|
809
|
+
const ignoredIdentifiers = new Set([
|
|
810
|
+
'undefined',
|
|
811
|
+
'null',
|
|
812
|
+
'true',
|
|
813
|
+
'false',
|
|
814
|
+
'Math',
|
|
815
|
+
'Date',
|
|
816
|
+
'Number',
|
|
817
|
+
'String',
|
|
818
|
+
'Boolean',
|
|
819
|
+
'Array',
|
|
820
|
+
'Object',
|
|
821
|
+
'JSON',
|
|
822
|
+
'console',
|
|
823
|
+
]);
|
|
824
|
+
|
|
825
|
+
for (const match of matches) {
|
|
826
|
+
const identifier = match[1];
|
|
827
|
+
if (
|
|
828
|
+
TS_RESERVED_WORDS.has(identifier) ||
|
|
829
|
+
ignoredIdentifiers.has(identifier)
|
|
830
|
+
) {
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (!identifiers.includes(identifier)) {
|
|
834
|
+
identifiers.push(identifier);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return identifiers;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function createModelTemplate(expressionSource) {
|
|
842
|
+
const modelKeys = inferModelKeysFromExpression(expressionSource);
|
|
843
|
+
const modelBody =
|
|
844
|
+
modelKeys.length > 0
|
|
845
|
+
? modelKeys.map((key) => ` ${key}: 1,`).join('\n')
|
|
846
|
+
: ' a: 1,';
|
|
847
|
+
|
|
450
848
|
return `export const model = {
|
|
451
|
-
|
|
849
|
+
${modelBody}
|
|
452
850
|
};
|
|
453
851
|
`;
|
|
454
852
|
}
|
|
455
853
|
|
|
854
|
+
function createInlineExpressionTemplate(expressionName, expressionSource) {
|
|
855
|
+
return `import { rsx } from '@rs-x/expression-parser';
|
|
856
|
+
|
|
857
|
+
${createModelTemplate(expressionSource).trim()}
|
|
858
|
+
|
|
859
|
+
export const ${expressionName} = rsx(${JSON.stringify(expressionSource)})(model);
|
|
860
|
+
`;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function createInlineExpressionAppendTemplate(
|
|
864
|
+
expressionName,
|
|
865
|
+
expressionSource,
|
|
866
|
+
fileContent,
|
|
867
|
+
) {
|
|
868
|
+
const hasRsxImport = fileContent.includes("from '@rs-x/expression-parser'");
|
|
869
|
+
const hasModelExport = /\bexport\s+const\s+model\s*=/u.test(fileContent);
|
|
870
|
+
const sections = [];
|
|
871
|
+
|
|
872
|
+
if (!hasRsxImport) {
|
|
873
|
+
sections.push("import { rsx } from '@rs-x/expression-parser';");
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (!hasModelExport) {
|
|
877
|
+
sections.push(createModelTemplate(expressionSource).trim());
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
sections.push(
|
|
881
|
+
`export const ${expressionName} = rsx(${JSON.stringify(expressionSource)})(model);`,
|
|
882
|
+
);
|
|
883
|
+
return `\n${sections.join('\n\n')}\n`;
|
|
884
|
+
}
|
|
885
|
+
|
|
456
886
|
function createExpressionTemplate(
|
|
457
887
|
expressionName,
|
|
888
|
+
expressionSource,
|
|
458
889
|
modelImportPath,
|
|
459
890
|
modelExportName,
|
|
460
891
|
) {
|
|
461
892
|
return `import { rsx } from '@rs-x/expression-parser';
|
|
462
893
|
import { ${modelExportName} } from '${modelImportPath}';
|
|
463
894
|
|
|
464
|
-
export const ${expressionName} = rsx(
|
|
895
|
+
export const ${expressionName} = rsx(${JSON.stringify(expressionSource)})(${modelExportName});
|
|
465
896
|
`;
|
|
466
897
|
}
|
|
467
898
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
899
|
+
function findExpressionFiles(searchRoot) {
|
|
900
|
+
const results = [];
|
|
901
|
+
const expressionPattern =
|
|
902
|
+
/\b(?:export\s+)?const\s+[A-Za-z_$][\w$]*\s*=\s*rsx\(/u;
|
|
903
|
+
|
|
904
|
+
function visit(currentPath) {
|
|
905
|
+
if (!fs.existsSync(currentPath)) {
|
|
906
|
+
return;
|
|
473
907
|
}
|
|
474
908
|
|
|
475
|
-
|
|
476
|
-
|
|
909
|
+
const stat = fs.statSync(currentPath);
|
|
910
|
+
if (stat.isDirectory()) {
|
|
911
|
+
const baseName = path.basename(currentPath);
|
|
912
|
+
if (
|
|
913
|
+
baseName === 'node_modules' ||
|
|
914
|
+
baseName === 'dist' ||
|
|
915
|
+
baseName === 'build' ||
|
|
916
|
+
baseName === '.git' ||
|
|
917
|
+
baseName === '.next' ||
|
|
918
|
+
baseName === 'coverage' ||
|
|
919
|
+
baseName === '.tests' ||
|
|
920
|
+
baseName === 'tmp'
|
|
921
|
+
) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
for (const entry of fs.readdirSync(currentPath)) {
|
|
926
|
+
visit(path.join(currentPath, entry));
|
|
927
|
+
}
|
|
928
|
+
return;
|
|
477
929
|
}
|
|
478
930
|
|
|
479
|
-
|
|
931
|
+
if (!/\.[cm]?[jt]sx?$/u.test(currentPath)) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const content = fs.readFileSync(currentPath, 'utf8');
|
|
936
|
+
if (
|
|
937
|
+
content.includes("from '@rs-x/expression-parser'") &&
|
|
938
|
+
expressionPattern.test(content)
|
|
939
|
+
) {
|
|
940
|
+
results.push(currentPath);
|
|
941
|
+
}
|
|
480
942
|
}
|
|
943
|
+
|
|
944
|
+
visit(searchRoot);
|
|
945
|
+
return results.sort((left, right) => left.localeCompare(right));
|
|
481
946
|
}
|
|
482
947
|
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
948
|
+
function resolveRsxCliAddConfig(projectRoot) {
|
|
949
|
+
const cliConfig = resolveRsxCliConfig(projectRoot);
|
|
950
|
+
const addConfig = cliConfig.add ?? {};
|
|
951
|
+
const defaultSearchRoots = ['src', 'app', 'expressions'];
|
|
952
|
+
const configuredSearchRoots = Array.isArray(addConfig.searchRoots)
|
|
953
|
+
? addConfig.searchRoots.filter(
|
|
954
|
+
(value) => typeof value === 'string' && value.trim() !== '',
|
|
955
|
+
)
|
|
956
|
+
: defaultSearchRoots;
|
|
957
|
+
const defaultDirectory =
|
|
958
|
+
typeof addConfig.defaultDirectory === 'string' &&
|
|
959
|
+
addConfig.defaultDirectory.trim() !== ''
|
|
960
|
+
? addConfig.defaultDirectory.trim()
|
|
961
|
+
: 'src/expressions';
|
|
962
|
+
|
|
963
|
+
return {
|
|
964
|
+
defaultDirectory,
|
|
965
|
+
searchRoots: configuredSearchRoots,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function filterPreferredExpressionFiles(candidates, projectRoot, addConfig) {
|
|
970
|
+
const preferredRootPrefixes = addConfig.searchRoots
|
|
971
|
+
.map((rootPath) =>
|
|
972
|
+
path.isAbsolute(rootPath) ? rootPath : path.join(projectRoot, rootPath),
|
|
973
|
+
)
|
|
974
|
+
.map((value) => `${value.replace(/\\/gu, '/')}/`);
|
|
975
|
+
|
|
976
|
+
const preferredCandidates = candidates.filter((candidate) => {
|
|
977
|
+
const normalizedCandidate = candidate.replace(/\\/gu, '/');
|
|
978
|
+
return preferredRootPrefixes.some((prefix) =>
|
|
979
|
+
normalizedCandidate.startsWith(prefix),
|
|
980
|
+
);
|
|
487
981
|
});
|
|
488
982
|
|
|
489
|
-
|
|
490
|
-
|
|
983
|
+
return preferredCandidates.length > 0 ? preferredCandidates : candidates;
|
|
984
|
+
}
|
|
491
985
|
|
|
492
|
-
|
|
493
|
-
|
|
986
|
+
function rankExpressionFiles(candidates, resolvedDirectory, projectRoot) {
|
|
987
|
+
const sourceRootPatterns = ['/src/', '/app/', '/expressions/'];
|
|
494
988
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
);
|
|
499
|
-
const resolvedDirectory = path.isAbsolute(directoryInput)
|
|
500
|
-
? directoryInput
|
|
501
|
-
: path.resolve(process.cwd(), directoryInput);
|
|
989
|
+
function score(filePath) {
|
|
990
|
+
const normalized = filePath.replace(/\\/gu, '/');
|
|
991
|
+
let value = 0;
|
|
502
992
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const expressionFileName = ensureTsExtension(baseFileName);
|
|
507
|
-
const expressionFileBase = stripTsLikeExtension(expressionFileName);
|
|
508
|
-
const modelFileName = `${expressionFileBase}.model.ts`;
|
|
509
|
-
const expressionPath = path.join(resolvedDirectory, expressionFileName);
|
|
510
|
-
const modelPath = path.join(resolvedDirectory, modelFileName);
|
|
511
|
-
const useExistingModelAnswer = await rl.question(
|
|
512
|
-
'Use existing model file? [y/N]: ',
|
|
513
|
-
);
|
|
514
|
-
const useExistingModel = normalizeYesNo(useExistingModelAnswer, false);
|
|
993
|
+
if (normalized.startsWith(resolvedDirectory.replace(/\\/gu, '/'))) {
|
|
994
|
+
value += 1000;
|
|
995
|
+
}
|
|
515
996
|
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
(!useExistingModel && fs.existsSync(modelPath))
|
|
519
|
-
) {
|
|
520
|
-
const overwriteAnswer = await rl.question(
|
|
521
|
-
`One or more target files already exist. Overwrite? [y/N]: `,
|
|
522
|
-
);
|
|
523
|
-
const shouldOverwrite = normalizeYesNo(overwriteAnswer, false);
|
|
524
|
-
if (!shouldOverwrite) {
|
|
525
|
-
logInfo('Cancelled. Existing file was not modified.');
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
997
|
+
if (sourceRootPatterns.some((pattern) => normalized.includes(pattern))) {
|
|
998
|
+
value += 100;
|
|
528
999
|
}
|
|
529
1000
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
1001
|
+
const relativeSegments = path
|
|
1002
|
+
.relative(projectRoot, filePath)
|
|
1003
|
+
.replace(/\\/gu, '/')
|
|
1004
|
+
.split('/').length;
|
|
1005
|
+
value -= relativeSegments;
|
|
1006
|
+
return value;
|
|
1007
|
+
}
|
|
533
1008
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
1009
|
+
return [...candidates].sort((left, right) => {
|
|
1010
|
+
const scoreDifference = score(right) - score(left);
|
|
1011
|
+
if (scoreDifference !== 0) {
|
|
1012
|
+
return scoreDifference;
|
|
1013
|
+
}
|
|
1014
|
+
return left.localeCompare(right);
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
542
1017
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1018
|
+
async function askForExistingExpressionFile(rl, resolvedDirectory) {
|
|
1019
|
+
const projectRoot = process.cwd();
|
|
1020
|
+
const addConfig = resolveRsxCliAddConfig(projectRoot);
|
|
1021
|
+
const directoryCandidates = filterPreferredExpressionFiles(
|
|
1022
|
+
findExpressionFiles(resolvedDirectory),
|
|
1023
|
+
projectRoot,
|
|
1024
|
+
addConfig,
|
|
1025
|
+
);
|
|
1026
|
+
const fallbackCandidates =
|
|
1027
|
+
directoryCandidates.length > 0
|
|
1028
|
+
? []
|
|
1029
|
+
: rankExpressionFiles(
|
|
1030
|
+
filterPreferredExpressionFiles(
|
|
1031
|
+
findExpressionFiles(projectRoot),
|
|
1032
|
+
projectRoot,
|
|
1033
|
+
addConfig,
|
|
1034
|
+
),
|
|
1035
|
+
resolvedDirectory,
|
|
1036
|
+
projectRoot,
|
|
1037
|
+
);
|
|
1038
|
+
const candidates =
|
|
1039
|
+
directoryCandidates.length > 0 ? directoryCandidates : fallbackCandidates;
|
|
1040
|
+
|
|
1041
|
+
if (candidates.length > 0) {
|
|
1042
|
+
console.log(
|
|
1043
|
+
directoryCandidates.length > 0
|
|
1044
|
+
? 'Existing expression files in the selected directory:'
|
|
1045
|
+
: 'Existing expression files in the current project:',
|
|
1046
|
+
);
|
|
1047
|
+
candidates.forEach((candidate, index) => {
|
|
1048
|
+
console.log(
|
|
1049
|
+
` ${index + 1}. ${path.relative(process.cwd(), candidate).replace(/\\/gu, '/')}`,
|
|
1050
|
+
);
|
|
1051
|
+
});
|
|
1052
|
+
console.log(' 0. Enter a custom path');
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
while (true) {
|
|
1056
|
+
const answer = (
|
|
1057
|
+
await rl.question(
|
|
1058
|
+
candidates.length > 0
|
|
1059
|
+
? 'Choose a file number or enter a file path: '
|
|
1060
|
+
: 'Existing file path (relative to output dir or absolute): ',
|
|
1061
|
+
)
|
|
1062
|
+
).trim();
|
|
1063
|
+
|
|
1064
|
+
if (!answer && candidates.length > 0) {
|
|
1065
|
+
logWarn('Please choose a file or enter a path.');
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (candidates.length > 0 && /^\d+$/u.test(answer)) {
|
|
1070
|
+
const selectedIndex = Number.parseInt(answer, 10);
|
|
1071
|
+
if (selectedIndex === 0) {
|
|
1072
|
+
const customPath = await askUntilNonEmpty(
|
|
1073
|
+
rl,
|
|
1074
|
+
'Existing file path (relative to output dir or absolute): ',
|
|
1075
|
+
);
|
|
1076
|
+
const resolvedPath = path.isAbsolute(customPath)
|
|
1077
|
+
? customPath
|
|
1078
|
+
: path.resolve(resolvedDirectory, customPath);
|
|
1079
|
+
|
|
1080
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
1081
|
+
logWarn(`File not found: ${resolvedPath}`);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return resolvedPath;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
if (selectedIndex >= 1 && selectedIndex <= candidates.length) {
|
|
1089
|
+
return candidates[selectedIndex - 1];
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
logWarn(`"${answer}" is not a valid file number.`);
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const resolvedPath = path.isAbsolute(answer)
|
|
1097
|
+
? answer
|
|
1098
|
+
: path.resolve(resolvedDirectory, answer);
|
|
1099
|
+
|
|
1100
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
1101
|
+
logWarn(`File not found: ${resolvedPath}`);
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
return resolvedPath;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
async function askForIdentifierWithDefault(rl, prompt, defaultValue) {
|
|
1110
|
+
while (true) {
|
|
1111
|
+
const answer = (await rl.question(prompt)).trim();
|
|
1112
|
+
if (!answer) {
|
|
1113
|
+
return defaultValue;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
if (isValidTsIdentifier(answer)) {
|
|
1117
|
+
return answer;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
logWarn(`"${answer}" is not a valid TypeScript identifier.`);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
async function askForExpressionSource(rl) {
|
|
1125
|
+
while (true) {
|
|
1126
|
+
const answer = (
|
|
1127
|
+
await rl.question("Initial expression string ['a']: ")
|
|
1128
|
+
).trim();
|
|
1129
|
+
|
|
1130
|
+
if (!answer) {
|
|
1131
|
+
return 'a';
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
return answer;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
async function askForDirectoryPath(rl, defaultDirectory) {
|
|
1139
|
+
while (true) {
|
|
1140
|
+
const prompt = defaultDirectory
|
|
1141
|
+
? `Directory path (relative or absolute) [${defaultDirectory}]: `
|
|
1142
|
+
: 'Directory path (relative or absolute): ';
|
|
1143
|
+
const answer = (await rl.question(prompt)).trim();
|
|
1144
|
+
|
|
1145
|
+
if (answer) {
|
|
1146
|
+
return answer;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (defaultDirectory) {
|
|
1150
|
+
return defaultDirectory;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
logWarn('Please enter a directory path.');
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
async function askForAddMode(rl) {
|
|
1158
|
+
console.log('Choose add mode:');
|
|
1159
|
+
console.log(
|
|
1160
|
+
' 1. Create new expression file (model in same file) [Recommended]',
|
|
1161
|
+
);
|
|
1162
|
+
console.log(' 2. Create new expression file with separate model');
|
|
1163
|
+
console.log(' 3. Update an existing expression file');
|
|
1164
|
+
|
|
1165
|
+
while (true) {
|
|
1166
|
+
const answer = (await rl.question('Add mode [1]: ')).trim();
|
|
1167
|
+
|
|
1168
|
+
if (!answer || answer === '1') {
|
|
1169
|
+
return 'create-inline';
|
|
1170
|
+
}
|
|
1171
|
+
if (answer === '2') {
|
|
1172
|
+
return 'create-separate';
|
|
1173
|
+
}
|
|
1174
|
+
if (answer === '3') {
|
|
1175
|
+
return 'update-existing';
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
logWarn(`"${answer}" is not a valid add mode. Use 1, 2, or 3.`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
async function runAdd() {
|
|
1183
|
+
const rl = readline.createInterface({
|
|
1184
|
+
input: process.stdin,
|
|
1185
|
+
output: process.stdout,
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
try {
|
|
1189
|
+
const projectRoot = process.cwd();
|
|
1190
|
+
const addConfig = resolveRsxCliAddConfig(projectRoot);
|
|
1191
|
+
const expressionName = await askUntilValidIdentifier(rl);
|
|
1192
|
+
const expressionSource = await askForExpressionSource(rl);
|
|
1193
|
+
|
|
1194
|
+
const kebabAnswer = await rl.question('Use kebab-case file name? [Y/n]: ');
|
|
1195
|
+
const useKebabCase = normalizeYesNo(kebabAnswer, true);
|
|
1196
|
+
|
|
1197
|
+
const directoryInput = await askForDirectoryPath(
|
|
1198
|
+
rl,
|
|
1199
|
+
addConfig.defaultDirectory,
|
|
1200
|
+
);
|
|
1201
|
+
const resolvedDirectory = path.isAbsolute(directoryInput)
|
|
1202
|
+
? directoryInput
|
|
1203
|
+
: path.resolve(projectRoot, directoryInput);
|
|
1204
|
+
|
|
1205
|
+
const baseFileName = useKebabCase
|
|
1206
|
+
? toKebabCase(expressionName)
|
|
1207
|
+
: expressionName;
|
|
1208
|
+
const expressionFileName = ensureTsExtension(baseFileName);
|
|
1209
|
+
const expressionFileBase = stripTsLikeExtension(expressionFileName);
|
|
1210
|
+
const modelFileName = `${expressionFileBase}.model.ts`;
|
|
1211
|
+
const expressionPath = path.join(resolvedDirectory, expressionFileName);
|
|
1212
|
+
const modelPath = path.join(resolvedDirectory, modelFileName);
|
|
1213
|
+
const addMode = await askForAddMode(rl);
|
|
1214
|
+
const updateExistingFile = addMode === 'update-existing';
|
|
1215
|
+
const keepModelInSameFile =
|
|
1216
|
+
addMode === 'create-inline'
|
|
1217
|
+
? true
|
|
1218
|
+
: addMode === 'create-separate'
|
|
1219
|
+
? false
|
|
1220
|
+
: normalizeYesNo(
|
|
1221
|
+
await rl.question('Keep model in the same file? [Y/n]: '),
|
|
1222
|
+
true,
|
|
1223
|
+
);
|
|
1224
|
+
|
|
1225
|
+
if (updateExistingFile) {
|
|
1226
|
+
const resolvedExistingFilePath = await askForExistingExpressionFile(
|
|
1227
|
+
rl,
|
|
1228
|
+
resolvedDirectory,
|
|
1229
|
+
);
|
|
1230
|
+
const existingFileContent = fs.readFileSync(
|
|
1231
|
+
resolvedExistingFilePath,
|
|
1232
|
+
'utf8',
|
|
1233
|
+
);
|
|
1234
|
+
let appendContent;
|
|
1235
|
+
|
|
1236
|
+
if (keepModelInSameFile) {
|
|
1237
|
+
appendContent = createInlineExpressionAppendTemplate(
|
|
1238
|
+
expressionName,
|
|
1239
|
+
expressionSource,
|
|
1240
|
+
existingFileContent,
|
|
1241
|
+
);
|
|
1242
|
+
} else {
|
|
1243
|
+
const useExistingModelAnswer = await rl.question(
|
|
1244
|
+
'Use existing model file? [y/N]: ',
|
|
1245
|
+
);
|
|
1246
|
+
const useExistingModel = normalizeYesNo(useExistingModelAnswer, false);
|
|
1247
|
+
let modelImportPath = './model';
|
|
1248
|
+
let modelExportName = 'model';
|
|
1249
|
+
|
|
1250
|
+
if (useExistingModel) {
|
|
1251
|
+
const existingModelPathInput = await askUntilNonEmpty(
|
|
1252
|
+
rl,
|
|
1253
|
+
'Existing model file path (relative to output dir or absolute): ',
|
|
1254
|
+
);
|
|
1255
|
+
const resolvedExistingModelPath = path.isAbsolute(
|
|
1256
|
+
existingModelPathInput,
|
|
1257
|
+
)
|
|
1258
|
+
? existingModelPathInput
|
|
1259
|
+
: path.resolve(resolvedDirectory, existingModelPathInput);
|
|
1260
|
+
|
|
1261
|
+
if (!fs.existsSync(resolvedExistingModelPath)) {
|
|
1262
|
+
logError(`Model file not found: ${resolvedExistingModelPath}`);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
modelImportPath = toModuleImportPath(
|
|
1267
|
+
resolvedExistingFilePath,
|
|
1268
|
+
resolvedExistingModelPath,
|
|
1269
|
+
);
|
|
1270
|
+
modelExportName = await askForIdentifierWithDefault(
|
|
1271
|
+
rl,
|
|
1272
|
+
'Model export name [model]: ',
|
|
1273
|
+
'model',
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
appendContent = `\n${createExpressionTemplate(
|
|
1278
|
+
expressionName,
|
|
1279
|
+
expressionSource,
|
|
1280
|
+
modelImportPath,
|
|
1281
|
+
modelExportName,
|
|
1282
|
+
)}`;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
fs.appendFileSync(resolvedExistingFilePath, appendContent, 'utf8');
|
|
1286
|
+
logOk(`Updated ${resolvedExistingFilePath}`);
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const useExistingModel = !keepModelInSameFile
|
|
1291
|
+
? normalizeYesNo(
|
|
1292
|
+
await rl.question('Use existing model file? [y/N]: '),
|
|
1293
|
+
false,
|
|
1294
|
+
)
|
|
1295
|
+
: false;
|
|
1296
|
+
|
|
1297
|
+
if (
|
|
1298
|
+
fs.existsSync(expressionPath) ||
|
|
1299
|
+
(!keepModelInSameFile && !useExistingModel && fs.existsSync(modelPath))
|
|
1300
|
+
) {
|
|
1301
|
+
const overwriteAnswer = await rl.question(
|
|
1302
|
+
`One or more target files already exist. Overwrite? [y/N]: `,
|
|
1303
|
+
);
|
|
1304
|
+
const shouldOverwrite = normalizeYesNo(overwriteAnswer, false);
|
|
1305
|
+
if (!shouldOverwrite) {
|
|
1306
|
+
logInfo('Cancelled. Existing file was not modified.');
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
fs.mkdirSync(resolvedDirectory, { recursive: true });
|
|
1312
|
+
let modelImportPath = `./${expressionFileBase}.model`;
|
|
1313
|
+
let modelExportName = 'model';
|
|
1314
|
+
|
|
1315
|
+
if (keepModelInSameFile) {
|
|
1316
|
+
fs.writeFileSync(
|
|
1317
|
+
expressionPath,
|
|
1318
|
+
createInlineExpressionTemplate(expressionName, expressionSource),
|
|
1319
|
+
'utf8',
|
|
1320
|
+
);
|
|
1321
|
+
logOk(`Created ${expressionPath}`);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
if (useExistingModel) {
|
|
1326
|
+
const existingModelPathInput = await askUntilNonEmpty(
|
|
1327
|
+
rl,
|
|
1328
|
+
'Existing model file path (relative to output dir or absolute): ',
|
|
1329
|
+
);
|
|
1330
|
+
const resolvedExistingModelPath = path.isAbsolute(existingModelPathInput)
|
|
1331
|
+
? existingModelPathInput
|
|
1332
|
+
: path.resolve(resolvedDirectory, existingModelPathInput);
|
|
1333
|
+
|
|
1334
|
+
if (!fs.existsSync(resolvedExistingModelPath)) {
|
|
1335
|
+
logError(`Model file not found: ${resolvedExistingModelPath}`);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
547
1338
|
|
|
548
1339
|
modelImportPath = toModuleImportPath(
|
|
549
1340
|
expressionPath,
|
|
@@ -555,7 +1346,11 @@ async function runAdd() {
|
|
|
555
1346
|
'model',
|
|
556
1347
|
);
|
|
557
1348
|
} else {
|
|
558
|
-
fs.writeFileSync(
|
|
1349
|
+
fs.writeFileSync(
|
|
1350
|
+
modelPath,
|
|
1351
|
+
createModelTemplate(expressionSource),
|
|
1352
|
+
'utf8',
|
|
1353
|
+
);
|
|
559
1354
|
logOk(`Created ${modelPath}`);
|
|
560
1355
|
}
|
|
561
1356
|
|
|
@@ -563,6 +1358,7 @@ async function runAdd() {
|
|
|
563
1358
|
expressionPath,
|
|
564
1359
|
createExpressionTemplate(
|
|
565
1360
|
expressionName,
|
|
1361
|
+
expressionSource,
|
|
566
1362
|
modelImportPath,
|
|
567
1363
|
modelExportName,
|
|
568
1364
|
),
|
|
@@ -585,87 +1381,301 @@ function writeFileWithDryRun(filePath, content, dryRun) {
|
|
|
585
1381
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
586
1382
|
}
|
|
587
1383
|
|
|
588
|
-
function
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1384
|
+
function stripJsonComments(content) {
|
|
1385
|
+
let result = '';
|
|
1386
|
+
let inString = false;
|
|
1387
|
+
let stringDelimiter = '"';
|
|
1388
|
+
let inLineComment = false;
|
|
1389
|
+
let inBlockComment = false;
|
|
1390
|
+
|
|
1391
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
1392
|
+
const current = content[index];
|
|
1393
|
+
const next = content[index + 1];
|
|
1394
|
+
|
|
1395
|
+
if (inLineComment) {
|
|
1396
|
+
if (current === '\n') {
|
|
1397
|
+
inLineComment = false;
|
|
1398
|
+
result += current;
|
|
1399
|
+
}
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
598
1402
|
|
|
599
|
-
|
|
600
|
-
|
|
1403
|
+
if (inBlockComment) {
|
|
1404
|
+
if (current === '*' && next === '/') {
|
|
1405
|
+
inBlockComment = false;
|
|
1406
|
+
index += 1;
|
|
1407
|
+
}
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
601
1410
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
1411
|
+
if (inString) {
|
|
1412
|
+
result += current;
|
|
1413
|
+
if (current === '\\') {
|
|
1414
|
+
index += 1;
|
|
1415
|
+
if (index < content.length) {
|
|
1416
|
+
result += content[index];
|
|
1417
|
+
}
|
|
609
1418
|
continue;
|
|
610
1419
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
entry.isFile() &&
|
|
614
|
-
entry.name.startsWith(`${packageSlug}-`) &&
|
|
615
|
-
entry.name.endsWith('.tgz')
|
|
616
|
-
) {
|
|
617
|
-
candidates.push(fullPath);
|
|
1420
|
+
if (current === stringDelimiter) {
|
|
1421
|
+
inString = false;
|
|
618
1422
|
}
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
if (current === '"' || current === "'") {
|
|
1427
|
+
inString = true;
|
|
1428
|
+
stringDelimiter = current;
|
|
1429
|
+
result += current;
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if (current === '/' && next === '/') {
|
|
1434
|
+
inLineComment = true;
|
|
1435
|
+
index += 1;
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
if (current === '/' && next === '*') {
|
|
1440
|
+
inBlockComment = true;
|
|
1441
|
+
index += 1;
|
|
1442
|
+
continue;
|
|
619
1443
|
}
|
|
1444
|
+
|
|
1445
|
+
result += current;
|
|
620
1446
|
}
|
|
621
1447
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1448
|
+
return result;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function parseJsonc(content) {
|
|
1452
|
+
return JSON.parse(stripJsonComments(content.replace(/^\uFEFF/u, '')));
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
function copyPathWithDryRun(sourcePath, targetPath, dryRun) {
|
|
1456
|
+
if (dryRun) {
|
|
1457
|
+
logInfo(`[dry-run] copy ${sourcePath} -> ${targetPath}`);
|
|
1458
|
+
return;
|
|
625
1459
|
}
|
|
626
1460
|
|
|
627
|
-
|
|
1461
|
+
const stat = fs.statSync(sourcePath);
|
|
1462
|
+
if (stat.isDirectory()) {
|
|
1463
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
1464
|
+
for (const entry of fs.readdirSync(sourcePath, { withFileTypes: true })) {
|
|
1465
|
+
copyPathWithDryRun(
|
|
1466
|
+
path.join(sourcePath, entry.name),
|
|
1467
|
+
path.join(targetPath, entry.name),
|
|
1468
|
+
false,
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
1475
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
628
1476
|
}
|
|
629
1477
|
|
|
630
|
-
function
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
options = {},
|
|
635
|
-
) {
|
|
636
|
-
const includeAngularPackage = Boolean(options.includeAngularPackage);
|
|
637
|
-
const versionSpec = options.tag ? options.tag : RSX_PACKAGE_VERSION;
|
|
638
|
-
const defaults = {
|
|
639
|
-
'@rs-x/core': versionSpec,
|
|
640
|
-
'@rs-x/state-manager': versionSpec,
|
|
641
|
-
'@rs-x/expression-parser': versionSpec,
|
|
642
|
-
'@rs-x/compiler': versionSpec,
|
|
643
|
-
'@rs-x/typescript-plugin': versionSpec,
|
|
644
|
-
...(includeAngularPackage ? { '@rs-x/angular': versionSpec } : {}),
|
|
645
|
-
'@rs-x/cli': null,
|
|
646
|
-
};
|
|
1478
|
+
function removeFileOrDirectoryWithDryRun(targetPath, dryRun) {
|
|
1479
|
+
if (!fs.existsSync(targetPath)) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
647
1482
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
'@rs-x/compiler': 'rs-x-compiler',
|
|
653
|
-
'@rs-x/typescript-plugin': 'rs-x-typescript-plugin',
|
|
654
|
-
...(includeAngularPackage ? { '@rs-x/angular': 'rs-x-angular' } : {}),
|
|
655
|
-
'@rs-x/cli': 'rs-x-cli',
|
|
656
|
-
};
|
|
1483
|
+
if (dryRun) {
|
|
1484
|
+
logInfo(`[dry-run] remove ${targetPath}`);
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
657
1487
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1488
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function resolveProjectTsConfig(projectRoot) {
|
|
1492
|
+
const appTsConfigPath = path.join(projectRoot, 'tsconfig.app.json');
|
|
1493
|
+
if (fs.existsSync(appTsConfigPath)) {
|
|
1494
|
+
return appTsConfigPath;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
return path.join(projectRoot, 'tsconfig.json');
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function resolveAngularProjectTsConfig(projectRoot) {
|
|
1501
|
+
return resolveProjectTsConfig(projectRoot);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
function upsertTypescriptPluginInTsConfig(configPath, dryRun) {
|
|
1505
|
+
if (!fs.existsSync(configPath)) {
|
|
1506
|
+
logWarn(`TypeScript config not found: ${configPath}`);
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const tsConfig = parseJsonc(fs.readFileSync(configPath, 'utf8'));
|
|
1511
|
+
const compilerOptions = tsConfig.compilerOptions ?? {};
|
|
1512
|
+
const plugins = Array.isArray(compilerOptions.plugins)
|
|
1513
|
+
? compilerOptions.plugins
|
|
1514
|
+
: [];
|
|
1515
|
+
|
|
1516
|
+
if (
|
|
1517
|
+
!plugins.some(
|
|
1518
|
+
(plugin) =>
|
|
1519
|
+
plugin &&
|
|
1520
|
+
typeof plugin === 'object' &&
|
|
1521
|
+
plugin.name === '@rs-x/typescript-plugin',
|
|
1522
|
+
)
|
|
1523
|
+
) {
|
|
1524
|
+
plugins.push({ name: '@rs-x/typescript-plugin' });
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
compilerOptions.plugins = plugins;
|
|
1528
|
+
tsConfig.compilerOptions = compilerOptions;
|
|
1529
|
+
|
|
1530
|
+
if (dryRun) {
|
|
1531
|
+
logInfo(`[dry-run] patch ${configPath}`);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
fs.writeFileSync(
|
|
1536
|
+
configPath,
|
|
1537
|
+
`${JSON.stringify(tsConfig, null, 2)}\n`,
|
|
1538
|
+
'utf8',
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function ensureTsConfigIncludePattern(configPath, pattern, dryRun) {
|
|
1543
|
+
if (!fs.existsSync(configPath)) {
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const tsConfig = parseJsonc(fs.readFileSync(configPath, 'utf8'));
|
|
1548
|
+
const include = Array.isArray(tsConfig.include) ? tsConfig.include : [];
|
|
1549
|
+
if (!include.includes(pattern)) {
|
|
1550
|
+
include.push(pattern);
|
|
1551
|
+
}
|
|
1552
|
+
tsConfig.include = include;
|
|
1553
|
+
|
|
1554
|
+
if (dryRun) {
|
|
1555
|
+
logInfo(`[dry-run] patch ${configPath}`);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
fs.writeFileSync(
|
|
1560
|
+
configPath,
|
|
1561
|
+
`${JSON.stringify(tsConfig, null, 2)}\n`,
|
|
1562
|
+
'utf8',
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function ensureVueEnvTypes(projectRoot, dryRun) {
|
|
1567
|
+
const envTypesPath = path.join(projectRoot, 'src', 'vite-env.d.ts');
|
|
1568
|
+
const envTypesSource = `/// <reference types="vite/client" />
|
|
1569
|
+
|
|
1570
|
+
declare module '*.vue' {
|
|
1571
|
+
import type { DefineComponent } from 'vue';
|
|
1572
|
+
|
|
1573
|
+
const component: DefineComponent<{}, {}, any>;
|
|
1574
|
+
export default component;
|
|
1575
|
+
}
|
|
1576
|
+
`;
|
|
1577
|
+
|
|
1578
|
+
if (fs.existsSync(envTypesPath)) {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
if (dryRun) {
|
|
1583
|
+
logInfo(`[dry-run] create ${envTypesPath}`);
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
fs.mkdirSync(path.dirname(envTypesPath), { recursive: true });
|
|
1588
|
+
fs.writeFileSync(envTypesPath, envTypesSource, 'utf8');
|
|
1589
|
+
logOk(`Created ${envTypesPath}`);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
function toFileDependencySpec(fromDir, targetPath) {
|
|
1593
|
+
const relative = path.relative(fromDir, targetPath).replace(/\\/gu, '/');
|
|
1594
|
+
const normalized = relative.startsWith('.') ? relative : `./${relative}`;
|
|
1595
|
+
return `file:${normalized}`;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
function findLatestTarball(packageDir, packageSlug) {
|
|
1599
|
+
if (!fs.existsSync(packageDir)) {
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
const candidates = [];
|
|
1604
|
+
const stack = [packageDir];
|
|
1605
|
+
|
|
1606
|
+
while (stack.length > 0) {
|
|
1607
|
+
const currentDir = stack.pop();
|
|
1608
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
1609
|
+
for (const entry of entries) {
|
|
1610
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
1611
|
+
if (entry.isDirectory()) {
|
|
1612
|
+
stack.push(fullPath);
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (
|
|
1617
|
+
entry.isFile() &&
|
|
1618
|
+
entry.name.startsWith(`${packageSlug}-`) &&
|
|
1619
|
+
entry.name.endsWith('.tgz')
|
|
1620
|
+
) {
|
|
1621
|
+
candidates.push(fullPath);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
candidates.sort();
|
|
1627
|
+
if (candidates.length === 0) {
|
|
1628
|
+
return null;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
return candidates[candidates.length - 1];
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function resolveProjectRsxSpecs(
|
|
1635
|
+
projectRoot,
|
|
1636
|
+
workspaceRoot,
|
|
1637
|
+
tarballsDir,
|
|
1638
|
+
options = {},
|
|
1639
|
+
) {
|
|
1640
|
+
const includeAngularPackage = Boolean(options.includeAngularPackage);
|
|
1641
|
+
const includeReactPackage = Boolean(options.includeReactPackage);
|
|
1642
|
+
const includeVuePackage = Boolean(options.includeVuePackage);
|
|
1643
|
+
const versionSpec = options.tag ? options.tag : RSX_PACKAGE_VERSION;
|
|
1644
|
+
const defaults = {
|
|
1645
|
+
'@rs-x/core': versionSpec,
|
|
1646
|
+
'@rs-x/state-manager': versionSpec,
|
|
1647
|
+
'@rs-x/expression-parser': versionSpec,
|
|
1648
|
+
'@rs-x/compiler': versionSpec,
|
|
1649
|
+
'@rs-x/typescript-plugin': versionSpec,
|
|
1650
|
+
...(includeAngularPackage ? { '@rs-x/angular': versionSpec } : {}),
|
|
1651
|
+
...(includeReactPackage ? { '@rs-x/react': versionSpec } : {}),
|
|
1652
|
+
...(includeVuePackage ? { '@rs-x/vue': versionSpec } : {}),
|
|
1653
|
+
'@rs-x/cli': versionSpec,
|
|
1654
|
+
};
|
|
1655
|
+
|
|
1656
|
+
const tarballSlugs = {
|
|
1657
|
+
'@rs-x/core': 'rs-x-core',
|
|
1658
|
+
'@rs-x/state-manager': 'rs-x-state-manager',
|
|
1659
|
+
'@rs-x/expression-parser': 'rs-x-expression-parser',
|
|
1660
|
+
'@rs-x/compiler': 'rs-x-compiler',
|
|
1661
|
+
'@rs-x/typescript-plugin': 'rs-x-typescript-plugin',
|
|
1662
|
+
...(includeAngularPackage ? { '@rs-x/angular': 'rs-x-angular' } : {}),
|
|
1663
|
+
...(includeReactPackage ? { '@rs-x/react': 'rs-x-react' } : {}),
|
|
1664
|
+
...(includeVuePackage ? { '@rs-x/vue': 'rs-x-vue' } : {}),
|
|
1665
|
+
'@rs-x/cli': 'rs-x-cli',
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1668
|
+
if (tarballsDir) {
|
|
1669
|
+
const specs = { ...defaults };
|
|
1670
|
+
const packageDirBySlug = {
|
|
1671
|
+
'rs-x-core': path.join(tarballsDir, 'rs-x-core'),
|
|
1672
|
+
'rs-x-state-manager': path.join(tarballsDir, 'rs-x-state-manager'),
|
|
1673
|
+
'rs-x-expression-parser': path.join(
|
|
1674
|
+
tarballsDir,
|
|
1675
|
+
'rs-x-expression-parser',
|
|
1676
|
+
),
|
|
1677
|
+
'rs-x-compiler': path.join(tarballsDir, 'rs-x-compiler'),
|
|
1678
|
+
'rs-x-typescript-plugin': path.join(
|
|
669
1679
|
tarballsDir,
|
|
670
1680
|
'rs-x-typescript-plugin',
|
|
671
1681
|
),
|
|
@@ -674,6 +1684,16 @@ function resolveProjectRsxSpecs(
|
|
|
674
1684
|
'rs-x-angular': path.join(tarballsDir, 'rs-x-angular'),
|
|
675
1685
|
}
|
|
676
1686
|
: {}),
|
|
1687
|
+
...(includeReactPackage
|
|
1688
|
+
? {
|
|
1689
|
+
'rs-x-react': path.join(tarballsDir, 'rs-x-react'),
|
|
1690
|
+
}
|
|
1691
|
+
: {}),
|
|
1692
|
+
...(includeVuePackage
|
|
1693
|
+
? {
|
|
1694
|
+
'rs-x-vue': path.join(tarballsDir, 'rs-x-vue'),
|
|
1695
|
+
}
|
|
1696
|
+
: {}),
|
|
677
1697
|
'rs-x-cli': path.join(tarballsDir, 'rs-x-cli'),
|
|
678
1698
|
};
|
|
679
1699
|
|
|
@@ -711,10 +1731,21 @@ function resolveProjectRsxSpecs(
|
|
|
711
1731
|
),
|
|
712
1732
|
...(includeAngularPackage
|
|
713
1733
|
? {
|
|
714
|
-
'@rs-x/angular':
|
|
715
|
-
workspaceRoot,
|
|
716
|
-
|
|
717
|
-
|
|
1734
|
+
'@rs-x/angular': fs.existsSync(
|
|
1735
|
+
path.join(workspaceRoot, 'rs-x-angular/dist/rsx'),
|
|
1736
|
+
)
|
|
1737
|
+
? path.join(workspaceRoot, 'rs-x-angular/dist/rsx')
|
|
1738
|
+
: path.join(workspaceRoot, 'rs-x-angular/projects/rsx'),
|
|
1739
|
+
}
|
|
1740
|
+
: {}),
|
|
1741
|
+
...(includeReactPackage
|
|
1742
|
+
? {
|
|
1743
|
+
'@rs-x/react': path.join(workspaceRoot, 'rs-x-react'),
|
|
1744
|
+
}
|
|
1745
|
+
: {}),
|
|
1746
|
+
...(includeVuePackage
|
|
1747
|
+
? {
|
|
1748
|
+
'@rs-x/vue': path.join(workspaceRoot, 'rs-x-vue'),
|
|
718
1749
|
}
|
|
719
1750
|
: {}),
|
|
720
1751
|
'@rs-x/cli': path.join(workspaceRoot, 'rs-x-cli'),
|
|
@@ -777,6 +1808,22 @@ function createProjectPackageJson(projectName, rsxSpecs) {
|
|
|
777
1808
|
);
|
|
778
1809
|
}
|
|
779
1810
|
|
|
1811
|
+
function resolveProjectRoot(projectName, flags) {
|
|
1812
|
+
const parentDir =
|
|
1813
|
+
typeof flags?.['project-parent-dir'] === 'string'
|
|
1814
|
+
? flags['project-parent-dir']
|
|
1815
|
+
: typeof process.env.RSX_PROJECT_PARENT_DIR === 'string' &&
|
|
1816
|
+
process.env.RSX_PROJECT_PARENT_DIR.trim().length > 0
|
|
1817
|
+
? process.env.RSX_PROJECT_PARENT_DIR
|
|
1818
|
+
: null;
|
|
1819
|
+
|
|
1820
|
+
if (parentDir) {
|
|
1821
|
+
return path.resolve(parentDir, projectName);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
return path.resolve(process.cwd(), projectName);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
780
1827
|
function createProjectTsConfig() {
|
|
781
1828
|
return (
|
|
782
1829
|
JSON.stringify(
|
|
@@ -945,8 +1992,9 @@ function applyVueRsxTemplate(projectRoot, dryRun) {
|
|
|
945
1992
|
async function runProject(flags) {
|
|
946
1993
|
const dryRun = Boolean(flags['dry-run']);
|
|
947
1994
|
const skipInstall = Boolean(flags['skip-install']);
|
|
948
|
-
const
|
|
949
|
-
const
|
|
1995
|
+
const invocationRoot = process.cwd();
|
|
1996
|
+
const pm = resolveCliPackageManager(invocationRoot, flags.pm);
|
|
1997
|
+
const tag = resolveConfiguredInstallTag(invocationRoot, flags);
|
|
950
1998
|
let projectName = typeof flags.name === 'string' ? flags.name.trim() : '';
|
|
951
1999
|
|
|
952
2000
|
if (!projectName) {
|
|
@@ -961,7 +2009,7 @@ async function runProject(flags) {
|
|
|
961
2009
|
}
|
|
962
2010
|
}
|
|
963
2011
|
|
|
964
|
-
const projectRoot =
|
|
2012
|
+
const projectRoot = resolveProjectRoot(projectName, flags);
|
|
965
2013
|
const tarballsDir =
|
|
966
2014
|
typeof flags['tarballs-dir'] === 'string'
|
|
967
2015
|
? path.resolve(process.cwd(), flags['tarballs-dir'])
|
|
@@ -1051,135 +2099,676 @@ export const sampleExpression = rsx('a + b')(model);
|
|
|
1051
2099
|
`import { sampleExpression } from './expressions/sample.expression';
|
|
1052
2100
|
import { initRsx } from './rsx-bootstrap';
|
|
1053
2101
|
|
|
1054
|
-
async function main(): Promise<void> {
|
|
1055
|
-
await initRsx();
|
|
1056
|
-
console.log('RS-X sample expression initialized:', Boolean(sampleExpression));
|
|
1057
|
-
}
|
|
2102
|
+
async function main(): Promise<void> {
|
|
2103
|
+
await initRsx();
|
|
2104
|
+
console.log('RS-X sample expression initialized:', Boolean(sampleExpression));
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
void main();
|
|
2108
|
+
`,
|
|
2109
|
+
dryRun,
|
|
2110
|
+
);
|
|
2111
|
+
|
|
2112
|
+
if (!skipInstall) {
|
|
2113
|
+
const installArgsByPm = {
|
|
2114
|
+
pnpm: ['install'],
|
|
2115
|
+
npm: ['install'],
|
|
2116
|
+
yarn: ['install'],
|
|
2117
|
+
bun: ['install'],
|
|
2118
|
+
};
|
|
2119
|
+
const installArgs = installArgsByPm[pm];
|
|
2120
|
+
if (!installArgs) {
|
|
2121
|
+
logError(`Unsupported package manager: ${pm}`);
|
|
2122
|
+
process.exit(1);
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
logInfo(`Installing dependencies with ${pm}...`);
|
|
2126
|
+
run(pm, installArgs, { dryRun, cwd: projectRoot });
|
|
2127
|
+
logOk('Dependencies installed.');
|
|
2128
|
+
} else {
|
|
2129
|
+
logInfo('Skipping dependency install (--skip-install).');
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
logOk(`Created RS-X project: ${projectRoot}`);
|
|
2133
|
+
logInfo('Next steps:');
|
|
2134
|
+
console.log(` cd ${projectName}`);
|
|
2135
|
+
if (skipInstall) {
|
|
2136
|
+
console.log(' npm install');
|
|
2137
|
+
}
|
|
2138
|
+
console.log(' npm run build');
|
|
2139
|
+
console.log(' npm run start');
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
async function resolveProjectName(nameFromFlags, fallbackName) {
|
|
2143
|
+
const fromFlags =
|
|
2144
|
+
typeof nameFromFlags === 'string' ? nameFromFlags.trim() : '';
|
|
2145
|
+
if (fromFlags.length > 0) {
|
|
2146
|
+
return fromFlags;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
const fromFallback =
|
|
2150
|
+
typeof fallbackName === 'string' ? fallbackName.trim() : '';
|
|
2151
|
+
if (fromFallback.length > 0) {
|
|
2152
|
+
return fromFallback;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
const rl = readline.createInterface({
|
|
2156
|
+
input: process.stdin,
|
|
2157
|
+
output: process.stdout,
|
|
2158
|
+
});
|
|
2159
|
+
try {
|
|
2160
|
+
return await askUntilNonEmpty(rl, 'Project name: ');
|
|
2161
|
+
} finally {
|
|
2162
|
+
rl.close();
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
function scaffoldProjectTemplate(
|
|
2167
|
+
template,
|
|
2168
|
+
projectName,
|
|
2169
|
+
projectRoot,
|
|
2170
|
+
pm,
|
|
2171
|
+
flags,
|
|
2172
|
+
) {
|
|
2173
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
2174
|
+
const skipInstall = Boolean(flags['skip-install']);
|
|
2175
|
+
const scaffoldCwd = path.dirname(projectRoot);
|
|
2176
|
+
const scaffoldProjectArg = `./${projectName}`;
|
|
2177
|
+
const scaffoldEnv = {
|
|
2178
|
+
INIT_CWD: scaffoldCwd,
|
|
2179
|
+
npm_config_local_prefix: scaffoldCwd,
|
|
2180
|
+
npm_prefix: scaffoldCwd,
|
|
2181
|
+
PWD: scaffoldCwd,
|
|
2182
|
+
CI: 'true',
|
|
2183
|
+
};
|
|
2184
|
+
|
|
2185
|
+
if (template === 'angular') {
|
|
2186
|
+
const args = [
|
|
2187
|
+
'-y',
|
|
2188
|
+
'@angular/cli@latest',
|
|
2189
|
+
'new',
|
|
2190
|
+
projectName,
|
|
2191
|
+
'--directory',
|
|
2192
|
+
scaffoldProjectArg,
|
|
2193
|
+
'--defaults',
|
|
2194
|
+
'--standalone',
|
|
2195
|
+
'--routing',
|
|
2196
|
+
'--style',
|
|
2197
|
+
'css',
|
|
2198
|
+
'--skip-git',
|
|
2199
|
+
];
|
|
2200
|
+
if (skipInstall) {
|
|
2201
|
+
args.push('--skip-install');
|
|
2202
|
+
}
|
|
2203
|
+
run('npx', args, { dryRun, cwd: scaffoldCwd, env: scaffoldEnv });
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
if (template === 'react') {
|
|
2208
|
+
run(
|
|
2209
|
+
'npx',
|
|
2210
|
+
[
|
|
2211
|
+
'create-vite@latest',
|
|
2212
|
+
scaffoldProjectArg,
|
|
2213
|
+
'--no-interactive',
|
|
2214
|
+
'--template',
|
|
2215
|
+
'react-ts',
|
|
2216
|
+
],
|
|
2217
|
+
{
|
|
2218
|
+
dryRun,
|
|
2219
|
+
cwd: scaffoldCwd,
|
|
2220
|
+
env: scaffoldEnv,
|
|
2221
|
+
},
|
|
2222
|
+
);
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
if (template === 'vuejs') {
|
|
2227
|
+
run(
|
|
2228
|
+
'npx',
|
|
2229
|
+
[
|
|
2230
|
+
'create-vite@latest',
|
|
2231
|
+
scaffoldProjectArg,
|
|
2232
|
+
'--no-interactive',
|
|
2233
|
+
'--template',
|
|
2234
|
+
'vue-ts',
|
|
2235
|
+
],
|
|
2236
|
+
{
|
|
2237
|
+
dryRun,
|
|
2238
|
+
cwd: scaffoldCwd,
|
|
2239
|
+
env: scaffoldEnv,
|
|
2240
|
+
},
|
|
2241
|
+
);
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
if (template === 'nextjs') {
|
|
2246
|
+
const packageManagerFlagByPm = {
|
|
2247
|
+
npm: '--use-npm',
|
|
2248
|
+
pnpm: '--use-pnpm',
|
|
2249
|
+
yarn: '--use-yarn',
|
|
2250
|
+
bun: '--use-bun',
|
|
2251
|
+
};
|
|
2252
|
+
const args = [
|
|
2253
|
+
'create-next-app@latest',
|
|
2254
|
+
scaffoldProjectArg,
|
|
2255
|
+
'--yes',
|
|
2256
|
+
'--ts',
|
|
2257
|
+
'--app',
|
|
2258
|
+
'--eslint',
|
|
2259
|
+
'--import-alias',
|
|
2260
|
+
'@/*',
|
|
2261
|
+
packageManagerFlagByPm[pm] ?? '--use-npm',
|
|
2262
|
+
];
|
|
2263
|
+
if (skipInstall) {
|
|
2264
|
+
args.push('--skip-install');
|
|
2265
|
+
}
|
|
2266
|
+
run('npx', args, { dryRun, cwd: scaffoldCwd, env: scaffoldEnv });
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
logError(`Unknown project template: ${template}`);
|
|
2271
|
+
process.exit(1);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
function applyAngularDemoStarter(projectRoot, projectName, pm, flags) {
|
|
2275
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
2276
|
+
ensureRsxConfigFile(projectRoot, 'angular', dryRun);
|
|
2277
|
+
const tag = resolveInstallTag(flags);
|
|
2278
|
+
const tarballsDir =
|
|
2279
|
+
typeof flags['tarballs-dir'] === 'string'
|
|
2280
|
+
? path.resolve(process.cwd(), flags['tarballs-dir'])
|
|
2281
|
+
: typeof process.env.RSX_TARBALLS_DIR === 'string' &&
|
|
2282
|
+
process.env.RSX_TARBALLS_DIR.trim().length > 0
|
|
2283
|
+
? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
|
|
2284
|
+
: null;
|
|
2285
|
+
const workspaceRoot = findRepoRoot(projectRoot);
|
|
2286
|
+
const rsxSpecs = resolveProjectRsxSpecs(
|
|
2287
|
+
projectRoot,
|
|
2288
|
+
workspaceRoot,
|
|
2289
|
+
tarballsDir,
|
|
2290
|
+
{ tag, includeAngularPackage: true },
|
|
2291
|
+
);
|
|
2292
|
+
|
|
2293
|
+
const templateFiles = ['README.md', 'src'];
|
|
2294
|
+
for (const entry of templateFiles) {
|
|
2295
|
+
copyPathWithDryRun(
|
|
2296
|
+
path.join(ANGULAR_DEMO_TEMPLATE_DIR, entry),
|
|
2297
|
+
path.join(projectRoot, entry),
|
|
2298
|
+
dryRun,
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
const staleAngularFiles = [
|
|
2303
|
+
path.join(projectRoot, 'src/app/app.ts'),
|
|
2304
|
+
path.join(projectRoot, 'src/app/app.spec.ts'),
|
|
2305
|
+
path.join(projectRoot, 'src/app/app.html'),
|
|
2306
|
+
path.join(projectRoot, 'src/app/app.css'),
|
|
2307
|
+
path.join(projectRoot, 'src/app/app.routes.ts'),
|
|
2308
|
+
path.join(projectRoot, 'src/app/app.config.ts'),
|
|
2309
|
+
];
|
|
2310
|
+
for (const stalePath of staleAngularFiles) {
|
|
2311
|
+
removeFileOrDirectoryWithDryRun(stalePath, dryRun);
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
const readmePath = path.join(projectRoot, 'README.md');
|
|
2315
|
+
if (fs.existsSync(readmePath)) {
|
|
2316
|
+
const readmeSource = fs.readFileSync(readmePath, 'utf8');
|
|
2317
|
+
const nextReadme = readmeSource.replace(
|
|
2318
|
+
/^#\s+rsx-angular-example/mu,
|
|
2319
|
+
`# ${projectName}`,
|
|
2320
|
+
);
|
|
2321
|
+
if (dryRun) {
|
|
2322
|
+
logInfo(`[dry-run] patch ${readmePath}`);
|
|
2323
|
+
} else {
|
|
2324
|
+
fs.writeFileSync(readmePath, nextReadme, 'utf8');
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2329
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2330
|
+
logError(
|
|
2331
|
+
`package.json not found in generated Angular app: ${packageJsonPath}`,
|
|
2332
|
+
);
|
|
2333
|
+
process.exit(1);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2337
|
+
const angularTsConfigPath = resolveAngularProjectTsConfig(projectRoot);
|
|
2338
|
+
const angularTsConfigRelative = path
|
|
2339
|
+
.relative(projectRoot, angularTsConfigPath)
|
|
2340
|
+
.replace(/\\/gu, '/');
|
|
2341
|
+
packageJson.name = projectName;
|
|
2342
|
+
packageJson.private = true;
|
|
2343
|
+
packageJson.version = '0.1.0';
|
|
2344
|
+
packageJson.scripts = {
|
|
2345
|
+
'build:rsx': `rsx build --project ${angularTsConfigRelative} --no-emit --prod`,
|
|
2346
|
+
'typecheck:rsx': `rsx typecheck --project ${angularTsConfigRelative}`,
|
|
2347
|
+
prebuild: 'npm run build:rsx',
|
|
2348
|
+
start: 'npm run build:rsx && ng serve',
|
|
2349
|
+
build: 'ng build',
|
|
2350
|
+
};
|
|
2351
|
+
packageJson.dependencies = {
|
|
2352
|
+
...(packageJson.dependencies ?? {}),
|
|
2353
|
+
'@rs-x/angular': rsxSpecs['@rs-x/angular'],
|
|
2354
|
+
'@rs-x/core': rsxSpecs['@rs-x/core'],
|
|
2355
|
+
'@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
|
|
2356
|
+
'@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
|
|
2357
|
+
};
|
|
2358
|
+
packageJson.devDependencies = {
|
|
2359
|
+
...(packageJson.devDependencies ?? {}),
|
|
2360
|
+
'@rs-x/cli': rsxSpecs['@rs-x/cli'],
|
|
2361
|
+
'@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
|
|
2362
|
+
'@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
|
|
2363
|
+
};
|
|
2364
|
+
|
|
2365
|
+
if (dryRun) {
|
|
2366
|
+
logInfo(`[dry-run] patch ${packageJsonPath}`);
|
|
2367
|
+
} else {
|
|
2368
|
+
fs.writeFileSync(
|
|
2369
|
+
packageJsonPath,
|
|
2370
|
+
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
2371
|
+
'utf8',
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
const angularJsonPath = path.join(projectRoot, 'angular.json');
|
|
2376
|
+
if (!fs.existsSync(angularJsonPath)) {
|
|
2377
|
+
logError(
|
|
2378
|
+
`angular.json not found in generated Angular app: ${angularJsonPath}`,
|
|
2379
|
+
);
|
|
2380
|
+
process.exit(1);
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8'));
|
|
2384
|
+
const projects = angularJson.projects ?? {};
|
|
2385
|
+
const [angularProjectName] = Object.keys(projects);
|
|
2386
|
+
if (!angularProjectName) {
|
|
2387
|
+
logError('Generated angular.json does not define any projects.');
|
|
2388
|
+
process.exit(1);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
const angularProject = projects[angularProjectName];
|
|
2392
|
+
const architect = angularProject.architect ?? angularProject.targets;
|
|
2393
|
+
const build = architect?.build;
|
|
2394
|
+
if (!build) {
|
|
2395
|
+
logError('Generated Angular project is missing a build target.');
|
|
2396
|
+
process.exit(1);
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
const buildOptions = build.options ?? {};
|
|
2400
|
+
const styles = Array.isArray(buildOptions.styles) ? buildOptions.styles : [];
|
|
2401
|
+
if (!styles.includes('src/styles.css')) {
|
|
2402
|
+
styles.push('src/styles.css');
|
|
2403
|
+
}
|
|
2404
|
+
buildOptions.styles = styles;
|
|
2405
|
+
buildOptions.preserveSymlinks = true;
|
|
2406
|
+
|
|
2407
|
+
const registrationFile =
|
|
2408
|
+
'src/rsx-generated/rsx-aot-registration.generated.ts';
|
|
2409
|
+
let polyfills = buildOptions.polyfills;
|
|
2410
|
+
if (typeof polyfills === 'string') {
|
|
2411
|
+
polyfills = [polyfills];
|
|
2412
|
+
} else if (!Array.isArray(polyfills)) {
|
|
2413
|
+
polyfills = [];
|
|
2414
|
+
}
|
|
2415
|
+
if (!polyfills.includes(registrationFile)) {
|
|
2416
|
+
polyfills.push(registrationFile);
|
|
2417
|
+
}
|
|
2418
|
+
buildOptions.polyfills = polyfills;
|
|
2419
|
+
build.options = buildOptions;
|
|
2420
|
+
|
|
2421
|
+
if (build.configurations?.production?.budgets) {
|
|
2422
|
+
delete build.configurations.production.budgets;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
if (dryRun) {
|
|
2426
|
+
logInfo(`[dry-run] patch ${angularJsonPath}`);
|
|
2427
|
+
} else {
|
|
2428
|
+
fs.writeFileSync(
|
|
2429
|
+
angularJsonPath,
|
|
2430
|
+
`${JSON.stringify(angularJson, null, 2)}\n`,
|
|
2431
|
+
'utf8',
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
if (!Boolean(flags['skip-install'])) {
|
|
2436
|
+
logInfo(`Refreshing ${pm} dependencies for the RS-X Angular starter...`);
|
|
2437
|
+
run(pm, ['install'], { dryRun });
|
|
2438
|
+
logOk('Angular starter dependencies are up to date.');
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
|
|
2443
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
2444
|
+
ensureRsxConfigFile(projectRoot, 'react', dryRun);
|
|
2445
|
+
const tag = resolveInstallTag(flags);
|
|
2446
|
+
const tarballsDir =
|
|
2447
|
+
typeof flags['tarballs-dir'] === 'string'
|
|
2448
|
+
? path.resolve(process.cwd(), flags['tarballs-dir'])
|
|
2449
|
+
: typeof process.env.RSX_TARBALLS_DIR === 'string' &&
|
|
2450
|
+
process.env.RSX_TARBALLS_DIR.trim().length > 0
|
|
2451
|
+
? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
|
|
2452
|
+
: null;
|
|
2453
|
+
const workspaceRoot = findRepoRoot(projectRoot);
|
|
2454
|
+
const rsxSpecs = resolveProjectRsxSpecs(
|
|
2455
|
+
projectRoot,
|
|
2456
|
+
workspaceRoot,
|
|
2457
|
+
tarballsDir,
|
|
2458
|
+
{ tag, includeReactPackage: true },
|
|
2459
|
+
);
|
|
2460
|
+
|
|
2461
|
+
const templateFiles = [
|
|
2462
|
+
'README.md',
|
|
2463
|
+
'index.html',
|
|
2464
|
+
'src',
|
|
2465
|
+
'tsconfig.json',
|
|
2466
|
+
'vite.config.ts',
|
|
2467
|
+
];
|
|
2468
|
+
for (const entry of templateFiles) {
|
|
2469
|
+
copyPathWithDryRun(
|
|
2470
|
+
path.join(REACT_DEMO_TEMPLATE_DIR, entry),
|
|
2471
|
+
path.join(projectRoot, entry),
|
|
2472
|
+
dryRun,
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
const staleReactFiles = [
|
|
2477
|
+
path.join(projectRoot, 'src/App.tsx'),
|
|
2478
|
+
path.join(projectRoot, 'src/App.css'),
|
|
2479
|
+
path.join(projectRoot, 'src/index.css'),
|
|
2480
|
+
path.join(projectRoot, 'src/vite-env.d.ts'),
|
|
2481
|
+
path.join(projectRoot, 'src/assets'),
|
|
2482
|
+
path.join(projectRoot, 'public'),
|
|
2483
|
+
path.join(projectRoot, 'eslint.config.js'),
|
|
2484
|
+
path.join(projectRoot, 'eslint.config.ts'),
|
|
2485
|
+
path.join(projectRoot, 'tsconfig.app.json'),
|
|
2486
|
+
path.join(projectRoot, 'tsconfig.node.json'),
|
|
2487
|
+
];
|
|
2488
|
+
for (const stalePath of staleReactFiles) {
|
|
2489
|
+
removeFileOrDirectoryWithDryRun(stalePath, dryRun);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
const readmePath = path.join(projectRoot, 'README.md');
|
|
2493
|
+
if (fs.existsSync(readmePath)) {
|
|
2494
|
+
const readmeSource = fs.readFileSync(readmePath, 'utf8');
|
|
2495
|
+
const nextReadme = readmeSource.replace(
|
|
2496
|
+
/^#\s+rsx-react-example/mu,
|
|
2497
|
+
`# ${projectName}`,
|
|
2498
|
+
);
|
|
2499
|
+
if (dryRun) {
|
|
2500
|
+
logInfo(`[dry-run] patch ${readmePath}`);
|
|
2501
|
+
} else {
|
|
2502
|
+
fs.writeFileSync(readmePath, nextReadme, 'utf8');
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2507
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2508
|
+
logError(
|
|
2509
|
+
`package.json not found in generated React app: ${packageJsonPath}`,
|
|
2510
|
+
);
|
|
2511
|
+
process.exit(1);
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2515
|
+
packageJson.name = projectName;
|
|
2516
|
+
packageJson.private = true;
|
|
2517
|
+
packageJson.version = '0.1.0';
|
|
2518
|
+
packageJson.type = 'module';
|
|
2519
|
+
packageJson.scripts = {
|
|
2520
|
+
'build:rsx': 'rsx build --project tsconfig.json --no-emit --prod',
|
|
2521
|
+
dev: 'npm run build:rsx && vite',
|
|
2522
|
+
build: 'npm run build:rsx && vite build',
|
|
2523
|
+
preview: 'vite preview',
|
|
2524
|
+
};
|
|
2525
|
+
packageJson.dependencies = {
|
|
2526
|
+
react: packageJson.dependencies?.react ?? '^19.2.4',
|
|
2527
|
+
'react-dom': packageJson.dependencies?.['react-dom'] ?? '^19.2.4',
|
|
2528
|
+
'@rs-x/core': rsxSpecs['@rs-x/core'],
|
|
2529
|
+
'@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
|
|
2530
|
+
'@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
|
|
2531
|
+
'@rs-x/react': rsxSpecs['@rs-x/react'],
|
|
2532
|
+
};
|
|
2533
|
+
packageJson.devDependencies = {
|
|
2534
|
+
typescript: packageJson.devDependencies?.typescript ?? '^5.9.3',
|
|
2535
|
+
vite: packageJson.devDependencies?.vite ?? '^7.3.1',
|
|
2536
|
+
'@vitejs/plugin-react':
|
|
2537
|
+
packageJson.devDependencies?.['@vitejs/plugin-react'] ?? '^5.1.4',
|
|
2538
|
+
'@types/react': packageJson.devDependencies?.['@types/react'] ?? '^19.2.2',
|
|
2539
|
+
'@types/react-dom':
|
|
2540
|
+
packageJson.devDependencies?.['@types/react-dom'] ?? '^19.2.2',
|
|
2541
|
+
'@rs-x/cli': rsxSpecs['@rs-x/cli'],
|
|
2542
|
+
'@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
|
|
2543
|
+
'@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
|
|
2544
|
+
};
|
|
2545
|
+
|
|
2546
|
+
if (dryRun) {
|
|
2547
|
+
logInfo(`[dry-run] patch ${packageJsonPath}`);
|
|
2548
|
+
} else {
|
|
2549
|
+
fs.writeFileSync(
|
|
2550
|
+
packageJsonPath,
|
|
2551
|
+
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
2552
|
+
'utf8',
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
2557
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
2558
|
+
upsertTypescriptPluginInTsConfig(tsConfigPath, dryRun);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
if (!Boolean(flags['skip-install'])) {
|
|
2562
|
+
logInfo(`Refreshing ${pm} dependencies for the RS-X React starter...`);
|
|
2563
|
+
run(pm, ['install'], { dryRun });
|
|
2564
|
+
logOk('React starter dependencies are up to date.');
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
function applyVueDemoStarter(projectRoot, projectName, pm, flags) {
|
|
2569
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
2570
|
+
ensureRsxConfigFile(projectRoot, 'vuejs', dryRun);
|
|
2571
|
+
const tag = resolveInstallTag(flags);
|
|
2572
|
+
const tarballsDir =
|
|
2573
|
+
typeof flags['tarballs-dir'] === 'string'
|
|
2574
|
+
? path.resolve(process.cwd(), flags['tarballs-dir'])
|
|
2575
|
+
: typeof process.env.RSX_TARBALLS_DIR === 'string' &&
|
|
2576
|
+
process.env.RSX_TARBALLS_DIR.trim().length > 0
|
|
2577
|
+
? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
|
|
2578
|
+
: null;
|
|
2579
|
+
const workspaceRoot = findRepoRoot(projectRoot);
|
|
2580
|
+
const rsxSpecs = resolveProjectRsxSpecs(
|
|
2581
|
+
projectRoot,
|
|
2582
|
+
workspaceRoot,
|
|
2583
|
+
tarballsDir,
|
|
2584
|
+
{ tag, includeVuePackage: true },
|
|
2585
|
+
);
|
|
2586
|
+
|
|
2587
|
+
const templateFiles = ['README.md', 'src'];
|
|
2588
|
+
for (const entry of templateFiles) {
|
|
2589
|
+
copyPathWithDryRun(
|
|
2590
|
+
path.join(VUE_DEMO_TEMPLATE_DIR, entry),
|
|
2591
|
+
path.join(projectRoot, entry),
|
|
2592
|
+
dryRun,
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
1058
2595
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
2596
|
+
const staleVueFiles = [
|
|
2597
|
+
path.join(projectRoot, 'public'),
|
|
2598
|
+
path.join(projectRoot, 'src/components/HelloWorld.vue'),
|
|
2599
|
+
path.join(projectRoot, 'src/assets'),
|
|
2600
|
+
];
|
|
2601
|
+
for (const stalePath of staleVueFiles) {
|
|
2602
|
+
removeFileOrDirectoryWithDryRun(stalePath, dryRun);
|
|
2603
|
+
}
|
|
1063
2604
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
2605
|
+
const readmePath = path.join(projectRoot, 'README.md');
|
|
2606
|
+
if (fs.existsSync(readmePath)) {
|
|
2607
|
+
const readmeSource = fs.readFileSync(readmePath, 'utf8');
|
|
2608
|
+
const nextReadme = readmeSource.replace(
|
|
2609
|
+
/^#\s+rsx-vue-example/mu,
|
|
2610
|
+
`# ${projectName}`,
|
|
2611
|
+
);
|
|
2612
|
+
if (dryRun) {
|
|
2613
|
+
logInfo(`[dry-run] patch ${readmePath}`);
|
|
2614
|
+
} else {
|
|
2615
|
+
fs.writeFileSync(readmePath, nextReadme, 'utf8');
|
|
1075
2616
|
}
|
|
1076
|
-
|
|
1077
|
-
logInfo(`Installing dependencies with ${pm}...`);
|
|
1078
|
-
run(pm, installArgs, { dryRun, cwd: projectRoot });
|
|
1079
|
-
logOk('Dependencies installed.');
|
|
1080
|
-
} else {
|
|
1081
|
-
logInfo('Skipping dependency install (--skip-install).');
|
|
1082
2617
|
}
|
|
1083
2618
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
console.log(' npm install');
|
|
2619
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2620
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2621
|
+
logError(`package.json not found in generated Vue app: ${packageJsonPath}`);
|
|
2622
|
+
process.exit(1);
|
|
1089
2623
|
}
|
|
1090
|
-
console.log(' npm run build');
|
|
1091
|
-
console.log(' npm run start');
|
|
1092
|
-
}
|
|
1093
2624
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
2625
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2626
|
+
packageJson.name = projectName;
|
|
2627
|
+
packageJson.private = true;
|
|
2628
|
+
packageJson.version = '0.1.0';
|
|
2629
|
+
packageJson.type = 'module';
|
|
2630
|
+
packageJson.scripts = {
|
|
2631
|
+
'build:rsx': 'rsx build --project tsconfig.app.json --no-emit --prod',
|
|
2632
|
+
'typecheck:rsx': 'rsx typecheck --project tsconfig.app.json',
|
|
2633
|
+
dev: 'npm run build:rsx && vite',
|
|
2634
|
+
build: 'npm run build:rsx && vue-tsc -b && vite build',
|
|
2635
|
+
preview: 'vite preview',
|
|
2636
|
+
};
|
|
2637
|
+
packageJson.dependencies = {
|
|
2638
|
+
vue: packageJson.dependencies?.vue ?? '^3.5.30',
|
|
2639
|
+
'@rs-x/core': rsxSpecs['@rs-x/core'],
|
|
2640
|
+
'@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
|
|
2641
|
+
'@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
|
|
2642
|
+
'@rs-x/vue': rsxSpecs['@rs-x/vue'],
|
|
2643
|
+
};
|
|
2644
|
+
packageJson.devDependencies = {
|
|
2645
|
+
...(packageJson.devDependencies ?? {}),
|
|
2646
|
+
'@rs-x/cli': rsxSpecs['@rs-x/cli'],
|
|
2647
|
+
'@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
|
|
2648
|
+
'@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
|
|
2649
|
+
};
|
|
2650
|
+
|
|
2651
|
+
if (dryRun) {
|
|
2652
|
+
logInfo(`[dry-run] patch ${packageJsonPath}`);
|
|
2653
|
+
} else {
|
|
2654
|
+
fs.writeFileSync(
|
|
2655
|
+
packageJsonPath,
|
|
2656
|
+
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
2657
|
+
'utf8',
|
|
2658
|
+
);
|
|
1099
2659
|
}
|
|
1100
2660
|
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
2661
|
+
const tsConfigAppPath = path.join(projectRoot, 'tsconfig.app.json');
|
|
2662
|
+
if (fs.existsSync(tsConfigAppPath)) {
|
|
2663
|
+
upsertTypescriptPluginInTsConfig(tsConfigAppPath, dryRun);
|
|
2664
|
+
ensureTsConfigIncludePattern(tsConfigAppPath, 'src/**/*.d.ts', dryRun);
|
|
1105
2665
|
}
|
|
1106
2666
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
try {
|
|
1112
|
-
return await askUntilNonEmpty(rl, 'Project name: ');
|
|
1113
|
-
} finally {
|
|
1114
|
-
rl.close();
|
|
2667
|
+
if (!Boolean(flags['skip-install'])) {
|
|
2668
|
+
logInfo(`Refreshing ${pm} dependencies for the RS-X Vue starter...`);
|
|
2669
|
+
run(pm, ['install'], { dryRun });
|
|
2670
|
+
logOk('Vue starter dependencies are up to date.');
|
|
1115
2671
|
}
|
|
1116
2672
|
}
|
|
1117
2673
|
|
|
1118
|
-
function
|
|
2674
|
+
function applyNextDemoStarter(projectRoot, projectName, pm, flags) {
|
|
1119
2675
|
const dryRun = Boolean(flags['dry-run']);
|
|
1120
|
-
|
|
2676
|
+
ensureRsxConfigFile(projectRoot, 'nextjs', dryRun);
|
|
2677
|
+
const tag = resolveInstallTag(flags);
|
|
2678
|
+
const tarballsDir =
|
|
2679
|
+
typeof flags['tarballs-dir'] === 'string'
|
|
2680
|
+
? path.resolve(process.cwd(), flags['tarballs-dir'])
|
|
2681
|
+
: typeof process.env.RSX_TARBALLS_DIR === 'string' &&
|
|
2682
|
+
process.env.RSX_TARBALLS_DIR.trim().length > 0
|
|
2683
|
+
? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
|
|
2684
|
+
: null;
|
|
2685
|
+
const workspaceRoot = findRepoRoot(projectRoot);
|
|
2686
|
+
const rsxSpecs = resolveProjectRsxSpecs(
|
|
2687
|
+
projectRoot,
|
|
2688
|
+
workspaceRoot,
|
|
2689
|
+
tarballsDir,
|
|
2690
|
+
{ tag, includeReactPackage: true },
|
|
2691
|
+
);
|
|
1121
2692
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
2693
|
+
const templateFiles = ['README.md', 'app', 'components', 'hooks', 'lib'];
|
|
2694
|
+
for (const entry of templateFiles) {
|
|
2695
|
+
copyPathWithDryRun(
|
|
2696
|
+
path.join(NEXT_DEMO_TEMPLATE_DIR, entry),
|
|
2697
|
+
path.join(projectRoot, entry),
|
|
2698
|
+
dryRun,
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
const readmePath = path.join(projectRoot, 'README.md');
|
|
2703
|
+
if (fs.existsSync(readmePath)) {
|
|
2704
|
+
const readmeSource = fs.readFileSync(readmePath, 'utf8');
|
|
2705
|
+
const nextReadme = readmeSource.replace(
|
|
2706
|
+
/^#\s+rsx-next-example/mu,
|
|
2707
|
+
`# ${projectName}`,
|
|
2708
|
+
);
|
|
2709
|
+
if (dryRun) {
|
|
2710
|
+
logInfo(`[dry-run] patch ${readmePath}`);
|
|
2711
|
+
} else {
|
|
2712
|
+
fs.writeFileSync(readmePath, nextReadme, 'utf8');
|
|
1137
2713
|
}
|
|
1138
|
-
run('npx', args, { dryRun });
|
|
1139
|
-
return;
|
|
1140
2714
|
}
|
|
1141
2715
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
2716
|
+
const publicDir = path.join(projectRoot, 'public');
|
|
2717
|
+
removeFileOrDirectoryWithDryRun(publicDir, dryRun);
|
|
2718
|
+
|
|
2719
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2720
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2721
|
+
logError(
|
|
2722
|
+
`package.json not found in generated Next.js app: ${packageJsonPath}`,
|
|
2723
|
+
);
|
|
2724
|
+
process.exit(1);
|
|
1147
2725
|
}
|
|
1148
2726
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
2727
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2728
|
+
packageJson.name = projectName;
|
|
2729
|
+
packageJson.private = true;
|
|
2730
|
+
packageJson.version = '0.1.0';
|
|
2731
|
+
packageJson.scripts = {
|
|
2732
|
+
...packageJson.scripts,
|
|
2733
|
+
'build:rsx': 'rsx build --project tsconfig.json --no-emit --prod',
|
|
2734
|
+
dev: 'npm run build:rsx && next dev',
|
|
2735
|
+
build: 'npm run build:rsx && next build',
|
|
2736
|
+
start: 'next start',
|
|
2737
|
+
};
|
|
2738
|
+
packageJson.dependencies = {
|
|
2739
|
+
...(packageJson.dependencies ?? {}),
|
|
2740
|
+
'@rs-x/core': rsxSpecs['@rs-x/core'],
|
|
2741
|
+
'@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
|
|
2742
|
+
'@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
|
|
2743
|
+
'@rs-x/react': rsxSpecs['@rs-x/react'],
|
|
2744
|
+
};
|
|
2745
|
+
packageJson.devDependencies = {
|
|
2746
|
+
...(packageJson.devDependencies ?? {}),
|
|
2747
|
+
'@rs-x/cli': rsxSpecs['@rs-x/cli'],
|
|
2748
|
+
'@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
|
|
2749
|
+
'@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
|
|
2750
|
+
};
|
|
2751
|
+
|
|
2752
|
+
if (dryRun) {
|
|
2753
|
+
logInfo(`[dry-run] patch ${packageJsonPath}`);
|
|
2754
|
+
} else {
|
|
2755
|
+
fs.writeFileSync(
|
|
2756
|
+
packageJsonPath,
|
|
2757
|
+
`${JSON.stringify(packageJson, null, 2)}\n`,
|
|
2758
|
+
'utf8',
|
|
2759
|
+
);
|
|
1154
2760
|
}
|
|
1155
2761
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
pnpm: '--use-pnpm',
|
|
1160
|
-
yarn: '--use-yarn',
|
|
1161
|
-
bun: '--use-bun',
|
|
1162
|
-
};
|
|
1163
|
-
const args = [
|
|
1164
|
-
'create-next-app@latest',
|
|
1165
|
-
projectName,
|
|
1166
|
-
'--yes',
|
|
1167
|
-
'--ts',
|
|
1168
|
-
'--app',
|
|
1169
|
-
'--eslint',
|
|
1170
|
-
'--import-alias',
|
|
1171
|
-
'@/*',
|
|
1172
|
-
packageManagerFlagByPm[pm] ?? '--use-npm',
|
|
1173
|
-
];
|
|
1174
|
-
if (skipInstall) {
|
|
1175
|
-
args.push('--skip-install');
|
|
1176
|
-
}
|
|
1177
|
-
run('npx', args, { dryRun });
|
|
1178
|
-
return;
|
|
2762
|
+
const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
2763
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
2764
|
+
upsertTypescriptPluginInTsConfig(tsConfigPath, dryRun);
|
|
1179
2765
|
}
|
|
1180
2766
|
|
|
1181
|
-
|
|
1182
|
-
|
|
2767
|
+
if (!Boolean(flags['skip-install'])) {
|
|
2768
|
+
logInfo(`Refreshing ${pm} dependencies for the RS-X Next.js starter...`);
|
|
2769
|
+
run(pm, ['install'], { dryRun });
|
|
2770
|
+
logOk('Next.js starter dependencies are up to date.');
|
|
2771
|
+
}
|
|
1183
2772
|
}
|
|
1184
2773
|
|
|
1185
2774
|
async function runProjectWithTemplate(template, flags) {
|
|
@@ -1196,15 +2785,22 @@ async function runProjectWithTemplate(template, flags) {
|
|
|
1196
2785
|
return;
|
|
1197
2786
|
}
|
|
1198
2787
|
|
|
1199
|
-
const
|
|
2788
|
+
const invocationRoot = process.cwd();
|
|
2789
|
+
const pm = resolveCliPackageManager(invocationRoot, flags.pm);
|
|
1200
2790
|
const projectName = await resolveProjectName(flags.name, flags._nameHint);
|
|
1201
|
-
const projectRoot =
|
|
2791
|
+
const projectRoot = resolveProjectRoot(projectName, flags);
|
|
1202
2792
|
if (fs.existsSync(projectRoot) && fs.readdirSync(projectRoot).length > 0) {
|
|
1203
2793
|
logError(`Target directory is not empty: ${projectRoot}`);
|
|
1204
2794
|
process.exit(1);
|
|
1205
2795
|
}
|
|
1206
2796
|
|
|
1207
|
-
scaffoldProjectTemplate(
|
|
2797
|
+
scaffoldProjectTemplate(
|
|
2798
|
+
normalizedTemplate,
|
|
2799
|
+
projectName,
|
|
2800
|
+
projectRoot,
|
|
2801
|
+
pm,
|
|
2802
|
+
flags,
|
|
2803
|
+
);
|
|
1208
2804
|
const dryRun = Boolean(flags['dry-run']);
|
|
1209
2805
|
if (dryRun) {
|
|
1210
2806
|
logInfo(`[dry-run] setup RS-X in ${projectRoot}`);
|
|
@@ -1213,40 +2809,221 @@ async function runProjectWithTemplate(template, flags) {
|
|
|
1213
2809
|
|
|
1214
2810
|
withWorkingDirectory(projectRoot, () => {
|
|
1215
2811
|
if (normalizedTemplate === 'angular') {
|
|
1216
|
-
|
|
2812
|
+
applyAngularDemoStarter(projectRoot, projectName, pm, flags);
|
|
1217
2813
|
return;
|
|
1218
2814
|
}
|
|
1219
2815
|
if (normalizedTemplate === 'react') {
|
|
1220
|
-
|
|
1221
|
-
...flags,
|
|
1222
|
-
entry: flags.entry ?? 'src/main.tsx',
|
|
1223
|
-
});
|
|
2816
|
+
applyReactDemoStarter(projectRoot, projectName, pm, flags);
|
|
1224
2817
|
return;
|
|
1225
2818
|
}
|
|
1226
2819
|
if (normalizedTemplate === 'nextjs') {
|
|
1227
|
-
|
|
2820
|
+
applyNextDemoStarter(projectRoot, projectName, pm, flags);
|
|
1228
2821
|
return;
|
|
1229
2822
|
}
|
|
1230
2823
|
if (normalizedTemplate === 'vuejs') {
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
2824
|
+
applyVueDemoStarter(projectRoot, projectName, pm, flags);
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
|
|
2828
|
+
verifyGeneratedProject(projectRoot, normalizedTemplate);
|
|
2829
|
+
if (resolveCliVerifyFlag(invocationRoot, flags, 'project')) {
|
|
2830
|
+
logInfo('Re-running starter verification (--verify)...');
|
|
2831
|
+
verifyGeneratedProject(projectRoot, normalizedTemplate);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
logOk(`Created RS-X ${normalizedTemplate} project: ${projectRoot}`);
|
|
2835
|
+
logInfo('Next steps:');
|
|
2836
|
+
console.log(` cd ${projectName}`);
|
|
2837
|
+
if (Boolean(flags['skip-install'])) {
|
|
2838
|
+
console.log(` ${pm} install`);
|
|
2839
|
+
}
|
|
2840
|
+
if (normalizedTemplate === 'angular') {
|
|
2841
|
+
console.log(` ${pm} run start`);
|
|
2842
|
+
} else {
|
|
2843
|
+
console.log(` ${pm} run dev`);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
function verifyGeneratedProject(projectRoot, template) {
|
|
2848
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2849
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2850
|
+
logError(`Generated project is missing package.json: ${packageJsonPath}`);
|
|
2851
|
+
process.exit(1);
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2855
|
+
const scripts = packageJson.scripts ?? {};
|
|
2856
|
+
const dependencies = {
|
|
2857
|
+
...(packageJson.dependencies ?? {}),
|
|
2858
|
+
...(packageJson.devDependencies ?? {}),
|
|
2859
|
+
};
|
|
2860
|
+
|
|
2861
|
+
const expectationsByTemplate = {
|
|
2862
|
+
angular: {
|
|
2863
|
+
scripts: ['build:rsx', 'start'],
|
|
2864
|
+
dependencies: [
|
|
2865
|
+
'@rs-x/angular',
|
|
2866
|
+
'@rs-x/compiler',
|
|
2867
|
+
'@rs-x/typescript-plugin',
|
|
2868
|
+
],
|
|
2869
|
+
files: [
|
|
2870
|
+
'src/main.ts',
|
|
2871
|
+
'src/app/app.component.ts',
|
|
2872
|
+
'src/app/virtual-table/virtual-table.component.ts',
|
|
2873
|
+
],
|
|
2874
|
+
},
|
|
2875
|
+
react: {
|
|
2876
|
+
scripts: ['build:rsx', 'dev', 'build'],
|
|
2877
|
+
dependencies: [
|
|
2878
|
+
'@rs-x/react',
|
|
2879
|
+
'@rs-x/compiler',
|
|
2880
|
+
'@rs-x/typescript-plugin',
|
|
2881
|
+
],
|
|
2882
|
+
files: [
|
|
2883
|
+
'src/main.tsx',
|
|
2884
|
+
'src/rsx-bootstrap.ts',
|
|
2885
|
+
'src/app/app.tsx',
|
|
2886
|
+
'src/app/virtual-table/virtual-table-shell.tsx',
|
|
2887
|
+
],
|
|
2888
|
+
},
|
|
2889
|
+
vuejs: {
|
|
2890
|
+
scripts: ['build:rsx', 'dev', 'build'],
|
|
2891
|
+
dependencies: ['@rs-x/vue', '@rs-x/compiler', '@rs-x/typescript-plugin'],
|
|
2892
|
+
files: [
|
|
2893
|
+
'src/main.ts',
|
|
2894
|
+
'src/App.vue',
|
|
2895
|
+
'src/lib/rsx-bootstrap.ts',
|
|
2896
|
+
'src/components/VirtualTableShell.vue',
|
|
2897
|
+
],
|
|
2898
|
+
},
|
|
2899
|
+
nextjs: {
|
|
2900
|
+
scripts: ['build:rsx', 'dev', 'build'],
|
|
2901
|
+
dependencies: [
|
|
2902
|
+
'@rs-x/react',
|
|
2903
|
+
'@rs-x/compiler',
|
|
2904
|
+
'@rs-x/typescript-plugin',
|
|
2905
|
+
],
|
|
2906
|
+
files: [
|
|
2907
|
+
'app/layout.tsx',
|
|
2908
|
+
'app/page.tsx',
|
|
2909
|
+
'components/demo-app.tsx',
|
|
2910
|
+
'lib/rsx-bootstrap.ts',
|
|
2911
|
+
],
|
|
2912
|
+
},
|
|
2913
|
+
};
|
|
2914
|
+
|
|
2915
|
+
const expected = expectationsByTemplate[template];
|
|
2916
|
+
if (!expected) {
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
for (const scriptName of expected.scripts) {
|
|
2921
|
+
if (
|
|
2922
|
+
typeof scripts[scriptName] !== 'string' ||
|
|
2923
|
+
scripts[scriptName].trim() === ''
|
|
2924
|
+
) {
|
|
2925
|
+
logError(
|
|
2926
|
+
`Generated ${template} project is missing script "${scriptName}" in package.json.`,
|
|
2927
|
+
);
|
|
2928
|
+
process.exit(1);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
for (const dependencyName of expected.dependencies) {
|
|
2933
|
+
if (typeof dependencies[dependencyName] !== 'string') {
|
|
2934
|
+
logError(
|
|
2935
|
+
`Generated ${template} project is missing dependency "${dependencyName}" in package.json.`,
|
|
2936
|
+
);
|
|
2937
|
+
process.exit(1);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
for (const relativeFilePath of expected.files) {
|
|
2942
|
+
const absoluteFilePath = path.join(projectRoot, relativeFilePath);
|
|
2943
|
+
if (!fs.existsSync(absoluteFilePath)) {
|
|
2944
|
+
logError(
|
|
2945
|
+
`Generated ${template} project is missing expected file: ${absoluteFilePath}`,
|
|
2946
|
+
);
|
|
2947
|
+
process.exit(1);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
const rsxConfigPath = path.join(projectRoot, 'rsx.config.json');
|
|
2952
|
+
if (!fs.existsSync(rsxConfigPath)) {
|
|
2953
|
+
logError(
|
|
2954
|
+
`Generated ${template} project is missing expected file: ${rsxConfigPath}`,
|
|
2955
|
+
);
|
|
2956
|
+
process.exit(1);
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
logOk(`Verified generated ${template} project structure.`);
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
function verifySetupOutput(projectRoot, template) {
|
|
2963
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2964
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2965
|
+
logError(`Project is missing package.json: ${packageJsonPath}`);
|
|
2966
|
+
process.exit(1);
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2970
|
+
const scripts = packageJson.scripts ?? {};
|
|
2971
|
+
|
|
2972
|
+
const setupExpectations = {
|
|
2973
|
+
react: {
|
|
2974
|
+
scripts: ['build:rsx', 'typecheck:rsx', 'dev', 'build'],
|
|
2975
|
+
files: ['vite.config.ts', 'src/rsx-bootstrap.ts'],
|
|
2976
|
+
},
|
|
2977
|
+
vuejs: {
|
|
2978
|
+
scripts: ['build:rsx', 'typecheck:rsx', 'dev', 'build'],
|
|
2979
|
+
files: ['vite.config.ts', 'src/rsx-bootstrap.ts'],
|
|
2980
|
+
},
|
|
2981
|
+
next: {
|
|
2982
|
+
scripts: ['build:rsx', 'typecheck:rsx', 'dev', 'build'],
|
|
2983
|
+
files: [
|
|
2984
|
+
'next.config.js',
|
|
2985
|
+
'rsx-webpack-loader.cjs',
|
|
2986
|
+
'app/rsx-bootstrap.ts',
|
|
2987
|
+
],
|
|
2988
|
+
},
|
|
2989
|
+
angular: {
|
|
2990
|
+
scripts: ['build:rsx', 'typecheck:rsx', 'prebuild', 'start'],
|
|
2991
|
+
files: ['src/main.ts', 'angular.json'],
|
|
2992
|
+
},
|
|
2993
|
+
};
|
|
2994
|
+
|
|
2995
|
+
const expected = setupExpectations[template];
|
|
2996
|
+
if (!expected) {
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
for (const scriptName of expected.scripts) {
|
|
3001
|
+
if (
|
|
3002
|
+
typeof scripts[scriptName] !== 'string' ||
|
|
3003
|
+
scripts[scriptName].trim() === ''
|
|
3004
|
+
) {
|
|
3005
|
+
logError(
|
|
3006
|
+
`Setup output is missing script "${scriptName}" in package.json.`,
|
|
3007
|
+
);
|
|
3008
|
+
process.exit(1);
|
|
1236
3009
|
}
|
|
1237
|
-
}
|
|
3010
|
+
}
|
|
1238
3011
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
3012
|
+
for (const relativeFilePath of expected.files) {
|
|
3013
|
+
const absoluteFilePath = path.join(projectRoot, relativeFilePath);
|
|
3014
|
+
if (!fs.existsSync(absoluteFilePath)) {
|
|
3015
|
+
logError(`Setup output is missing expected file: ${absoluteFilePath}`);
|
|
3016
|
+
process.exit(1);
|
|
3017
|
+
}
|
|
1244
3018
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
3019
|
+
|
|
3020
|
+
const rsxConfigPath = path.join(projectRoot, 'rsx.config.json');
|
|
3021
|
+
if (!fs.existsSync(rsxConfigPath)) {
|
|
3022
|
+
logError(`Setup output is missing expected file: ${rsxConfigPath}`);
|
|
3023
|
+
process.exit(1);
|
|
1249
3024
|
}
|
|
3025
|
+
|
|
3026
|
+
logOk(`Verified ${template} setup output.`);
|
|
1250
3027
|
}
|
|
1251
3028
|
|
|
1252
3029
|
function detectProjectContext(projectRoot) {
|
|
@@ -1507,12 +3284,12 @@ function ensureNextGateFile(gateFile, bootstrapFile, dryRun) {
|
|
|
1507
3284
|
const content = useTypeScript
|
|
1508
3285
|
? `'use client';
|
|
1509
3286
|
|
|
1510
|
-
import { type ReactNode, useEffect, useState } from 'react';
|
|
3287
|
+
import { type ReactElement, type ReactNode, useEffect, useState } from 'react';
|
|
1511
3288
|
|
|
1512
3289
|
import { initRsx } from '${importPath}';
|
|
1513
3290
|
|
|
1514
3291
|
// Generated by rsx init
|
|
1515
|
-
export function RsxBootstrapGate(props: { children: ReactNode }):
|
|
3292
|
+
export function RsxBootstrapGate(props: { children: ReactNode }): ReactElement | null {
|
|
1516
3293
|
const [ready, setReady] = useState(false);
|
|
1517
3294
|
|
|
1518
3295
|
useEffect(() => {
|
|
@@ -1726,15 +3503,14 @@ function patchEntryFileForRsx(entryFile, bootstrapFile, context, dryRun) {
|
|
|
1726
3503
|
|
|
1727
3504
|
function runInit(flags) {
|
|
1728
3505
|
const dryRun = Boolean(flags['dry-run']);
|
|
1729
|
-
const skipVscode = Boolean(flags['skip-vscode']);
|
|
1730
3506
|
const skipInstall = Boolean(flags['skip-install']);
|
|
1731
|
-
const pm = detectPackageManager(flags.pm);
|
|
1732
|
-
const tag = resolveInstallTag(flags);
|
|
1733
3507
|
const projectRoot = process.cwd();
|
|
3508
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
3509
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
1734
3510
|
|
|
1735
3511
|
if (!skipInstall) {
|
|
1736
|
-
installRuntimePackages(pm, dryRun, tag);
|
|
1737
|
-
installCompilerPackages(pm, dryRun, tag);
|
|
3512
|
+
installRuntimePackages(pm, dryRun, tag, projectRoot, flags);
|
|
3513
|
+
installCompilerPackages(pm, dryRun, tag, projectRoot, flags);
|
|
1738
3514
|
} else {
|
|
1739
3515
|
logInfo('Skipping package installation (--skip-install).');
|
|
1740
3516
|
}
|
|
@@ -1788,13 +3564,179 @@ function runInit(flags) {
|
|
|
1788
3564
|
}
|
|
1789
3565
|
}
|
|
1790
3566
|
|
|
1791
|
-
|
|
1792
|
-
installVsCodeExtension(flags);
|
|
1793
|
-
}
|
|
3567
|
+
ensureRsxConfigFile(projectRoot, effectiveContext, dryRun);
|
|
1794
3568
|
|
|
1795
3569
|
logOk('RS-X init completed.');
|
|
1796
3570
|
}
|
|
1797
3571
|
|
|
3572
|
+
function ensureAngularProvidersInEntry(entryFile, dryRun) {
|
|
3573
|
+
if (!fs.existsSync(entryFile)) {
|
|
3574
|
+
return false;
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
const original = fs.readFileSync(entryFile, 'utf8');
|
|
3578
|
+
|
|
3579
|
+
if (!original.includes('bootstrapApplication(')) {
|
|
3580
|
+
logWarn(
|
|
3581
|
+
`Could not automatically patch Angular providers in ${entryFile}. Expected bootstrapApplication(...).`,
|
|
3582
|
+
);
|
|
3583
|
+
logInfo(
|
|
3584
|
+
"Manual setup: import { providexRsx } from '@rs-x/angular' and add providers: [...providexRsx()] to bootstrapApplication(...).",
|
|
3585
|
+
);
|
|
3586
|
+
return false;
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
const bootstrapCallMatch = original.match(
|
|
3590
|
+
/bootstrapApplication\(\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*|\{[\s\S]*?\}))?\s*\)/mu,
|
|
3591
|
+
);
|
|
3592
|
+
const componentIdentifier = bootstrapCallMatch?.[1] ?? null;
|
|
3593
|
+
const configArgument = bootstrapCallMatch?.[2] ?? null;
|
|
3594
|
+
const configImportIdentifier =
|
|
3595
|
+
configArgument && /^[A-Za-z_$][\w$]*$/u.test(configArgument)
|
|
3596
|
+
? configArgument
|
|
3597
|
+
: original.includes('appConfig')
|
|
3598
|
+
? 'appConfig'
|
|
3599
|
+
: null;
|
|
3600
|
+
|
|
3601
|
+
const staticImportPattern = new RegExp(
|
|
3602
|
+
String.raw`^\s*import\s+\{\s*([^}]*)\b${componentIdentifier ?? ''}\b([^}]*)\}\s+from\s+['"]([^'"]+)['"];\s*$`,
|
|
3603
|
+
'mu',
|
|
3604
|
+
);
|
|
3605
|
+
const componentImportMatch = componentIdentifier
|
|
3606
|
+
? original.match(staticImportPattern)
|
|
3607
|
+
: null;
|
|
3608
|
+
const dynamicComponentImportMatch = componentIdentifier
|
|
3609
|
+
? original.match(
|
|
3610
|
+
new RegExp(
|
|
3611
|
+
String.raw`const\s+\{\s*${componentIdentifier}\s*\}\s*=\s*await\s+import\('([^']+)'\);`,
|
|
3612
|
+
'mu',
|
|
3613
|
+
),
|
|
3614
|
+
)
|
|
3615
|
+
: null;
|
|
3616
|
+
const componentImportPath =
|
|
3617
|
+
componentImportMatch?.[3] ?? dynamicComponentImportMatch?.[1] ?? null;
|
|
3618
|
+
|
|
3619
|
+
const configImportMatch = configImportIdentifier
|
|
3620
|
+
? original.match(
|
|
3621
|
+
new RegExp(
|
|
3622
|
+
String.raw`^\s*import\s+\{\s*([^}]*)\b${configImportIdentifier}\b([^}]*)\}\s+from\s+['"]([^'"]+)['"];\s*$`,
|
|
3623
|
+
'mu',
|
|
3624
|
+
),
|
|
3625
|
+
)
|
|
3626
|
+
: null;
|
|
3627
|
+
const configImportPath = configImportMatch?.[3] ?? null;
|
|
3628
|
+
|
|
3629
|
+
const canPreloadBeforeComponentImport =
|
|
3630
|
+
componentIdentifier !== null && componentImportPath !== null;
|
|
3631
|
+
|
|
3632
|
+
if (canPreloadBeforeComponentImport) {
|
|
3633
|
+
const importLines = original
|
|
3634
|
+
.split('\n')
|
|
3635
|
+
.filter((line) => /^\s*import\s+/u.test(line))
|
|
3636
|
+
.filter((line) => !line.match(staticImportPattern))
|
|
3637
|
+
.filter((line) =>
|
|
3638
|
+
configImportMatch
|
|
3639
|
+
? !line.match(
|
|
3640
|
+
new RegExp(
|
|
3641
|
+
String.raw`^\s*import\s+\{\s*([^}]*)\b${configImportIdentifier}\b([^}]*)\}\s+from\s+['"][^'"]+['"];\s*$`,
|
|
3642
|
+
'u',
|
|
3643
|
+
),
|
|
3644
|
+
)
|
|
3645
|
+
: true,
|
|
3646
|
+
)
|
|
3647
|
+
.filter(
|
|
3648
|
+
(line) =>
|
|
3649
|
+
!line.includes("import { providexRsx } from '@rs-x/angular';"),
|
|
3650
|
+
)
|
|
3651
|
+
.filter(
|
|
3652
|
+
(line) =>
|
|
3653
|
+
!line.includes("import { InjectionContainer } from '@rs-x/core';"),
|
|
3654
|
+
)
|
|
3655
|
+
.filter(
|
|
3656
|
+
(line) =>
|
|
3657
|
+
!line.includes(
|
|
3658
|
+
"import { RsXExpressionParserModule } from '@rs-x/expression-parser';",
|
|
3659
|
+
),
|
|
3660
|
+
);
|
|
3661
|
+
|
|
3662
|
+
const bootstrapConfigExpression =
|
|
3663
|
+
configImportPath && configImportIdentifier
|
|
3664
|
+
? `const [{ ${configImportIdentifier} }, { ${componentIdentifier} }] = await Promise.all([\n import('${configImportPath}'),\n import('${componentImportPath}'),\n ]);\n\n await bootstrapApplication(${componentIdentifier}, {\n ...${configImportIdentifier},\n providers: [...(${configImportIdentifier}.providers ?? []), ...providexRsx()],\n });`
|
|
3665
|
+
: `const { ${componentIdentifier} } = await import('${componentImportPath}');\n\n await bootstrapApplication(${componentIdentifier}, {\n providers: [...providexRsx()],\n });`;
|
|
3666
|
+
|
|
3667
|
+
const rewritten = `${importLines.join('\n')}
|
|
3668
|
+
import { providexRsx } from '@rs-x/angular';
|
|
3669
|
+
import { InjectionContainer } from '@rs-x/core';
|
|
3670
|
+
import { RsXExpressionParserModule } from '@rs-x/expression-parser';
|
|
3671
|
+
|
|
3672
|
+
const bootstrap = async (): Promise<void> => {
|
|
3673
|
+
await InjectionContainer.load(RsXExpressionParserModule);
|
|
3674
|
+
${bootstrapConfigExpression}
|
|
3675
|
+
};
|
|
3676
|
+
|
|
3677
|
+
void bootstrap().catch((error) => {
|
|
3678
|
+
console.error(error);
|
|
3679
|
+
});
|
|
3680
|
+
`;
|
|
3681
|
+
|
|
3682
|
+
if (dryRun) {
|
|
3683
|
+
logInfo(`[dry-run] patch ${entryFile} (providexRsx + preload)`);
|
|
3684
|
+
return true;
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
fs.writeFileSync(entryFile, rewritten, 'utf8');
|
|
3688
|
+
logOk(`Patched ${entryFile} to preload RS-X and include providexRsx.`);
|
|
3689
|
+
return true;
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
const sourceWithImport = injectImport(
|
|
3693
|
+
original,
|
|
3694
|
+
"import { providexRsx } from '@rs-x/angular';",
|
|
3695
|
+
);
|
|
3696
|
+
|
|
3697
|
+
let updated = sourceWithImport;
|
|
3698
|
+
if (/bootstrapApplication\([\s\S]*?,\s*appConfig\s*\)/mu.test(updated)) {
|
|
3699
|
+
updated = updated.replace(
|
|
3700
|
+
/bootstrapApplication\(([\s\S]*?),\s*appConfig\s*\)/mu,
|
|
3701
|
+
'bootstrapApplication($1, {\n ...appConfig,\n providers: [...(appConfig.providers ?? []), ...providexRsx()],\n})',
|
|
3702
|
+
);
|
|
3703
|
+
} else if (
|
|
3704
|
+
/bootstrapApplication\([\s\S]*?,\s*\{[\s\S]*?providers\s*:/mu.test(updated)
|
|
3705
|
+
) {
|
|
3706
|
+
updated = updated.replace(
|
|
3707
|
+
/providers\s*:\s*\[/mu,
|
|
3708
|
+
'providers: [...providexRsx(), ',
|
|
3709
|
+
);
|
|
3710
|
+
} else if (/bootstrapApplication\([\s\S]*?,\s*\{/mu.test(updated)) {
|
|
3711
|
+
updated = updated.replace(
|
|
3712
|
+
/bootstrapApplication\(([\s\S]*?),\s*\{/mu,
|
|
3713
|
+
'bootstrapApplication($1, {\n providers: [...providexRsx()],',
|
|
3714
|
+
);
|
|
3715
|
+
} else {
|
|
3716
|
+
updated = updated.replace(
|
|
3717
|
+
/bootstrapApplication\(([\s\S]*?)\)\s*(?:\.catch\([\s\S]*?\))?\s*;/mu,
|
|
3718
|
+
'bootstrapApplication($1, {\n providers: [...providexRsx()],\n}).catch((error) => {\n console.error(error);\n});',
|
|
3719
|
+
);
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
if (updated === sourceWithImport) {
|
|
3723
|
+
logWarn(`Could not automatically inject providexRsx into ${entryFile}.`);
|
|
3724
|
+
logInfo(
|
|
3725
|
+
"Manual setup: import { providexRsx } from '@rs-x/angular' and add providers: [...providexRsx()] to bootstrapApplication(...).",
|
|
3726
|
+
);
|
|
3727
|
+
return false;
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
if (dryRun) {
|
|
3731
|
+
logInfo(`[dry-run] patch ${entryFile} (providexRsx)`);
|
|
3732
|
+
return true;
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
fs.writeFileSync(entryFile, updated, 'utf8');
|
|
3736
|
+
logOk(`Patched ${entryFile} to include providexRsx.`);
|
|
3737
|
+
return true;
|
|
3738
|
+
}
|
|
3739
|
+
|
|
1798
3740
|
function upsertScriptInPackageJson(
|
|
1799
3741
|
projectRoot,
|
|
1800
3742
|
scriptName,
|
|
@@ -1906,109 +3848,6 @@ module.exports = function rsxWebpackLoader(source) {
|
|
|
1906
3848
|
}
|
|
1907
3849
|
|
|
1908
3850
|
function wireRsxVitePlugin(projectRoot, dryRun) {
|
|
1909
|
-
const pluginFile = path.join(projectRoot, 'rsx-vite-plugin.mjs');
|
|
1910
|
-
const pluginSource = `import path from 'node:path';
|
|
1911
|
-
|
|
1912
|
-
import ts from 'typescript';
|
|
1913
|
-
|
|
1914
|
-
import { createExpressionCachePreloadTransformer } from '@rs-x/compiler';
|
|
1915
|
-
|
|
1916
|
-
function normalizeFileName(fileName) {
|
|
1917
|
-
return path.resolve(fileName).replace(/\\\\/gu, '/');
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
function buildTransformedSourceMap(tsconfigPath) {
|
|
1921
|
-
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
1922
|
-
if (configFile.error) {
|
|
1923
|
-
return new Map();
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
1927
|
-
configFile.config,
|
|
1928
|
-
ts.sys,
|
|
1929
|
-
path.dirname(tsconfigPath),
|
|
1930
|
-
undefined,
|
|
1931
|
-
tsconfigPath,
|
|
1932
|
-
);
|
|
1933
|
-
if (parsed.errors.length > 0) {
|
|
1934
|
-
return new Map();
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
const program = ts.createProgram({
|
|
1938
|
-
rootNames: parsed.fileNames,
|
|
1939
|
-
options: parsed.options,
|
|
1940
|
-
});
|
|
1941
|
-
const transformer = createExpressionCachePreloadTransformer(program);
|
|
1942
|
-
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
1943
|
-
const transformedByFile = new Map();
|
|
1944
|
-
|
|
1945
|
-
for (const sourceFile of program.getSourceFiles()) {
|
|
1946
|
-
if (sourceFile.isDeclarationFile) {
|
|
1947
|
-
continue;
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
if (sourceFile.fileName.includes('/node_modules/')) {
|
|
1951
|
-
continue;
|
|
1952
|
-
}
|
|
1953
|
-
|
|
1954
|
-
const transformed = ts.transform(sourceFile, [transformer]);
|
|
1955
|
-
const transformedSource = transformed.transformed[0];
|
|
1956
|
-
const transformedText = printer.printFile(transformedSource);
|
|
1957
|
-
transformed.dispose();
|
|
1958
|
-
|
|
1959
|
-
transformedByFile.set(normalizeFileName(sourceFile.fileName), transformedText);
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
return transformedByFile;
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
export function rsxVitePlugin(tsconfigPath = 'tsconfig.json') {
|
|
1966
|
-
let transformedByFile = new Map();
|
|
1967
|
-
let resolvedTsConfigPath = '';
|
|
1968
|
-
|
|
1969
|
-
const refresh = () => {
|
|
1970
|
-
transformedByFile = buildTransformedSourceMap(resolvedTsConfigPath);
|
|
1971
|
-
};
|
|
1972
|
-
|
|
1973
|
-
return {
|
|
1974
|
-
name: 'rsx-vite-transform',
|
|
1975
|
-
enforce: 'pre',
|
|
1976
|
-
configResolved(config) {
|
|
1977
|
-
resolvedTsConfigPath = normalizeFileName(path.resolve(config.root, tsconfigPath));
|
|
1978
|
-
refresh();
|
|
1979
|
-
},
|
|
1980
|
-
buildStart() {
|
|
1981
|
-
if (!resolvedTsConfigPath) {
|
|
1982
|
-
resolvedTsConfigPath = normalizeFileName(path.resolve(process.cwd(), tsconfigPath));
|
|
1983
|
-
}
|
|
1984
|
-
refresh();
|
|
1985
|
-
},
|
|
1986
|
-
handleHotUpdate() {
|
|
1987
|
-
refresh();
|
|
1988
|
-
},
|
|
1989
|
-
transform(_code, id) {
|
|
1990
|
-
const normalizedId = normalizeFileName(id.split('?')[0]);
|
|
1991
|
-
const transformed = transformedByFile.get(normalizedId);
|
|
1992
|
-
if (!transformed) {
|
|
1993
|
-
return null;
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
return {
|
|
1997
|
-
code: transformed,
|
|
1998
|
-
map: null,
|
|
1999
|
-
};
|
|
2000
|
-
},
|
|
2001
|
-
};
|
|
2002
|
-
}
|
|
2003
|
-
`;
|
|
2004
|
-
|
|
2005
|
-
if (dryRun) {
|
|
2006
|
-
logInfo(`[dry-run] create ${pluginFile}`);
|
|
2007
|
-
} else {
|
|
2008
|
-
fs.writeFileSync(pluginFile, pluginSource, 'utf8');
|
|
2009
|
-
logOk(`Created ${pluginFile}`);
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
3851
|
const viteConfigCandidates = [
|
|
2013
3852
|
'vite.config.ts',
|
|
2014
3853
|
'vite.config.mts',
|
|
@@ -2018,61 +3857,47 @@ export function rsxVitePlugin(tsconfigPath = 'tsconfig.json') {
|
|
|
2018
3857
|
const viteConfigPath = viteConfigCandidates.find((candidate) =>
|
|
2019
3858
|
fs.existsSync(candidate),
|
|
2020
3859
|
);
|
|
3860
|
+
const stalePluginFiles = [
|
|
3861
|
+
path.join(projectRoot, 'rsx-vite-plugin.ts'),
|
|
3862
|
+
path.join(projectRoot, 'rsx-vite-plugin.mjs'),
|
|
3863
|
+
path.join(projectRoot, 'rsx-vite-plugin.d.ts'),
|
|
3864
|
+
];
|
|
3865
|
+
|
|
2021
3866
|
if (!viteConfigPath) {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
logInfo(
|
|
2026
|
-
"Add it manually: import { rsxVitePlugin } from './rsx-vite-plugin.mjs' and include rsxVitePlugin() in plugins.",
|
|
2027
|
-
);
|
|
3867
|
+
for (const staleFile of stalePluginFiles) {
|
|
3868
|
+
removeFileOrDirectoryWithDryRun(staleFile, dryRun);
|
|
3869
|
+
}
|
|
2028
3870
|
return;
|
|
2029
3871
|
}
|
|
2030
3872
|
|
|
2031
3873
|
const original = fs.readFileSync(viteConfigPath, 'utf8');
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
3874
|
+
const updated = original
|
|
3875
|
+
.replace(
|
|
3876
|
+
/import\s+\{\s*rsxVitePlugin\s*\}\s+from\s+['"]\.\/rsx-vite-plugin(?:\.mjs)?['"];\n?/gu,
|
|
3877
|
+
'',
|
|
3878
|
+
)
|
|
3879
|
+
.replace(/rsxVitePlugin\(\)\s*,\s*/gu, '')
|
|
3880
|
+
.replace(/,\s*rsxVitePlugin\(\)/gu, '')
|
|
3881
|
+
.replace(/\[\s*rsxVitePlugin\(\)\s*\]/gu, '[]');
|
|
3882
|
+
|
|
3883
|
+
if (updated !== original) {
|
|
3884
|
+
if (dryRun) {
|
|
3885
|
+
logInfo(
|
|
3886
|
+
`[dry-run] patch ${viteConfigPath} (remove legacy RS-X Vite plugin)`,
|
|
3887
|
+
);
|
|
3888
|
+
} else {
|
|
3889
|
+
fs.writeFileSync(viteConfigPath, updated, 'utf8');
|
|
3890
|
+
logOk(`Patched ${viteConfigPath} (removed legacy RS-X Vite plugin).`);
|
|
2048
3891
|
}
|
|
2049
|
-
lines.splice(insertAt, 0, importStatement);
|
|
2050
|
-
updated = lines.join('\n');
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
if (/plugins\s*:\s*\[/u.test(updated)) {
|
|
2054
|
-
updated = updated.replace(
|
|
2055
|
-
/plugins\s*:\s*\[/u,
|
|
2056
|
-
'plugins: [rsxVitePlugin(), ',
|
|
2057
|
-
);
|
|
2058
|
-
} else if (/defineConfig\s*\(\s*\{/u.test(updated)) {
|
|
2059
|
-
updated = updated.replace(
|
|
2060
|
-
/defineConfig\s*\(\s*\{/u,
|
|
2061
|
-
'defineConfig({\n plugins: [rsxVitePlugin()],',
|
|
2062
|
-
);
|
|
2063
3892
|
} else {
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
3893
|
+
logInfo(
|
|
3894
|
+
`Vite config already uses the default plugin list: ${viteConfigPath}`,
|
|
3895
|
+
);
|
|
2067
3896
|
}
|
|
2068
3897
|
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
return;
|
|
3898
|
+
for (const staleFile of stalePluginFiles) {
|
|
3899
|
+
removeFileOrDirectoryWithDryRun(staleFile, dryRun);
|
|
2072
3900
|
}
|
|
2073
|
-
|
|
2074
|
-
fs.writeFileSync(viteConfigPath, updated, 'utf8');
|
|
2075
|
-
logOk(`Patched ${viteConfigPath} with RS-X Vite plugin.`);
|
|
2076
3901
|
}
|
|
2077
3902
|
|
|
2078
3903
|
function wireRsxNextWebpack(projectRoot, dryRun) {
|
|
@@ -2154,96 +3979,15 @@ ${patchBlock}
|
|
|
2154
3979
|
logOk(`Patched ${nextConfigJs} with RS-X webpack loader.`);
|
|
2155
3980
|
}
|
|
2156
3981
|
|
|
2157
|
-
function wireRsxAngularWebpack(projectRoot, dryRun) {
|
|
2158
|
-
const angularJsonPath = path.join(projectRoot, 'angular.json');
|
|
2159
|
-
if (!fs.existsSync(angularJsonPath)) {
|
|
2160
|
-
logWarn('angular.json not found. Skipping Angular build integration.');
|
|
2161
|
-
return;
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
createRsxWebpackLoaderFile(projectRoot, dryRun);
|
|
2165
|
-
|
|
2166
|
-
const webpackConfigPath = path.join(projectRoot, 'rsx-angular-webpack.cjs');
|
|
2167
|
-
const webpackConfigSource = `const path = require('node:path');
|
|
2168
|
-
|
|
2169
|
-
module.exports = {
|
|
2170
|
-
module: {
|
|
2171
|
-
rules: [
|
|
2172
|
-
{
|
|
2173
|
-
test: /\\.[jt]sx?$/u,
|
|
2174
|
-
exclude: /node_modules/u,
|
|
2175
|
-
use: [
|
|
2176
|
-
{
|
|
2177
|
-
loader: path.resolve(__dirname, './rsx-webpack-loader.cjs'),
|
|
2178
|
-
},
|
|
2179
|
-
],
|
|
2180
|
-
},
|
|
2181
|
-
],
|
|
2182
|
-
},
|
|
2183
|
-
};
|
|
2184
|
-
`;
|
|
2185
|
-
|
|
2186
|
-
if (dryRun) {
|
|
2187
|
-
logInfo(`[dry-run] create ${webpackConfigPath}`);
|
|
2188
|
-
} else {
|
|
2189
|
-
fs.writeFileSync(webpackConfigPath, webpackConfigSource, 'utf8');
|
|
2190
|
-
logOk(`Created ${webpackConfigPath}`);
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8'));
|
|
2194
|
-
const projects = angularJson.projects ?? {};
|
|
2195
|
-
const projectNames = Object.keys(projects);
|
|
2196
|
-
if (projectNames.length === 0) {
|
|
2197
|
-
logWarn('No Angular projects found in angular.json.');
|
|
2198
|
-
return;
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
const patchPath = 'rsx-angular-webpack.cjs';
|
|
2202
|
-
for (const projectName of projectNames) {
|
|
2203
|
-
const project = projects[projectName];
|
|
2204
|
-
const architect = project.architect ?? project.targets;
|
|
2205
|
-
if (!architect?.build) {
|
|
2206
|
-
continue;
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
const build = architect.build;
|
|
2210
|
-
if (build.builder !== '@angular-builders/custom-webpack:browser') {
|
|
2211
|
-
build.builder = '@angular-builders/custom-webpack:browser';
|
|
2212
|
-
}
|
|
2213
|
-
build.options = build.options ?? {};
|
|
2214
|
-
build.options.customWebpackConfig = build.options.customWebpackConfig ?? {};
|
|
2215
|
-
build.options.customWebpackConfig.path = patchPath;
|
|
2216
|
-
|
|
2217
|
-
if (architect.serve) {
|
|
2218
|
-
const serve = architect.serve;
|
|
2219
|
-
if (serve.builder !== '@angular-builders/custom-webpack:dev-server') {
|
|
2220
|
-
serve.builder = '@angular-builders/custom-webpack:dev-server';
|
|
2221
|
-
}
|
|
2222
|
-
serve.options = serve.options ?? {};
|
|
2223
|
-
serve.options.buildTarget =
|
|
2224
|
-
serve.options.buildTarget ?? `${projectName}:build`;
|
|
2225
|
-
serve.options.browserTarget =
|
|
2226
|
-
serve.options.browserTarget ?? `${projectName}:build`;
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
|
-
if (dryRun) {
|
|
2231
|
-
logInfo(`[dry-run] patch ${angularJsonPath}`);
|
|
2232
|
-
} else {
|
|
2233
|
-
fs.writeFileSync(
|
|
2234
|
-
angularJsonPath,
|
|
2235
|
-
`${JSON.stringify(angularJson, null, 2)}\n`,
|
|
2236
|
-
'utf8',
|
|
2237
|
-
);
|
|
2238
|
-
logOk(`Patched ${angularJsonPath} for RS-X Angular webpack integration.`);
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
3982
|
function runSetupReact(flags) {
|
|
2243
3983
|
const dryRun = Boolean(flags['dry-run']);
|
|
2244
|
-
const pm = detectPackageManager(flags.pm);
|
|
2245
|
-
const tag = resolveInstallTag(flags);
|
|
2246
3984
|
const projectRoot = process.cwd();
|
|
3985
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
3986
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
3987
|
+
const reactTsConfigPath = resolveProjectTsConfig(projectRoot);
|
|
3988
|
+
const reactTsConfigRelative = path
|
|
3989
|
+
.relative(projectRoot, reactTsConfigPath)
|
|
3990
|
+
.replace(/\\/gu, '/');
|
|
2247
3991
|
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2248
3992
|
if (!fs.existsSync(packageJsonPath)) {
|
|
2249
3993
|
logError(`package.json not found in ${projectRoot}`);
|
|
@@ -2266,124 +4010,318 @@ function runSetupReact(flags) {
|
|
|
2266
4010
|
'skip-vscode': true,
|
|
2267
4011
|
});
|
|
2268
4012
|
if (!Boolean(flags['skip-install'])) {
|
|
2269
|
-
|
|
4013
|
+
const specs = resolveLocalRsxSpecs(projectRoot, flags, {
|
|
4014
|
+
tag,
|
|
4015
|
+
includeReactPackage: true,
|
|
4016
|
+
});
|
|
4017
|
+
installResolvedPackages(pm, ['@rs-x/react'], {
|
|
2270
4018
|
dev: false,
|
|
2271
4019
|
dryRun,
|
|
2272
4020
|
tag,
|
|
4021
|
+
specs,
|
|
4022
|
+
cwd: projectRoot,
|
|
2273
4023
|
label: 'RS-X React bindings',
|
|
2274
4024
|
});
|
|
4025
|
+
installResolvedPackages(pm, ['@rs-x/cli'], {
|
|
4026
|
+
dev: true,
|
|
4027
|
+
dryRun,
|
|
4028
|
+
tag,
|
|
4029
|
+
specs,
|
|
4030
|
+
cwd: projectRoot,
|
|
4031
|
+
label: 'RS-X CLI',
|
|
4032
|
+
});
|
|
2275
4033
|
} else {
|
|
2276
4034
|
logInfo('Skipping RS-X React bindings install (--skip-install).');
|
|
2277
4035
|
}
|
|
4036
|
+
ensureRsxConfigFile(projectRoot, 'react', dryRun);
|
|
4037
|
+
upsertScriptInPackageJson(
|
|
4038
|
+
projectRoot,
|
|
4039
|
+
'build:rsx',
|
|
4040
|
+
`rsx build --project ${reactTsConfigRelative} --no-emit --prod`,
|
|
4041
|
+
dryRun,
|
|
4042
|
+
);
|
|
4043
|
+
upsertScriptInPackageJson(
|
|
4044
|
+
projectRoot,
|
|
4045
|
+
'typecheck:rsx',
|
|
4046
|
+
`rsx typecheck --project ${reactTsConfigRelative}`,
|
|
4047
|
+
dryRun,
|
|
4048
|
+
);
|
|
4049
|
+
upsertScriptInPackageJson(
|
|
4050
|
+
projectRoot,
|
|
4051
|
+
'dev',
|
|
4052
|
+
'npm run build:rsx && vite',
|
|
4053
|
+
dryRun,
|
|
4054
|
+
);
|
|
4055
|
+
upsertScriptInPackageJson(
|
|
4056
|
+
projectRoot,
|
|
4057
|
+
'build',
|
|
4058
|
+
'npm run build:rsx && vite build',
|
|
4059
|
+
dryRun,
|
|
4060
|
+
);
|
|
2278
4061
|
wireRsxVitePlugin(projectRoot, dryRun);
|
|
2279
|
-
if (
|
|
2280
|
-
|
|
4062
|
+
if (resolveCliVerifyFlag(projectRoot, flags, 'setup')) {
|
|
4063
|
+
verifySetupOutput(projectRoot, 'react');
|
|
2281
4064
|
}
|
|
2282
4065
|
logOk('RS-X React setup completed.');
|
|
2283
4066
|
}
|
|
2284
4067
|
|
|
2285
4068
|
function runSetupNext(flags) {
|
|
2286
4069
|
const dryRun = Boolean(flags['dry-run']);
|
|
2287
|
-
const
|
|
2288
|
-
const
|
|
4070
|
+
const projectRoot = process.cwd();
|
|
4071
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
4072
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
4073
|
+
const nextTsConfigPath = resolveProjectTsConfig(projectRoot);
|
|
4074
|
+
const nextTsConfigRelative = path
|
|
4075
|
+
.relative(projectRoot, nextTsConfigPath)
|
|
4076
|
+
.replace(/\\/gu, '/');
|
|
2289
4077
|
runInit({
|
|
2290
4078
|
...flags,
|
|
2291
4079
|
'skip-vscode': true,
|
|
2292
4080
|
});
|
|
2293
4081
|
if (!Boolean(flags['skip-install'])) {
|
|
2294
|
-
|
|
4082
|
+
const specs = resolveLocalRsxSpecs(projectRoot, flags, {
|
|
4083
|
+
tag,
|
|
4084
|
+
includeReactPackage: true,
|
|
4085
|
+
});
|
|
4086
|
+
installResolvedPackages(pm, ['@rs-x/react'], {
|
|
2295
4087
|
dev: false,
|
|
2296
4088
|
dryRun,
|
|
2297
4089
|
tag,
|
|
4090
|
+
specs,
|
|
4091
|
+
cwd: projectRoot,
|
|
2298
4092
|
label: 'RS-X React bindings',
|
|
2299
4093
|
});
|
|
2300
4094
|
} else {
|
|
2301
4095
|
logInfo('Skipping RS-X React bindings install (--skip-install).');
|
|
2302
4096
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
4097
|
+
ensureRsxConfigFile(projectRoot, 'next', dryRun);
|
|
4098
|
+
upsertScriptInPackageJson(
|
|
4099
|
+
projectRoot,
|
|
4100
|
+
'build:rsx',
|
|
4101
|
+
`rsx build --project ${nextTsConfigRelative} --no-emit --prod`,
|
|
4102
|
+
dryRun,
|
|
4103
|
+
);
|
|
4104
|
+
upsertScriptInPackageJson(
|
|
4105
|
+
projectRoot,
|
|
4106
|
+
'typecheck:rsx',
|
|
4107
|
+
`rsx typecheck --project ${nextTsConfigRelative}`,
|
|
4108
|
+
dryRun,
|
|
4109
|
+
);
|
|
4110
|
+
upsertScriptInPackageJson(
|
|
4111
|
+
projectRoot,
|
|
4112
|
+
'dev',
|
|
4113
|
+
'npm run build:rsx && next dev',
|
|
4114
|
+
dryRun,
|
|
4115
|
+
);
|
|
4116
|
+
upsertScriptInPackageJson(
|
|
4117
|
+
projectRoot,
|
|
4118
|
+
'build',
|
|
4119
|
+
'npm run build:rsx && next build',
|
|
4120
|
+
dryRun,
|
|
4121
|
+
);
|
|
4122
|
+
wireRsxNextWebpack(projectRoot, dryRun);
|
|
4123
|
+
if (resolveCliVerifyFlag(projectRoot, flags, 'setup')) {
|
|
4124
|
+
verifySetupOutput(projectRoot, 'next');
|
|
2306
4125
|
}
|
|
2307
4126
|
logOk('RS-X Next.js setup completed.');
|
|
2308
4127
|
}
|
|
2309
4128
|
|
|
2310
4129
|
function runSetupVue(flags) {
|
|
2311
4130
|
const dryRun = Boolean(flags['dry-run']);
|
|
2312
|
-
const
|
|
2313
|
-
const
|
|
4131
|
+
const projectRoot = process.cwd();
|
|
4132
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
4133
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
2314
4134
|
runInit({
|
|
2315
4135
|
...flags,
|
|
2316
4136
|
'skip-vscode': true,
|
|
2317
4137
|
});
|
|
2318
4138
|
if (!Boolean(flags['skip-install'])) {
|
|
2319
|
-
|
|
4139
|
+
const specs = resolveLocalRsxSpecs(projectRoot, flags, {
|
|
4140
|
+
tag,
|
|
4141
|
+
includeVuePackage: true,
|
|
4142
|
+
});
|
|
4143
|
+
installResolvedPackages(pm, ['@rs-x/vue'], {
|
|
2320
4144
|
dev: false,
|
|
2321
4145
|
dryRun,
|
|
2322
4146
|
tag,
|
|
4147
|
+
specs,
|
|
4148
|
+
cwd: projectRoot,
|
|
2323
4149
|
label: 'RS-X Vue bindings',
|
|
2324
4150
|
});
|
|
4151
|
+
installResolvedPackages(pm, ['@rs-x/cli'], {
|
|
4152
|
+
dev: true,
|
|
4153
|
+
dryRun,
|
|
4154
|
+
tag,
|
|
4155
|
+
specs,
|
|
4156
|
+
cwd: projectRoot,
|
|
4157
|
+
label: 'RS-X CLI',
|
|
4158
|
+
});
|
|
2325
4159
|
} else {
|
|
2326
4160
|
logInfo('Skipping RS-X Vue bindings install (--skip-install).');
|
|
2327
4161
|
}
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
4162
|
+
ensureRsxConfigFile(projectRoot, 'vuejs', dryRun);
|
|
4163
|
+
upsertScriptInPackageJson(
|
|
4164
|
+
projectRoot,
|
|
4165
|
+
'build:rsx',
|
|
4166
|
+
'rsx build --project tsconfig.app.json --no-emit --prod',
|
|
4167
|
+
dryRun,
|
|
4168
|
+
);
|
|
4169
|
+
upsertScriptInPackageJson(
|
|
4170
|
+
projectRoot,
|
|
4171
|
+
'typecheck:rsx',
|
|
4172
|
+
'rsx typecheck --project tsconfig.app.json',
|
|
4173
|
+
dryRun,
|
|
4174
|
+
);
|
|
4175
|
+
upsertScriptInPackageJson(
|
|
4176
|
+
projectRoot,
|
|
4177
|
+
'dev',
|
|
4178
|
+
'npm run build:rsx && vite',
|
|
4179
|
+
dryRun,
|
|
4180
|
+
);
|
|
4181
|
+
upsertScriptInPackageJson(
|
|
4182
|
+
projectRoot,
|
|
4183
|
+
'build',
|
|
4184
|
+
'npm run build:rsx && vue-tsc -b && vite build',
|
|
4185
|
+
dryRun,
|
|
4186
|
+
);
|
|
4187
|
+
const vueTsConfigPath = path.join(projectRoot, 'tsconfig.app.json');
|
|
4188
|
+
upsertTypescriptPluginInTsConfig(vueTsConfigPath, dryRun);
|
|
4189
|
+
ensureTsConfigIncludePattern(vueTsConfigPath, 'src/**/*.d.ts', dryRun);
|
|
4190
|
+
ensureVueEnvTypes(projectRoot, dryRun);
|
|
4191
|
+
wireRsxVitePlugin(projectRoot, dryRun);
|
|
4192
|
+
if (resolveCliVerifyFlag(projectRoot, flags, 'setup')) {
|
|
4193
|
+
verifySetupOutput(projectRoot, 'vuejs');
|
|
2331
4194
|
}
|
|
2332
4195
|
logOk('RS-X Vue setup completed.');
|
|
2333
4196
|
}
|
|
2334
4197
|
|
|
2335
4198
|
function runSetupAngular(flags) {
|
|
2336
4199
|
const dryRun = Boolean(flags['dry-run']);
|
|
2337
|
-
const
|
|
2338
|
-
const
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
4200
|
+
const projectRoot = process.cwd();
|
|
4201
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
4202
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
4203
|
+
const angularTsConfigPath = resolveAngularProjectTsConfig(projectRoot);
|
|
4204
|
+
const angularTsConfigRelative = path
|
|
4205
|
+
.relative(projectRoot, angularTsConfigPath)
|
|
4206
|
+
.replace(/\\/gu, '/');
|
|
2344
4207
|
|
|
2345
4208
|
if (!Boolean(flags['skip-install'])) {
|
|
2346
|
-
|
|
4209
|
+
installRuntimePackages(pm, dryRun, tag, projectRoot, flags);
|
|
4210
|
+
installCompilerPackages(pm, dryRun, tag, projectRoot, flags);
|
|
4211
|
+
const specs = resolveLocalRsxSpecs(projectRoot, flags, {
|
|
4212
|
+
tag,
|
|
4213
|
+
includeAngularPackage: true,
|
|
4214
|
+
});
|
|
4215
|
+
installResolvedPackages(pm, ['@rs-x/angular'], {
|
|
2347
4216
|
dev: false,
|
|
2348
4217
|
dryRun,
|
|
2349
4218
|
tag,
|
|
4219
|
+
specs,
|
|
4220
|
+
cwd: projectRoot,
|
|
2350
4221
|
label: 'RS-X Angular bindings',
|
|
2351
4222
|
});
|
|
2352
|
-
|
|
4223
|
+
installResolvedPackages(pm, ['@rs-x/cli'], {
|
|
2353
4224
|
dev: true,
|
|
2354
4225
|
dryRun,
|
|
2355
|
-
|
|
4226
|
+
tag,
|
|
4227
|
+
specs,
|
|
4228
|
+
cwd: projectRoot,
|
|
4229
|
+
label: 'RS-X CLI',
|
|
2356
4230
|
});
|
|
2357
4231
|
} else {
|
|
4232
|
+
logInfo('Skipping package installation (--skip-install).');
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
const entryFile = resolveEntryFile(projectRoot, 'angular', flags.entry);
|
|
4236
|
+
if (entryFile) {
|
|
4237
|
+
logInfo(`Using Angular entry file: ${entryFile}`);
|
|
4238
|
+
ensureAngularProvidersInEntry(entryFile, dryRun);
|
|
4239
|
+
} else {
|
|
4240
|
+
logWarn('Could not detect an Angular entry file automatically.');
|
|
2358
4241
|
logInfo(
|
|
2359
|
-
'
|
|
4242
|
+
'Manual setup: add providexRsx() to bootstrapApplication(...) in your main entry file.',
|
|
2360
4243
|
);
|
|
2361
4244
|
}
|
|
2362
4245
|
|
|
2363
|
-
|
|
4246
|
+
ensureRsxConfigFile(projectRoot, 'angular', dryRun);
|
|
4247
|
+
|
|
2364
4248
|
upsertScriptInPackageJson(
|
|
2365
|
-
|
|
4249
|
+
projectRoot,
|
|
2366
4250
|
'build:rsx',
|
|
2367
|
-
|
|
4251
|
+
`rsx build --project ${angularTsConfigRelative} --no-emit --prod`,
|
|
2368
4252
|
dryRun,
|
|
2369
4253
|
);
|
|
2370
4254
|
upsertScriptInPackageJson(
|
|
2371
|
-
|
|
4255
|
+
projectRoot,
|
|
2372
4256
|
'typecheck:rsx',
|
|
2373
|
-
|
|
4257
|
+
`rsx typecheck --project ${angularTsConfigRelative}`,
|
|
4258
|
+
dryRun,
|
|
4259
|
+
);
|
|
4260
|
+
upsertScriptInPackageJson(
|
|
4261
|
+
projectRoot,
|
|
4262
|
+
'prebuild',
|
|
4263
|
+
'npm run build:rsx',
|
|
4264
|
+
dryRun,
|
|
4265
|
+
);
|
|
4266
|
+
upsertScriptInPackageJson(
|
|
4267
|
+
projectRoot,
|
|
4268
|
+
'start',
|
|
4269
|
+
'npm run build:rsx && ng serve',
|
|
2374
4270
|
dryRun,
|
|
2375
4271
|
);
|
|
2376
4272
|
|
|
2377
|
-
|
|
2378
|
-
|
|
4273
|
+
const rsxRegistrationFile = path.join(
|
|
4274
|
+
projectRoot,
|
|
4275
|
+
'src/rsx-generated/rsx-aot-registration.generated.ts',
|
|
4276
|
+
);
|
|
4277
|
+
const angularJsonPath = path.join(projectRoot, 'angular.json');
|
|
4278
|
+
if (fs.existsSync(angularJsonPath)) {
|
|
4279
|
+
const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8'));
|
|
4280
|
+
const projects = angularJson.projects ?? {};
|
|
4281
|
+
for (const projectConfig of Object.values(projects)) {
|
|
4282
|
+
const buildOptions = projectConfig?.architect?.build?.options;
|
|
4283
|
+
if (buildOptions && typeof buildOptions === 'object') {
|
|
4284
|
+
buildOptions.preserveSymlinks = true;
|
|
4285
|
+
}
|
|
4286
|
+
if (
|
|
4287
|
+
projectConfig?.architect?.build?.configurations?.production?.budgets
|
|
4288
|
+
) {
|
|
4289
|
+
delete projectConfig.architect.build.configurations.production.budgets;
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
if (dryRun) {
|
|
4293
|
+
logInfo(
|
|
4294
|
+
`[dry-run] patch ${angularJsonPath} (preserveSymlinks, production budgets)`,
|
|
4295
|
+
);
|
|
4296
|
+
} else {
|
|
4297
|
+
fs.writeFileSync(
|
|
4298
|
+
angularJsonPath,
|
|
4299
|
+
`${JSON.stringify(angularJson, null, 2)}\n`,
|
|
4300
|
+
'utf8',
|
|
4301
|
+
);
|
|
4302
|
+
logOk(
|
|
4303
|
+
`Patched ${angularJsonPath} (preserveSymlinks, production budgets).`,
|
|
4304
|
+
);
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
ensureAngularPolyfillsContainsFile({
|
|
4308
|
+
projectRoot,
|
|
4309
|
+
configPath: angularTsConfigPath,
|
|
4310
|
+
filePath: rsxRegistrationFile,
|
|
4311
|
+
dryRun,
|
|
4312
|
+
});
|
|
4313
|
+
|
|
4314
|
+
if (resolveCliVerifyFlag(projectRoot, flags, 'setup')) {
|
|
4315
|
+
verifySetupOutput(projectRoot, 'angular');
|
|
2379
4316
|
}
|
|
4317
|
+
|
|
2380
4318
|
logOk('RS-X Angular setup completed.');
|
|
2381
4319
|
}
|
|
2382
4320
|
|
|
2383
4321
|
function runSetupAuto(flags) {
|
|
2384
4322
|
const projectRoot = process.cwd();
|
|
2385
4323
|
const context = detectProjectContext(projectRoot);
|
|
2386
|
-
const tag =
|
|
4324
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
2387
4325
|
|
|
2388
4326
|
if (context === 'react') {
|
|
2389
4327
|
logInfo('Auto-detected framework: react');
|
|
@@ -2410,10 +4348,21 @@ function runSetupAuto(flags) {
|
|
|
2410
4348
|
}
|
|
2411
4349
|
|
|
2412
4350
|
logInfo('No framework-specific setup detected; running generic setup.');
|
|
2413
|
-
const pm =
|
|
2414
|
-
installRuntimePackages(
|
|
2415
|
-
|
|
2416
|
-
|
|
4351
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
4352
|
+
installRuntimePackages(
|
|
4353
|
+
pm,
|
|
4354
|
+
Boolean(flags['dry-run']),
|
|
4355
|
+
tag,
|
|
4356
|
+
projectRoot,
|
|
4357
|
+
flags,
|
|
4358
|
+
);
|
|
4359
|
+
installCompilerPackages(
|
|
4360
|
+
pm,
|
|
4361
|
+
Boolean(flags['dry-run']),
|
|
4362
|
+
tag,
|
|
4363
|
+
projectRoot,
|
|
4364
|
+
flags,
|
|
4365
|
+
);
|
|
2417
4366
|
}
|
|
2418
4367
|
|
|
2419
4368
|
function resolveProjectModule(projectRoot, moduleName) {
|
|
@@ -2430,8 +4379,13 @@ function runBuild(flags) {
|
|
|
2430
4379
|
const dryRun = Boolean(flags['dry-run']);
|
|
2431
4380
|
const noEmit = Boolean(flags['no-emit']);
|
|
2432
4381
|
const prodMode = parseBooleanFlag(flags.prod, false);
|
|
4382
|
+
const invocationConfig = resolveRsxBuildConfig(invocationRoot);
|
|
2433
4383
|
const projectArg =
|
|
2434
|
-
typeof flags.project === 'string'
|
|
4384
|
+
typeof flags.project === 'string'
|
|
4385
|
+
? flags.project
|
|
4386
|
+
: typeof invocationConfig.tsconfig === 'string'
|
|
4387
|
+
? invocationConfig.tsconfig
|
|
4388
|
+
: 'tsconfig.json';
|
|
2435
4389
|
const configPath = path.resolve(invocationRoot, projectArg);
|
|
2436
4390
|
const projectRoot = path.dirname(configPath);
|
|
2437
4391
|
const context = detectProjectContext(projectRoot);
|
|
@@ -2530,7 +4484,9 @@ function runBuild(flags) {
|
|
|
2530
4484
|
const outDirOverride =
|
|
2531
4485
|
typeof flags['out-dir'] === 'string'
|
|
2532
4486
|
? path.resolve(projectRoot, flags['out-dir'])
|
|
2533
|
-
:
|
|
4487
|
+
: typeof rsxBuildConfig.outDir === 'string'
|
|
4488
|
+
? path.resolve(projectRoot, rsxBuildConfig.outDir)
|
|
4489
|
+
: null;
|
|
2534
4490
|
const outDir =
|
|
2535
4491
|
outDirOverride ??
|
|
2536
4492
|
parsedConfig.options.outDir ??
|
|
@@ -2978,9 +4934,14 @@ function ensureAngularPolyfillsContainsFile({
|
|
|
2978
4934
|
});
|
|
2979
4935
|
|
|
2980
4936
|
const selectedEntries = targetEntries.length > 0 ? targetEntries : entries;
|
|
2981
|
-
const
|
|
4937
|
+
const polyfillsRelativePath = path
|
|
2982
4938
|
.relative(projectRoot, filePath)
|
|
2983
4939
|
.replace(/\\/g, '/');
|
|
4940
|
+
const polyfillsPath =
|
|
4941
|
+
polyfillsRelativePath.startsWith('./') ||
|
|
4942
|
+
polyfillsRelativePath.startsWith('../')
|
|
4943
|
+
? polyfillsRelativePath
|
|
4944
|
+
: `./${polyfillsRelativePath}`;
|
|
2984
4945
|
|
|
2985
4946
|
let changed = false;
|
|
2986
4947
|
const isRsxAotRegistrationEntry = (entry) =>
|
|
@@ -3047,20 +5008,286 @@ function ensureAngularPolyfillsContainsFile({
|
|
|
3047
5008
|
logOk(`Updated angular.json to inject RS-X AOT runtime registration.`);
|
|
3048
5009
|
}
|
|
3049
5010
|
|
|
3050
|
-
function
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
return {};
|
|
5011
|
+
function readJsonFileIfPresent(filePath) {
|
|
5012
|
+
if (!fs.existsSync(filePath)) {
|
|
5013
|
+
return null;
|
|
3054
5014
|
}
|
|
3055
5015
|
|
|
3056
5016
|
try {
|
|
3057
|
-
|
|
3058
|
-
const rsxConfig = packageJson.rsx ?? {};
|
|
3059
|
-
const buildConfig = rsxConfig.build ?? {};
|
|
3060
|
-
return typeof buildConfig === 'object' && buildConfig ? buildConfig : {};
|
|
5017
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
3061
5018
|
} catch {
|
|
3062
|
-
return
|
|
5019
|
+
return null;
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
|
|
5023
|
+
function validateRsxConfigShape(config, filePath) {
|
|
5024
|
+
if (typeof config !== 'object' || !config || Array.isArray(config)) {
|
|
5025
|
+
logError(
|
|
5026
|
+
`Invalid RS-X config in ${filePath}: expected a JSON object at the top level.`,
|
|
5027
|
+
);
|
|
5028
|
+
process.exit(1);
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
const build = config.build;
|
|
5032
|
+
if (build !== undefined) {
|
|
5033
|
+
if (typeof build !== 'object' || !build || Array.isArray(build)) {
|
|
5034
|
+
logError(
|
|
5035
|
+
`Invalid RS-X config in ${filePath}: "build" must be an object.`,
|
|
5036
|
+
);
|
|
5037
|
+
process.exit(1);
|
|
5038
|
+
}
|
|
5039
|
+
|
|
5040
|
+
const stringKeys = ['preparseFile', 'compiledFile', 'registrationFile'];
|
|
5041
|
+
const extraStringKeys = ['tsconfig', 'outDir'];
|
|
5042
|
+
for (const key of extraStringKeys) {
|
|
5043
|
+
if (build[key] !== undefined && typeof build[key] !== 'string') {
|
|
5044
|
+
logError(
|
|
5045
|
+
`Invalid RS-X config in ${filePath}: "build.${key}" must be a string.`,
|
|
5046
|
+
);
|
|
5047
|
+
process.exit(1);
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
for (const key of stringKeys) {
|
|
5051
|
+
if (build[key] !== undefined && typeof build[key] !== 'string') {
|
|
5052
|
+
logError(
|
|
5053
|
+
`Invalid RS-X config in ${filePath}: "build.${key}" must be a string.`,
|
|
5054
|
+
);
|
|
5055
|
+
process.exit(1);
|
|
5056
|
+
}
|
|
5057
|
+
}
|
|
5058
|
+
|
|
5059
|
+
const booleanKeys = ['preparse', 'compiled', 'compiledResolvedEvaluator'];
|
|
5060
|
+
for (const key of booleanKeys) {
|
|
5061
|
+
if (build[key] !== undefined && typeof build[key] !== 'boolean') {
|
|
5062
|
+
logError(
|
|
5063
|
+
`Invalid RS-X config in ${filePath}: "build.${key}" must be a boolean.`,
|
|
5064
|
+
);
|
|
5065
|
+
process.exit(1);
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
|
|
5070
|
+
const cli = config.cli;
|
|
5071
|
+
if (cli !== undefined) {
|
|
5072
|
+
if (typeof cli !== 'object' || !cli || Array.isArray(cli)) {
|
|
5073
|
+
logError(`Invalid RS-X config in ${filePath}: "cli" must be an object.`);
|
|
5074
|
+
process.exit(1);
|
|
5075
|
+
}
|
|
5076
|
+
|
|
5077
|
+
const cliStringKeys = ['packageManager', 'installTag'];
|
|
5078
|
+
for (const key of cliStringKeys) {
|
|
5079
|
+
if (cli[key] !== undefined && typeof cli[key] !== 'string') {
|
|
5080
|
+
logError(
|
|
5081
|
+
`Invalid RS-X config in ${filePath}: "cli.${key}" must be a string.`,
|
|
5082
|
+
);
|
|
5083
|
+
process.exit(1);
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
|
|
5087
|
+
if (
|
|
5088
|
+
cli.packageManager !== undefined &&
|
|
5089
|
+
!['pnpm', 'npm', 'yarn', 'bun'].includes(cli.packageManager)
|
|
5090
|
+
) {
|
|
5091
|
+
logError(
|
|
5092
|
+
`Invalid RS-X config in ${filePath}: "cli.packageManager" must be one of pnpm, npm, yarn, or bun.`,
|
|
5093
|
+
);
|
|
5094
|
+
process.exit(1);
|
|
5095
|
+
}
|
|
5096
|
+
|
|
5097
|
+
if (
|
|
5098
|
+
cli.installTag !== undefined &&
|
|
5099
|
+
!['latest', 'next'].includes(cli.installTag)
|
|
5100
|
+
) {
|
|
5101
|
+
logError(
|
|
5102
|
+
`Invalid RS-X config in ${filePath}: "cli.installTag" must be "latest" or "next".`,
|
|
5103
|
+
);
|
|
5104
|
+
process.exit(1);
|
|
5105
|
+
}
|
|
5106
|
+
|
|
5107
|
+
const cliBooleanSections = ['setup', 'project'];
|
|
5108
|
+
for (const key of cliBooleanSections) {
|
|
5109
|
+
if (cli[key] !== undefined) {
|
|
5110
|
+
if (
|
|
5111
|
+
typeof cli[key] !== 'object' ||
|
|
5112
|
+
!cli[key] ||
|
|
5113
|
+
Array.isArray(cli[key])
|
|
5114
|
+
) {
|
|
5115
|
+
logError(
|
|
5116
|
+
`Invalid RS-X config in ${filePath}: "cli.${key}" must be an object.`,
|
|
5117
|
+
);
|
|
5118
|
+
process.exit(1);
|
|
5119
|
+
}
|
|
5120
|
+
if (
|
|
5121
|
+
cli[key].verify !== undefined &&
|
|
5122
|
+
typeof cli[key].verify !== 'boolean'
|
|
5123
|
+
) {
|
|
5124
|
+
logError(
|
|
5125
|
+
`Invalid RS-X config in ${filePath}: "cli.${key}.verify" must be a boolean.`,
|
|
5126
|
+
);
|
|
5127
|
+
process.exit(1);
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
|
|
5132
|
+
const add = cli.add;
|
|
5133
|
+
if (add !== undefined) {
|
|
5134
|
+
if (typeof add !== 'object' || !add || Array.isArray(add)) {
|
|
5135
|
+
logError(
|
|
5136
|
+
`Invalid RS-X config in ${filePath}: "cli.add" must be an object.`,
|
|
5137
|
+
);
|
|
5138
|
+
process.exit(1);
|
|
5139
|
+
}
|
|
5140
|
+
|
|
5141
|
+
if (
|
|
5142
|
+
add.defaultDirectory !== undefined &&
|
|
5143
|
+
typeof add.defaultDirectory !== 'string'
|
|
5144
|
+
) {
|
|
5145
|
+
logError(
|
|
5146
|
+
`Invalid RS-X config in ${filePath}: "cli.add.defaultDirectory" must be a string.`,
|
|
5147
|
+
);
|
|
5148
|
+
process.exit(1);
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
if (add.searchRoots !== undefined) {
|
|
5152
|
+
if (
|
|
5153
|
+
!Array.isArray(add.searchRoots) ||
|
|
5154
|
+
add.searchRoots.some((entry) => typeof entry !== 'string')
|
|
5155
|
+
) {
|
|
5156
|
+
logError(
|
|
5157
|
+
`Invalid RS-X config in ${filePath}: "cli.add.searchRoots" must be an array of strings.`,
|
|
5158
|
+
);
|
|
5159
|
+
process.exit(1);
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
function mergeRsxConfig(baseConfig, overrideConfig) {
|
|
5167
|
+
const base = typeof baseConfig === 'object' && baseConfig ? baseConfig : {};
|
|
5168
|
+
const override =
|
|
5169
|
+
typeof overrideConfig === 'object' && overrideConfig ? overrideConfig : {};
|
|
5170
|
+
|
|
5171
|
+
return {
|
|
5172
|
+
...base,
|
|
5173
|
+
...override,
|
|
5174
|
+
build: {
|
|
5175
|
+
...(typeof base.build === 'object' && base.build ? base.build : {}),
|
|
5176
|
+
...(typeof override.build === 'object' && override.build
|
|
5177
|
+
? override.build
|
|
5178
|
+
: {}),
|
|
5179
|
+
},
|
|
5180
|
+
cli: {
|
|
5181
|
+
...(typeof base.cli === 'object' && base.cli ? base.cli : {}),
|
|
5182
|
+
...(typeof override.cli === 'object' && override.cli ? override.cli : {}),
|
|
5183
|
+
add: {
|
|
5184
|
+
...(typeof base.cli?.add === 'object' && base.cli?.add
|
|
5185
|
+
? base.cli.add
|
|
5186
|
+
: {}),
|
|
5187
|
+
...(typeof override.cli?.add === 'object' && override.cli?.add
|
|
5188
|
+
? override.cli.add
|
|
5189
|
+
: {}),
|
|
5190
|
+
},
|
|
5191
|
+
},
|
|
5192
|
+
};
|
|
5193
|
+
}
|
|
5194
|
+
|
|
5195
|
+
function resolveRsxProjectConfig(projectRoot) {
|
|
5196
|
+
const fileConfigPath = path.join(projectRoot, 'rsx.config.json');
|
|
5197
|
+
const fileRsxConfig = readJsonFileIfPresent(fileConfigPath) ?? {};
|
|
5198
|
+
validateRsxConfigShape(fileRsxConfig, fileConfigPath);
|
|
5199
|
+
return mergeRsxConfig({}, fileRsxConfig);
|
|
5200
|
+
}
|
|
5201
|
+
|
|
5202
|
+
function defaultCliAddConfigForTemplate(template) {
|
|
5203
|
+
if (template === 'next' || template === 'nextjs') {
|
|
5204
|
+
return {
|
|
5205
|
+
defaultDirectory: 'app/expressions',
|
|
5206
|
+
searchRoots: ['app', 'src', 'expressions'],
|
|
5207
|
+
};
|
|
5208
|
+
}
|
|
5209
|
+
|
|
5210
|
+
return {
|
|
5211
|
+
defaultDirectory: 'src/expressions',
|
|
5212
|
+
searchRoots: ['src', 'app', 'expressions'],
|
|
5213
|
+
};
|
|
5214
|
+
}
|
|
5215
|
+
|
|
5216
|
+
function defaultCliConfigForTemplate(template) {
|
|
5217
|
+
return {
|
|
5218
|
+
packageManager: 'npm',
|
|
5219
|
+
installTag: 'next',
|
|
5220
|
+
setup: {
|
|
5221
|
+
verify: false,
|
|
5222
|
+
},
|
|
5223
|
+
project: {
|
|
5224
|
+
verify: false,
|
|
5225
|
+
},
|
|
5226
|
+
add: defaultCliAddConfigForTemplate(template),
|
|
5227
|
+
};
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5230
|
+
function defaultRsxBuildConfigForTemplate(template) {
|
|
5231
|
+
if (template === 'next' || template === 'nextjs') {
|
|
5232
|
+
return {
|
|
5233
|
+
preparse: true,
|
|
5234
|
+
preparseFile: 'app/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
5235
|
+
compiled: true,
|
|
5236
|
+
compiledFile: 'app/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
5237
|
+
registrationFile: 'app/rsx-generated/rsx-aot-registration.generated.ts',
|
|
5238
|
+
compiledResolvedEvaluator: false,
|
|
5239
|
+
};
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5242
|
+
return {
|
|
5243
|
+
preparse: true,
|
|
5244
|
+
preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
5245
|
+
compiled: true,
|
|
5246
|
+
compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
5247
|
+
registrationFile: 'src/rsx-generated/rsx-aot-registration.generated.ts',
|
|
5248
|
+
compiledResolvedEvaluator: false,
|
|
5249
|
+
};
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
function ensureRsxConfigFile(projectRoot, template, dryRun) {
|
|
5253
|
+
const configPath = path.join(projectRoot, 'rsx.config.json');
|
|
5254
|
+
const defaultConfig = {
|
|
5255
|
+
build: defaultRsxBuildConfigForTemplate(template),
|
|
5256
|
+
cli: defaultCliConfigForTemplate(template),
|
|
5257
|
+
};
|
|
5258
|
+
|
|
5259
|
+
const existingConfig = readJsonFileIfPresent(configPath);
|
|
5260
|
+
const nextConfig = mergeRsxConfig(defaultConfig, existingConfig ?? {});
|
|
5261
|
+
|
|
5262
|
+
if (
|
|
5263
|
+
existingConfig &&
|
|
5264
|
+
JSON.stringify(existingConfig) === JSON.stringify(nextConfig)
|
|
5265
|
+
) {
|
|
5266
|
+
return false;
|
|
5267
|
+
}
|
|
5268
|
+
|
|
5269
|
+
if (dryRun) {
|
|
5270
|
+
logInfo(`[dry-run] ${existingConfig ? 'patch' : 'create'} ${configPath}`);
|
|
5271
|
+
return true;
|
|
3063
5272
|
}
|
|
5273
|
+
|
|
5274
|
+
fs.writeFileSync(
|
|
5275
|
+
configPath,
|
|
5276
|
+
`${JSON.stringify(nextConfig, null, 2)}\n`,
|
|
5277
|
+
'utf8',
|
|
5278
|
+
);
|
|
5279
|
+
logOk(`${existingConfig ? 'Patched' : 'Created'} ${configPath}`);
|
|
5280
|
+
return true;
|
|
5281
|
+
}
|
|
5282
|
+
|
|
5283
|
+
function resolveRsxBuildConfig(projectRoot) {
|
|
5284
|
+
const buildConfig = resolveRsxProjectConfig(projectRoot).build ?? {};
|
|
5285
|
+
return typeof buildConfig === 'object' && buildConfig ? buildConfig : {};
|
|
5286
|
+
}
|
|
5287
|
+
|
|
5288
|
+
function resolveRsxCliConfig(projectRoot) {
|
|
5289
|
+
const cliConfig = resolveRsxProjectConfig(projectRoot).cli ?? {};
|
|
5290
|
+
return typeof cliConfig === 'object' && cliConfig ? cliConfig : {};
|
|
3064
5291
|
}
|
|
3065
5292
|
|
|
3066
5293
|
function parseBooleanFlag(value, defaultValue) {
|
|
@@ -3121,7 +5348,7 @@ function printGeneralHelp() {
|
|
|
3121
5348
|
console.log(
|
|
3122
5349
|
' typecheck Type-check project + RS-X semantic checks',
|
|
3123
5350
|
);
|
|
3124
|
-
console.log(' version | -v
|
|
5351
|
+
console.log(' version | v | -v Print CLI version');
|
|
3125
5352
|
console.log('');
|
|
3126
5353
|
console.log('Help Aliases:');
|
|
3127
5354
|
console.log(' rsx -h');
|
|
@@ -3155,14 +5382,28 @@ function printAddHelp() {
|
|
|
3155
5382
|
console.log(
|
|
3156
5383
|
' - Prompts for expression export name (must be valid TS identifier)',
|
|
3157
5384
|
);
|
|
5385
|
+
console.log(" - Prompts for the initial expression string (default: 'a')");
|
|
5386
|
+
console.log(
|
|
5387
|
+
' - Seeds the generated model with top-level identifiers found in that expression when possible',
|
|
5388
|
+
);
|
|
3158
5389
|
console.log(
|
|
3159
5390
|
' - Prompts whether file name should be kebab-case (default: yes)',
|
|
3160
5391
|
);
|
|
3161
5392
|
console.log(' - Prompts for output directory (relative or absolute)');
|
|
3162
|
-
console.log(' - Prompts whether to reuse an existing model file');
|
|
3163
|
-
console.log(' - Creates <name>.ts and optionally creates <name>.model.ts');
|
|
3164
5393
|
console.log(
|
|
3165
|
-
' -
|
|
5394
|
+
' - Prompts for add mode: new one-file, new separate-model, or update existing',
|
|
5395
|
+
);
|
|
5396
|
+
console.log(
|
|
5397
|
+
' - Defaults to keeping the model in the same file as the expression',
|
|
5398
|
+
);
|
|
5399
|
+
console.log(
|
|
5400
|
+
' - If you choose an existing file, shows a list of files that already contain RS-X expressions',
|
|
5401
|
+
);
|
|
5402
|
+
console.log(
|
|
5403
|
+
' - Can still create or reuse a separate model file when you opt out of same-file mode',
|
|
5404
|
+
);
|
|
5405
|
+
console.log(
|
|
5406
|
+
' - Respects rsx.config.json (`cli.add`) for add defaults and file discovery',
|
|
3166
5407
|
);
|
|
3167
5408
|
}
|
|
3168
5409
|
|
|
@@ -3201,7 +5442,7 @@ function printInstallHelp(target) {
|
|
|
3201
5442
|
function printSetupHelp() {
|
|
3202
5443
|
console.log('Usage:');
|
|
3203
5444
|
console.log(
|
|
3204
|
-
' rsx setup [--pm <pnpm|npm|yarn|bun>] [--next] [--force] [--local] [--dry-run]',
|
|
5445
|
+
' rsx setup [--pm <pnpm|npm|yarn|bun>] [--next] [--verify] [--force] [--local] [--dry-run]',
|
|
3205
5446
|
);
|
|
3206
5447
|
console.log('');
|
|
3207
5448
|
console.log('What it does:');
|
|
@@ -3210,12 +5451,17 @@ function printSetupHelp() {
|
|
|
3210
5451
|
);
|
|
3211
5452
|
console.log(' - Installs runtime packages');
|
|
3212
5453
|
console.log(' - Installs compiler tooling packages');
|
|
3213
|
-
console.log(' -
|
|
5454
|
+
console.log(' - Writes rsx.build config plus build/typecheck scripts');
|
|
5455
|
+
console.log(' - Creates rsx.config.json with CLI defaults you can override');
|
|
3214
5456
|
console.log(' - Applies framework-specific transform/build integration');
|
|
5457
|
+
console.log(' - Does not install the VS Code extension automatically');
|
|
3215
5458
|
console.log('');
|
|
3216
5459
|
console.log('Options:');
|
|
3217
5460
|
console.log(' --pm Explicit package manager');
|
|
3218
5461
|
console.log(' --next Install prerelease versions (dist-tag next)');
|
|
5462
|
+
console.log(
|
|
5463
|
+
' --verify Validate the resulting setup output before returning',
|
|
5464
|
+
);
|
|
3219
5465
|
console.log(' --force Reinstall extension if already installed');
|
|
3220
5466
|
console.log(' --local Build/install local VSIX from repo workspace');
|
|
3221
5467
|
console.log(' --dry-run Print commands without executing them');
|
|
@@ -3234,14 +5480,17 @@ function printInitHelp() {
|
|
|
3234
5480
|
console.log(
|
|
3235
5481
|
' - Detects project context and wires RS-X bootstrap in entry file',
|
|
3236
5482
|
);
|
|
3237
|
-
console.log(' -
|
|
5483
|
+
console.log(' - Creates rsx.config.json with CLI defaults you can override');
|
|
5484
|
+
console.log(' - Does not install the VS Code extension automatically');
|
|
3238
5485
|
console.log('');
|
|
3239
5486
|
console.log('Options:');
|
|
3240
5487
|
console.log(' --pm Explicit package manager');
|
|
3241
5488
|
console.log(' --entry Explicit application entry file');
|
|
3242
5489
|
console.log(' --next Install prerelease versions (dist-tag next)');
|
|
3243
5490
|
console.log(' --skip-install Skip npm/pnpm/yarn/bun package installation');
|
|
3244
|
-
console.log(
|
|
5491
|
+
console.log(
|
|
5492
|
+
' --skip-vscode Accepted for compatibility; VS Code is not auto-installed',
|
|
5493
|
+
);
|
|
3245
5494
|
console.log(' --force Reinstall extension if already installed');
|
|
3246
5495
|
console.log(' --local Build/install local VSIX from repo workspace');
|
|
3247
5496
|
console.log(' --dry-run Print commands without executing them');
|
|
@@ -3250,21 +5499,30 @@ function printInitHelp() {
|
|
|
3250
5499
|
function printProjectHelp() {
|
|
3251
5500
|
console.log('Usage:');
|
|
3252
5501
|
console.log(
|
|
3253
|
-
' rsx project [angular|vuejs|react|nextjs|nodejs] [--name <project-name>] [--pm <pnpm|npm|yarn|bun>] [--next] [--template <angular|vuejs|react|nextjs|nodejs>] [--tarballs-dir <path>] [--skip-install] [--skip-vscode] [--dry-run]',
|
|
5502
|
+
' rsx project [angular|vuejs|react|nextjs|nodejs] [--name <project-name>] [--pm <pnpm|npm|yarn|bun>] [--next] [--template <angular|vuejs|react|nextjs|nodejs>] [--tarballs-dir <path>] [--skip-install] [--skip-vscode] [--verify] [--dry-run]',
|
|
3254
5503
|
);
|
|
3255
5504
|
console.log('');
|
|
3256
5505
|
console.log('What it does:');
|
|
3257
5506
|
console.log(' - Creates a new project folder');
|
|
3258
5507
|
console.log(' - Supports templates: angular, vuejs, react, nextjs, nodejs');
|
|
5508
|
+
console.log(
|
|
5509
|
+
' - Angular generates the RS-X virtual-table demo starter on top of the latest Angular scaffold',
|
|
5510
|
+
);
|
|
3259
5511
|
console.log(' - Scaffolds framework app and wires RS-X bootstrap/setup');
|
|
3260
5512
|
console.log(' - Writes package.json with RS-X dependencies');
|
|
5513
|
+
console.log(
|
|
5514
|
+
' - Creates rsx.config.json with starter CLI defaults you can override',
|
|
5515
|
+
);
|
|
3261
5516
|
console.log(
|
|
3262
5517
|
' - Adds tsconfig + TypeScript plugin config for editor support',
|
|
3263
5518
|
);
|
|
3264
|
-
console.log(
|
|
5519
|
+
console.log(
|
|
5520
|
+
' - For Angular template: uses the latest Angular CLI scaffold, then applies the RS-X demo starter',
|
|
5521
|
+
);
|
|
3265
5522
|
console.log(' - For React/Next templates: also installs @rs-x/react');
|
|
3266
5523
|
console.log(' - For Vue template: also installs @rs-x/vue');
|
|
3267
5524
|
console.log(' - Installs dependencies (unless --skip-install)');
|
|
5525
|
+
console.log(' - Verifies the generated starter before reporting success');
|
|
3268
5526
|
console.log('');
|
|
3269
5527
|
console.log('Options:');
|
|
3270
5528
|
console.log(' --name Project folder/package name');
|
|
@@ -3278,7 +5536,12 @@ function printProjectHelp() {
|
|
|
3278
5536
|
);
|
|
3279
5537
|
console.log(' (or set RSX_TARBALLS_DIR env var)');
|
|
3280
5538
|
console.log(' --skip-install Skip dependency installation');
|
|
3281
|
-
console.log(
|
|
5539
|
+
console.log(
|
|
5540
|
+
' --skip-vscode Accepted for compatibility; VS Code is not auto-installed',
|
|
5541
|
+
);
|
|
5542
|
+
console.log(
|
|
5543
|
+
' --verify Re-run starter structure checks explicitly after generation',
|
|
5544
|
+
);
|
|
3282
5545
|
console.log(' --dry-run Print actions without writing files');
|
|
3283
5546
|
}
|
|
3284
5547
|
|
|
@@ -3338,6 +5601,7 @@ function printTypecheckHelp() {
|
|
|
3338
5601
|
function printVersionHelp() {
|
|
3339
5602
|
console.log('Usage:');
|
|
3340
5603
|
console.log(' rsx version');
|
|
5604
|
+
console.log(' rsx v');
|
|
3341
5605
|
console.log(' rsx -v');
|
|
3342
5606
|
console.log(' rsx -version');
|
|
3343
5607
|
console.log(' rsx --version');
|
|
@@ -3349,6 +5613,7 @@ function isHelpToken(value) {
|
|
|
3349
5613
|
|
|
3350
5614
|
function isVersionToken(value) {
|
|
3351
5615
|
return (
|
|
5616
|
+
value === 'v' ||
|
|
3352
5617
|
value === '-v' ||
|
|
3353
5618
|
value === '--version' ||
|
|
3354
5619
|
value === '-version' ||
|
|
@@ -3409,6 +5674,7 @@ function printHelpFor(command, target) {
|
|
|
3409
5674
|
}
|
|
3410
5675
|
|
|
3411
5676
|
if (
|
|
5677
|
+
command === 'v' ||
|
|
3412
5678
|
command === 'version' ||
|
|
3413
5679
|
command === '-v' ||
|
|
3414
5680
|
command === '--version' ||
|
|
@@ -3493,9 +5759,16 @@ function main() {
|
|
|
3493
5759
|
}
|
|
3494
5760
|
|
|
3495
5761
|
if (command === 'install' && target === 'compiler') {
|
|
3496
|
-
const
|
|
3497
|
-
const
|
|
3498
|
-
|
|
5762
|
+
const projectRoot = process.cwd();
|
|
5763
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
5764
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
5765
|
+
installCompilerPackages(
|
|
5766
|
+
pm,
|
|
5767
|
+
Boolean(flags['dry-run']),
|
|
5768
|
+
tag,
|
|
5769
|
+
projectRoot,
|
|
5770
|
+
flags,
|
|
5771
|
+
);
|
|
3499
5772
|
return;
|
|
3500
5773
|
}
|
|
3501
5774
|
|