@rs-x/cli 2.0.0-next.0 → 2.0.0-next.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +10 -4
  2. package/bin/rsx.cjs +679 -153
  3. package/package.json +14 -1
  4. package/{rs-x-vscode-extension-2.0.0-next.0.vsix → rs-x-vscode-extension-2.0.0-next.11.vsix} +0 -0
  5. package/templates/angular-demo/README.md +115 -0
  6. package/templates/angular-demo/src/app/app.component.css +97 -0
  7. package/templates/angular-demo/src/app/app.component.html +58 -0
  8. package/templates/angular-demo/src/app/app.component.ts +52 -0
  9. package/templates/angular-demo/src/app/virtual-table/row-data.ts +35 -0
  10. package/templates/angular-demo/src/app/virtual-table/row-model.ts +45 -0
  11. package/templates/angular-demo/src/app/virtual-table/virtual-table-data.service.ts +136 -0
  12. package/templates/angular-demo/src/app/virtual-table/virtual-table-model.ts +224 -0
  13. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.css +174 -0
  14. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.html +50 -0
  15. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.ts +83 -0
  16. package/templates/angular-demo/src/index.html +11 -0
  17. package/templates/angular-demo/src/main.ts +16 -0
  18. package/templates/angular-demo/src/styles.css +261 -0
  19. package/templates/react-demo/README.md +113 -0
  20. package/templates/react-demo/index.html +12 -0
  21. package/templates/react-demo/src/app/app.tsx +87 -0
  22. package/templates/react-demo/src/app/hooks/use-virtual-table-controller.ts +24 -0
  23. package/templates/react-demo/src/app/hooks/use-virtual-table-viewport.ts +39 -0
  24. package/templates/react-demo/src/app/virtual-table/row-data.ts +35 -0
  25. package/templates/react-demo/src/app/virtual-table/row-model.ts +45 -0
  26. package/templates/react-demo/src/app/virtual-table/virtual-table-controller.ts +247 -0
  27. package/templates/react-demo/src/app/virtual-table/virtual-table-data.service.ts +126 -0
  28. package/templates/react-demo/src/app/virtual-table/virtual-table-row.tsx +38 -0
  29. package/templates/react-demo/src/app/virtual-table/virtual-table-shell.tsx +83 -0
  30. package/templates/react-demo/src/main.tsx +23 -0
  31. package/templates/react-demo/src/rsx-bootstrap.ts +18 -0
  32. package/templates/react-demo/src/styles.css +422 -0
  33. package/templates/react-demo/tsconfig.json +17 -0
package/bin/rsx.cjs CHANGED
@@ -5,8 +5,28 @@ 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 = '0.2.0';
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
+ );
10
30
  const RUNTIME_PACKAGES = [
11
31
  '@rs-x/core',
12
32
  '@rs-x/state-manager',
@@ -178,15 +198,38 @@ function detectPackageManager(explicitPm) {
178
198
  return 'npm';
179
199
  }
180
200
 
201
+ function applyTagToPackages(packages, tag) {
202
+ return packages.map((pkg) => {
203
+ const lastAt = pkg.lastIndexOf('@');
204
+ const slashIndex = pkg.indexOf('/');
205
+ const hasVersion = pkg.startsWith('@') ? lastAt > slashIndex : lastAt > 0;
206
+ if (hasVersion) {
207
+ return pkg;
208
+ }
209
+ return `${pkg}@${tag}`;
210
+ });
211
+ }
212
+
213
+ function resolveInstallTag(flags) {
214
+ return parseBooleanFlag(flags.next, false) ? 'next' : undefined;
215
+ }
216
+
181
217
  function installPackages(pm, packages, options = {}) {
182
- const { dev = false, dryRun = false, label = 'packages' } = options;
218
+ const { dev = false, dryRun = false, label = 'packages', tag } = options;
219
+ const resolvedPackages = tag ? applyTagToPackages(packages, tag) : packages;
183
220
  const argsByPm = {
184
- pnpm: dev ? ['add', '-D', ...packages] : ['add', ...packages],
221
+ pnpm: dev
222
+ ? ['add', '-D', ...resolvedPackages]
223
+ : ['add', ...resolvedPackages],
185
224
  npm: dev
186
- ? ['install', '--save-dev', ...packages]
187
- : ['install', '--save', ...packages],
188
- yarn: dev ? ['add', '--dev', ...packages] : ['add', ...packages],
189
- bun: dev ? ['add', '--dev', ...packages] : ['add', ...packages],
225
+ ? ['install', '--save-dev', ...resolvedPackages]
226
+ : ['install', '--save', ...resolvedPackages],
227
+ yarn: dev
228
+ ? ['add', '--dev', ...resolvedPackages]
229
+ : ['add', ...resolvedPackages],
230
+ bun: dev
231
+ ? ['add', '--dev', ...resolvedPackages]
232
+ : ['add', ...resolvedPackages],
190
233
  };
191
234
 
192
235
  const installArgs = argsByPm[pm];
@@ -195,23 +238,26 @@ function installPackages(pm, packages, options = {}) {
195
238
  process.exit(1);
196
239
  }
197
240
 
198
- logInfo(`Installing ${label} with ${pm}...`);
241
+ const tagInfo = tag ? ` (tag: ${tag})` : '';
242
+ logInfo(`Installing ${label} with ${pm}${tagInfo}...`);
199
243
  run(pm, installArgs, { dryRun });
200
244
  logOk(`Installed ${label}.`);
201
245
  }
202
246
 
203
- function installRuntimePackages(pm, dryRun) {
247
+ function installRuntimePackages(pm, dryRun, tag) {
204
248
  installPackages(pm, RUNTIME_PACKAGES, {
205
249
  dev: false,
206
250
  dryRun,
251
+ tag,
207
252
  label: 'runtime RS-X packages',
208
253
  });
209
254
  }
210
255
 
211
- function installCompilerPackages(pm, dryRun) {
256
+ function installCompilerPackages(pm, dryRun, tag) {
212
257
  installPackages(pm, COMPILER_PACKAGES, {
213
258
  dev: true,
214
259
  dryRun,
260
+ tag,
215
261
  label: 'compiler tooling',
216
262
  });
217
263
  }
@@ -236,14 +282,50 @@ function installVsCodeExtension(flags) {
236
282
  return;
237
283
  }
238
284
 
239
- const args = ['--install-extension', VS_CODE_EXTENSION_ID];
285
+ installBundledVsix(dryRun, force);
286
+ }
287
+
288
+ function resolveBundledVsix() {
289
+ const packageRoot = path.resolve(__dirname, '..');
290
+ const candidates = fs
291
+ .readdirSync(packageRoot)
292
+ .filter((name) => /^rs-x-vscode-extension-.*\.vsix$/u.test(name))
293
+ .map((name) => path.join(packageRoot, name));
294
+
295
+ if (candidates.length === 0) {
296
+ return null;
297
+ }
298
+
299
+ const latest = candidates
300
+ .map((fullPath) => ({
301
+ fullPath,
302
+ mtimeMs: fs.statSync(fullPath).mtimeMs,
303
+ }))
304
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
305
+
306
+ return latest?.fullPath ?? null;
307
+ }
308
+
309
+ function installBundledVsix(dryRun, force) {
310
+ const bundledVsix = resolveBundledVsix();
311
+ if (!bundledVsix) {
312
+ logWarn(
313
+ 'No bundled VSIX found in @rs-x/cli. Skipping VS Code extension install.',
314
+ );
315
+ logInfo(
316
+ 'If you are developing in the rs-x repo, use `rsx install vscode --local` instead.',
317
+ );
318
+ return;
319
+ }
320
+
321
+ const args = ['--install-extension', bundledVsix];
240
322
  if (force) {
241
323
  args.push('--force');
242
324
  }
243
325
 
244
- logInfo(`Installing ${VS_CODE_EXTENSION_ID} from VS Code marketplace...`);
326
+ logInfo(`Installing bundled VSIX from ${bundledVsix}...`);
245
327
  run('code', args, { dryRun });
246
- logOk('VS Code extension installed.');
328
+ logOk('VS Code extension installed from bundled VSIX.');
247
329
  }
248
330
 
249
331
  function installLocalVsix(dryRun, force) {
@@ -559,6 +641,85 @@ function writeFileWithDryRun(filePath, content, dryRun) {
559
641
  fs.writeFileSync(filePath, content, 'utf8');
560
642
  }
561
643
 
644
+ function copyPathWithDryRun(sourcePath, targetPath, dryRun) {
645
+ if (dryRun) {
646
+ logInfo(`[dry-run] copy ${sourcePath} -> ${targetPath}`);
647
+ return;
648
+ }
649
+
650
+ const stat = fs.statSync(sourcePath);
651
+ if (stat.isDirectory()) {
652
+ fs.mkdirSync(targetPath, { recursive: true });
653
+ for (const entry of fs.readdirSync(sourcePath, { withFileTypes: true })) {
654
+ copyPathWithDryRun(
655
+ path.join(sourcePath, entry.name),
656
+ path.join(targetPath, entry.name),
657
+ false,
658
+ );
659
+ }
660
+ return;
661
+ }
662
+
663
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
664
+ fs.copyFileSync(sourcePath, targetPath);
665
+ }
666
+
667
+ function removeFileOrDirectoryWithDryRun(targetPath, dryRun) {
668
+ if (!fs.existsSync(targetPath)) {
669
+ return;
670
+ }
671
+
672
+ if (dryRun) {
673
+ logInfo(`[dry-run] remove ${targetPath}`);
674
+ return;
675
+ }
676
+
677
+ fs.rmSync(targetPath, { recursive: true, force: true });
678
+ }
679
+
680
+ function resolveAngularProjectTsConfig(projectRoot) {
681
+ const appTsConfigPath = path.join(projectRoot, 'tsconfig.app.json');
682
+ if (fs.existsSync(appTsConfigPath)) {
683
+ return appTsConfigPath;
684
+ }
685
+
686
+ return path.join(projectRoot, 'tsconfig.json');
687
+ }
688
+
689
+ function upsertTypescriptPluginInTsConfig(configPath, dryRun) {
690
+ if (!fs.existsSync(configPath)) {
691
+ logWarn(`TypeScript config not found: ${configPath}`);
692
+ return;
693
+ }
694
+
695
+ const tsConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
696
+ const compilerOptions = tsConfig.compilerOptions ?? {};
697
+ const plugins = Array.isArray(compilerOptions.plugins)
698
+ ? compilerOptions.plugins
699
+ : [];
700
+
701
+ if (
702
+ !plugins.some(
703
+ (plugin) =>
704
+ plugin &&
705
+ typeof plugin === 'object' &&
706
+ plugin.name === '@rs-x/typescript-plugin',
707
+ )
708
+ ) {
709
+ plugins.push({ name: '@rs-x/typescript-plugin' });
710
+ }
711
+
712
+ compilerOptions.plugins = plugins;
713
+ tsConfig.compilerOptions = compilerOptions;
714
+
715
+ if (dryRun) {
716
+ logInfo(`[dry-run] patch ${configPath}`);
717
+ return;
718
+ }
719
+
720
+ fs.writeFileSync(configPath, `${JSON.stringify(tsConfig, null, 2)}\n`, 'utf8');
721
+ }
722
+
562
723
  function toFileDependencySpec(fromDir, targetPath) {
563
724
  const relative = path.relative(fromDir, targetPath).replace(/\\/gu, '/');
564
725
  const normalized = relative.startsWith('.') ? relative : `./${relative}`;
@@ -608,14 +769,17 @@ function resolveProjectRsxSpecs(
608
769
  options = {},
609
770
  ) {
610
771
  const includeAngularPackage = Boolean(options.includeAngularPackage);
772
+ const includeReactPackage = Boolean(options.includeReactPackage);
773
+ const versionSpec = options.tag ? options.tag : RSX_PACKAGE_VERSION;
611
774
  const defaults = {
612
- '@rs-x/core': RSX_PACKAGE_VERSION,
613
- '@rs-x/state-manager': RSX_PACKAGE_VERSION,
614
- '@rs-x/expression-parser': RSX_PACKAGE_VERSION,
615
- '@rs-x/compiler': RSX_PACKAGE_VERSION,
616
- '@rs-x/typescript-plugin': RSX_PACKAGE_VERSION,
617
- ...(includeAngularPackage ? { '@rs-x/angular': RSX_PACKAGE_VERSION } : {}),
618
- '@rs-x/cli': null,
775
+ '@rs-x/core': versionSpec,
776
+ '@rs-x/state-manager': versionSpec,
777
+ '@rs-x/expression-parser': versionSpec,
778
+ '@rs-x/compiler': versionSpec,
779
+ '@rs-x/typescript-plugin': versionSpec,
780
+ ...(includeAngularPackage ? { '@rs-x/angular': versionSpec } : {}),
781
+ ...(includeReactPackage ? { '@rs-x/react': versionSpec } : {}),
782
+ '@rs-x/cli': versionSpec,
619
783
  };
620
784
 
621
785
  const tarballSlugs = {
@@ -625,6 +789,7 @@ function resolveProjectRsxSpecs(
625
789
  '@rs-x/compiler': 'rs-x-compiler',
626
790
  '@rs-x/typescript-plugin': 'rs-x-typescript-plugin',
627
791
  ...(includeAngularPackage ? { '@rs-x/angular': 'rs-x-angular' } : {}),
792
+ ...(includeReactPackage ? { '@rs-x/react': 'rs-x-react' } : {}),
628
793
  '@rs-x/cli': 'rs-x-cli',
629
794
  };
630
795
 
@@ -647,6 +812,11 @@ function resolveProjectRsxSpecs(
647
812
  'rs-x-angular': path.join(tarballsDir, 'rs-x-angular'),
648
813
  }
649
814
  : {}),
815
+ ...(includeReactPackage
816
+ ? {
817
+ 'rs-x-react': path.join(tarballsDir, 'rs-x-react'),
818
+ }
819
+ : {}),
650
820
  'rs-x-cli': path.join(tarballsDir, 'rs-x-cli'),
651
821
  };
652
822
 
@@ -690,6 +860,11 @@ function resolveProjectRsxSpecs(
690
860
  ),
691
861
  }
692
862
  : {}),
863
+ ...(includeReactPackage
864
+ ? {
865
+ '@rs-x/react': path.join(workspaceRoot, 'rs-x-react'),
866
+ }
867
+ : {}),
693
868
  '@rs-x/cli': path.join(workspaceRoot, 'rs-x-cli'),
694
869
  };
695
870
 
@@ -919,6 +1094,7 @@ async function runProject(flags) {
919
1094
  const dryRun = Boolean(flags['dry-run']);
920
1095
  const skipInstall = Boolean(flags['skip-install']);
921
1096
  const pm = detectPackageManager(flags.pm);
1097
+ const tag = resolveInstallTag(flags);
922
1098
  let projectName = typeof flags.name === 'string' ? flags.name.trim() : '';
923
1099
 
924
1100
  if (!projectName) {
@@ -946,6 +1122,7 @@ async function runProject(flags) {
946
1122
  projectRoot,
947
1123
  workspaceRoot,
948
1124
  tarballsDir,
1125
+ { tag },
949
1126
  );
950
1127
  if (fs.existsSync(projectRoot) && fs.readdirSync(projectRoot).length > 0) {
951
1128
  logError(`Target directory is not empty: ${projectRoot}`);
@@ -1153,6 +1330,315 @@ function scaffoldProjectTemplate(template, projectName, pm, flags) {
1153
1330
  process.exit(1);
1154
1331
  }
1155
1332
 
1333
+ function applyAngularDemoStarter(projectRoot, projectName, pm, flags) {
1334
+ const dryRun = Boolean(flags['dry-run']);
1335
+ const tag = resolveInstallTag(flags);
1336
+ const tarballsDir =
1337
+ typeof flags['tarballs-dir'] === 'string'
1338
+ ? path.resolve(process.cwd(), flags['tarballs-dir'])
1339
+ : typeof process.env.RSX_TARBALLS_DIR === 'string' &&
1340
+ process.env.RSX_TARBALLS_DIR.trim().length > 0
1341
+ ? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
1342
+ : null;
1343
+ const workspaceRoot = findRepoRoot(projectRoot);
1344
+ const rsxSpecs = resolveProjectRsxSpecs(
1345
+ projectRoot,
1346
+ workspaceRoot,
1347
+ tarballsDir,
1348
+ { tag, includeAngularPackage: true },
1349
+ );
1350
+
1351
+ const templateFiles = ['README.md', 'src'];
1352
+ for (const entry of templateFiles) {
1353
+ copyPathWithDryRun(
1354
+ path.join(ANGULAR_DEMO_TEMPLATE_DIR, entry),
1355
+ path.join(projectRoot, entry),
1356
+ dryRun,
1357
+ );
1358
+ }
1359
+
1360
+ const staleAngularFiles = [
1361
+ path.join(projectRoot, 'src/app/app.ts'),
1362
+ path.join(projectRoot, 'src/app/app.spec.ts'),
1363
+ path.join(projectRoot, 'src/app/app.html'),
1364
+ path.join(projectRoot, 'src/app/app.css'),
1365
+ path.join(projectRoot, 'src/app/app.routes.ts'),
1366
+ path.join(projectRoot, 'src/app/app.config.ts'),
1367
+ ];
1368
+ for (const stalePath of staleAngularFiles) {
1369
+ removeFileOrDirectoryWithDryRun(stalePath, dryRun);
1370
+ }
1371
+
1372
+ const readmePath = path.join(projectRoot, 'README.md');
1373
+ if (fs.existsSync(readmePath)) {
1374
+ const readmeSource = fs.readFileSync(readmePath, 'utf8');
1375
+ const nextReadme = readmeSource.replace(
1376
+ /^#\s+rsx-angular-example/mu,
1377
+ `# ${projectName}`,
1378
+ );
1379
+ if (dryRun) {
1380
+ logInfo(`[dry-run] patch ${readmePath}`);
1381
+ } else {
1382
+ fs.writeFileSync(readmePath, nextReadme, 'utf8');
1383
+ }
1384
+ }
1385
+
1386
+ const packageJsonPath = path.join(projectRoot, 'package.json');
1387
+ if (!fs.existsSync(packageJsonPath)) {
1388
+ logError(
1389
+ `package.json not found in generated Angular app: ${packageJsonPath}`,
1390
+ );
1391
+ process.exit(1);
1392
+ }
1393
+
1394
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
1395
+ const angularTsConfigPath = resolveAngularProjectTsConfig(projectRoot);
1396
+ const angularTsConfigRelative = path
1397
+ .relative(projectRoot, angularTsConfigPath)
1398
+ .replace(/\\/gu, '/');
1399
+ packageJson.name = projectName;
1400
+ packageJson.private = true;
1401
+ packageJson.version = '0.1.0';
1402
+ packageJson.scripts = {
1403
+ 'build:rsx': `rsx build --project ${angularTsConfigRelative} --no-emit --prod`,
1404
+ 'typecheck:rsx': `rsx typecheck --project ${angularTsConfigRelative}`,
1405
+ prebuild: 'npm run build:rsx',
1406
+ start: 'npm run build:rsx && ng serve',
1407
+ build: 'ng build',
1408
+ };
1409
+ packageJson.rsx = {
1410
+ build: {
1411
+ preparse: true,
1412
+ preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
1413
+ compiled: true,
1414
+ compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
1415
+ registrationFile: 'src/rsx-generated/rsx-aot-registration.generated.ts',
1416
+ compiledResolvedEvaluator: false,
1417
+ },
1418
+ };
1419
+ packageJson.dependencies = {
1420
+ ...(packageJson.dependencies ?? {}),
1421
+ '@rs-x/angular': rsxSpecs['@rs-x/angular'],
1422
+ '@rs-x/core': rsxSpecs['@rs-x/core'],
1423
+ '@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
1424
+ '@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
1425
+ };
1426
+ packageJson.devDependencies = {
1427
+ ...(packageJson.devDependencies ?? {}),
1428
+ '@rs-x/cli': rsxSpecs['@rs-x/cli'],
1429
+ '@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
1430
+ '@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
1431
+ };
1432
+
1433
+ if (dryRun) {
1434
+ logInfo(`[dry-run] patch ${packageJsonPath}`);
1435
+ } else {
1436
+ fs.writeFileSync(
1437
+ packageJsonPath,
1438
+ `${JSON.stringify(packageJson, null, 2)}\n`,
1439
+ 'utf8',
1440
+ );
1441
+ }
1442
+
1443
+ const angularJsonPath = path.join(projectRoot, 'angular.json');
1444
+ if (!fs.existsSync(angularJsonPath)) {
1445
+ logError(
1446
+ `angular.json not found in generated Angular app: ${angularJsonPath}`,
1447
+ );
1448
+ process.exit(1);
1449
+ }
1450
+
1451
+ const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8'));
1452
+ const projects = angularJson.projects ?? {};
1453
+ const [angularProjectName] = Object.keys(projects);
1454
+ if (!angularProjectName) {
1455
+ logError('Generated angular.json does not define any projects.');
1456
+ process.exit(1);
1457
+ }
1458
+
1459
+ const angularProject = projects[angularProjectName];
1460
+ const architect = angularProject.architect ?? angularProject.targets;
1461
+ const build = architect?.build;
1462
+ if (!build) {
1463
+ logError('Generated Angular project is missing a build target.');
1464
+ process.exit(1);
1465
+ }
1466
+
1467
+ const buildOptions = build.options ?? {};
1468
+ const styles = Array.isArray(buildOptions.styles) ? buildOptions.styles : [];
1469
+ if (!styles.includes('src/styles.css')) {
1470
+ styles.push('src/styles.css');
1471
+ }
1472
+ buildOptions.styles = styles;
1473
+ buildOptions.preserveSymlinks = true;
1474
+
1475
+ const registrationFile =
1476
+ 'src/rsx-generated/rsx-aot-registration.generated.ts';
1477
+ let polyfills = buildOptions.polyfills;
1478
+ if (typeof polyfills === 'string') {
1479
+ polyfills = [polyfills];
1480
+ } else if (!Array.isArray(polyfills)) {
1481
+ polyfills = [];
1482
+ }
1483
+ if (!polyfills.includes(registrationFile)) {
1484
+ polyfills.push(registrationFile);
1485
+ }
1486
+ buildOptions.polyfills = polyfills;
1487
+ build.options = buildOptions;
1488
+
1489
+ if (build.configurations?.production?.budgets) {
1490
+ delete build.configurations.production.budgets;
1491
+ }
1492
+
1493
+ if (dryRun) {
1494
+ logInfo(`[dry-run] patch ${angularJsonPath}`);
1495
+ } else {
1496
+ fs.writeFileSync(
1497
+ angularJsonPath,
1498
+ `${JSON.stringify(angularJson, null, 2)}\n`,
1499
+ 'utf8',
1500
+ );
1501
+ }
1502
+
1503
+ if (!Boolean(flags['skip-install'])) {
1504
+ logInfo(`Refreshing ${pm} dependencies for the RS-X Angular starter...`);
1505
+ run(pm, ['install'], { dryRun });
1506
+ logOk('Angular starter dependencies are up to date.');
1507
+ }
1508
+ }
1509
+
1510
+ function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
1511
+ const dryRun = Boolean(flags['dry-run']);
1512
+ const tag = resolveInstallTag(flags);
1513
+ const tarballsDir =
1514
+ typeof flags['tarballs-dir'] === 'string'
1515
+ ? path.resolve(process.cwd(), flags['tarballs-dir'])
1516
+ : typeof process.env.RSX_TARBALLS_DIR === 'string' &&
1517
+ process.env.RSX_TARBALLS_DIR.trim().length > 0
1518
+ ? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
1519
+ : null;
1520
+ const workspaceRoot = findRepoRoot(projectRoot);
1521
+ const rsxSpecs = resolveProjectRsxSpecs(
1522
+ projectRoot,
1523
+ workspaceRoot,
1524
+ tarballsDir,
1525
+ { tag, includeReactPackage: true },
1526
+ );
1527
+
1528
+ const templateFiles = [
1529
+ 'README.md',
1530
+ 'index.html',
1531
+ 'src',
1532
+ 'tsconfig.json',
1533
+ 'vite.config.ts',
1534
+ ];
1535
+ for (const entry of templateFiles) {
1536
+ copyPathWithDryRun(
1537
+ path.join(REACT_DEMO_TEMPLATE_DIR, entry),
1538
+ path.join(projectRoot, entry),
1539
+ dryRun,
1540
+ );
1541
+ }
1542
+
1543
+ const staleReactFiles = [
1544
+ path.join(projectRoot, 'src/App.tsx'),
1545
+ path.join(projectRoot, 'src/App.css'),
1546
+ path.join(projectRoot, 'src/index.css'),
1547
+ path.join(projectRoot, 'src/vite-env.d.ts'),
1548
+ path.join(projectRoot, 'src/assets'),
1549
+ path.join(projectRoot, 'public'),
1550
+ path.join(projectRoot, 'eslint.config.js'),
1551
+ path.join(projectRoot, 'eslint.config.ts'),
1552
+ path.join(projectRoot, 'tsconfig.app.json'),
1553
+ path.join(projectRoot, 'tsconfig.node.json'),
1554
+ ];
1555
+ for (const stalePath of staleReactFiles) {
1556
+ removeFileOrDirectoryWithDryRun(stalePath, dryRun);
1557
+ }
1558
+
1559
+ const readmePath = path.join(projectRoot, 'README.md');
1560
+ if (fs.existsSync(readmePath)) {
1561
+ const readmeSource = fs.readFileSync(readmePath, 'utf8');
1562
+ const nextReadme = readmeSource.replace(
1563
+ /^#\s+rsx-react-example/mu,
1564
+ `# ${projectName}`,
1565
+ );
1566
+ if (dryRun) {
1567
+ logInfo(`[dry-run] patch ${readmePath}`);
1568
+ } else {
1569
+ fs.writeFileSync(readmePath, nextReadme, 'utf8');
1570
+ }
1571
+ }
1572
+
1573
+ const packageJsonPath = path.join(projectRoot, 'package.json');
1574
+ if (!fs.existsSync(packageJsonPath)) {
1575
+ logError(`package.json not found in generated React app: ${packageJsonPath}`);
1576
+ process.exit(1);
1577
+ }
1578
+
1579
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
1580
+ packageJson.name = projectName;
1581
+ packageJson.private = true;
1582
+ packageJson.version = '0.1.0';
1583
+ packageJson.type = 'module';
1584
+ packageJson.scripts = {
1585
+ 'build:rsx': 'rsx build --project tsconfig.json --no-emit --prod',
1586
+ dev: 'npm run build:rsx && vite',
1587
+ build: 'npm run build:rsx && vite build',
1588
+ preview: 'vite preview',
1589
+ };
1590
+ packageJson.rsx = {
1591
+ build: {
1592
+ preparse: true,
1593
+ preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
1594
+ compiled: true,
1595
+ compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
1596
+ compiledResolvedEvaluator: false,
1597
+ },
1598
+ };
1599
+ packageJson.dependencies = {
1600
+ react: packageJson.dependencies?.react ?? '^19.2.4',
1601
+ 'react-dom': packageJson.dependencies?.['react-dom'] ?? '^19.2.4',
1602
+ '@rs-x/core': rsxSpecs['@rs-x/core'],
1603
+ '@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
1604
+ '@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
1605
+ '@rs-x/react': rsxSpecs['@rs-x/react'],
1606
+ };
1607
+ packageJson.devDependencies = {
1608
+ typescript: packageJson.devDependencies?.typescript ?? '^5.9.3',
1609
+ vite: packageJson.devDependencies?.vite ?? '^7.3.1',
1610
+ '@vitejs/plugin-react':
1611
+ packageJson.devDependencies?.['@vitejs/plugin-react'] ?? '^5.1.4',
1612
+ '@types/react': packageJson.devDependencies?.['@types/react'] ?? '^19.2.2',
1613
+ '@types/react-dom':
1614
+ packageJson.devDependencies?.['@types/react-dom'] ?? '^19.2.2',
1615
+ '@rs-x/cli': rsxSpecs['@rs-x/cli'],
1616
+ '@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
1617
+ '@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
1618
+ };
1619
+
1620
+ if (dryRun) {
1621
+ logInfo(`[dry-run] patch ${packageJsonPath}`);
1622
+ } else {
1623
+ fs.writeFileSync(
1624
+ packageJsonPath,
1625
+ `${JSON.stringify(packageJson, null, 2)}\n`,
1626
+ 'utf8',
1627
+ );
1628
+ }
1629
+
1630
+ const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
1631
+ if (fs.existsSync(tsConfigPath)) {
1632
+ upsertTypescriptPluginInTsConfig(tsConfigPath, dryRun);
1633
+ }
1634
+
1635
+ if (!Boolean(flags['skip-install'])) {
1636
+ logInfo(`Refreshing ${pm} dependencies for the RS-X React starter...`);
1637
+ run(pm, ['install'], { dryRun });
1638
+ logOk('React starter dependencies are up to date.');
1639
+ }
1640
+ }
1641
+
1156
1642
  async function runProjectWithTemplate(template, flags) {
1157
1643
  const normalizedTemplate = normalizeProjectTemplate(template);
1158
1644
  if (!normalizedTemplate) {
@@ -1184,14 +1670,11 @@ async function runProjectWithTemplate(template, flags) {
1184
1670
 
1185
1671
  withWorkingDirectory(projectRoot, () => {
1186
1672
  if (normalizedTemplate === 'angular') {
1187
- runSetupAngular(flags);
1673
+ applyAngularDemoStarter(projectRoot, projectName, pm, flags);
1188
1674
  return;
1189
1675
  }
1190
1676
  if (normalizedTemplate === 'react') {
1191
- runSetupReact({
1192
- ...flags,
1193
- entry: flags.entry ?? 'src/main.tsx',
1194
- });
1677
+ applyReactDemoStarter(projectRoot, projectName, pm, flags);
1195
1678
  return;
1196
1679
  }
1197
1680
  if (normalizedTemplate === 'nextjs') {
@@ -1700,11 +2183,12 @@ function runInit(flags) {
1700
2183
  const skipVscode = Boolean(flags['skip-vscode']);
1701
2184
  const skipInstall = Boolean(flags['skip-install']);
1702
2185
  const pm = detectPackageManager(flags.pm);
2186
+ const tag = resolveInstallTag(flags);
1703
2187
  const projectRoot = process.cwd();
1704
2188
 
1705
2189
  if (!skipInstall) {
1706
- installRuntimePackages(pm, dryRun);
1707
- installCompilerPackages(pm, dryRun);
2190
+ installRuntimePackages(pm, dryRun, tag);
2191
+ installCompilerPackages(pm, dryRun, tag);
1708
2192
  } else {
1709
2193
  logInfo('Skipping package installation (--skip-install).');
1710
2194
  }
@@ -1758,11 +2242,112 @@ function runInit(flags) {
1758
2242
  }
1759
2243
  }
1760
2244
 
1761
- if (!skipVscode) {
1762
- installVsCodeExtension(flags);
2245
+ logOk('RS-X init completed.');
2246
+ }
2247
+
2248
+ function upsertRsxBuildConfigInPackageJson(projectRoot, dryRun) {
2249
+ const packageJsonPath = path.join(projectRoot, 'package.json');
2250
+ if (!fs.existsSync(packageJsonPath)) {
2251
+ return false;
1763
2252
  }
1764
2253
 
1765
- logOk('RS-X init completed.');
2254
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
2255
+ const currentRsx = packageJson.rsx ?? {};
2256
+ const currentBuild = currentRsx.build ?? {};
2257
+ const nextBuild = {
2258
+ preparse: true,
2259
+ preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
2260
+ compiled: true,
2261
+ compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
2262
+ registrationFile: 'src/rsx-generated/rsx-aot-registration.generated.ts',
2263
+ compiledResolvedEvaluator: false,
2264
+ ...currentBuild,
2265
+ };
2266
+
2267
+ const nextPackageJson = {
2268
+ ...packageJson,
2269
+ rsx: {
2270
+ ...currentRsx,
2271
+ build: nextBuild,
2272
+ },
2273
+ };
2274
+
2275
+ if (dryRun) {
2276
+ logInfo(`[dry-run] patch ${packageJsonPath} (rsx.build)`);
2277
+ return true;
2278
+ }
2279
+
2280
+ fs.writeFileSync(
2281
+ packageJsonPath,
2282
+ `${JSON.stringify(nextPackageJson, null, 2)}\n`,
2283
+ 'utf8',
2284
+ );
2285
+ logOk(`Patched ${packageJsonPath} (rsx.build)`);
2286
+ return true;
2287
+ }
2288
+
2289
+ function ensureAngularProvidersInEntry(entryFile, dryRun) {
2290
+ if (!fs.existsSync(entryFile)) {
2291
+ return false;
2292
+ }
2293
+
2294
+ const original = fs.readFileSync(entryFile, 'utf8');
2295
+ if (original.includes('providexRsx')) {
2296
+ logInfo(`Angular entry already includes providexRsx: ${entryFile}`);
2297
+ return true;
2298
+ }
2299
+
2300
+ if (!original.includes('bootstrapApplication(')) {
2301
+ logWarn(
2302
+ `Could not automatically patch Angular providers in ${entryFile}. Expected bootstrapApplication(...).`,
2303
+ );
2304
+ logInfo(
2305
+ "Manual setup: import { providexRsx } from '@rs-x/angular' and add providers: [...providexRsx()] to bootstrapApplication(...).",
2306
+ );
2307
+ return false;
2308
+ }
2309
+
2310
+ const sourceWithImport = injectImport(
2311
+ original,
2312
+ "import { providexRsx } from '@rs-x/angular';",
2313
+ );
2314
+
2315
+ let updated = sourceWithImport;
2316
+ if (
2317
+ /bootstrapApplication\([\s\S]*?,\s*\{[\s\S]*?providers\s*:/mu.test(updated)
2318
+ ) {
2319
+ updated = updated.replace(
2320
+ /providers\s*:\s*\[/mu,
2321
+ 'providers: [...providexRsx(), ',
2322
+ );
2323
+ } else if (/bootstrapApplication\([\s\S]*?,\s*\{/mu.test(updated)) {
2324
+ updated = updated.replace(
2325
+ /bootstrapApplication\(([\s\S]*?),\s*\{/mu,
2326
+ 'bootstrapApplication($1, {\n providers: [...providexRsx()],',
2327
+ );
2328
+ } else {
2329
+ updated = updated.replace(
2330
+ /bootstrapApplication\(([\s\S]*?)\)\s*(?:\.catch\([\s\S]*?\))?\s*;/mu,
2331
+ 'bootstrapApplication($1, {\n providers: [...providexRsx()],\n}).catch((error) => {\n console.error(error);\n});',
2332
+ );
2333
+ }
2334
+
2335
+ if (updated === sourceWithImport) {
2336
+ logWarn(`Could not automatically inject providexRsx into ${entryFile}.`);
2337
+ logInfo(
2338
+ "Manual setup: import { providexRsx } from '@rs-x/angular' and add providers: [...providexRsx()] to bootstrapApplication(...).",
2339
+ );
2340
+ return false;
2341
+ }
2342
+
2343
+ if (dryRun) {
2344
+ logInfo(`[dry-run] patch ${entryFile} (providexRsx)`);
2345
+ return true;
2346
+ }
2347
+
2348
+ fs.writeFileSync(entryFile, updated, 'utf8');
2349
+ logOk(`Patched ${entryFile} to include providexRsx.`);
2350
+ return true;
1766
2351
  }
1767
2352
 
1768
2353
  function upsertScriptInPackageJson(
@@ -2124,95 +2709,15 @@ ${patchBlock}
2124
2709
  logOk(`Patched ${nextConfigJs} with RS-X webpack loader.`);
2125
2710
  }
2126
2711
 
2127
- function wireRsxAngularWebpack(projectRoot, dryRun) {
2128
- const angularJsonPath = path.join(projectRoot, 'angular.json');
2129
- if (!fs.existsSync(angularJsonPath)) {
2130
- logWarn('angular.json not found. Skipping Angular build integration.');
2131
- return;
2132
- }
2133
-
2134
- createRsxWebpackLoaderFile(projectRoot, dryRun);
2135
-
2136
- const webpackConfigPath = path.join(projectRoot, 'rsx-angular-webpack.cjs');
2137
- const webpackConfigSource = `const path = require('node:path');
2138
-
2139
- module.exports = {
2140
- module: {
2141
- rules: [
2142
- {
2143
- test: /\\.[jt]sx?$/u,
2144
- exclude: /node_modules/u,
2145
- use: [
2146
- {
2147
- loader: path.resolve(__dirname, './rsx-webpack-loader.cjs'),
2148
- },
2149
- ],
2150
- },
2151
- ],
2152
- },
2153
- };
2154
- `;
2155
-
2156
- if (dryRun) {
2157
- logInfo(`[dry-run] create ${webpackConfigPath}`);
2158
- } else {
2159
- fs.writeFileSync(webpackConfigPath, webpackConfigSource, 'utf8');
2160
- logOk(`Created ${webpackConfigPath}`);
2161
- }
2162
-
2163
- const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8'));
2164
- const projects = angularJson.projects ?? {};
2165
- const projectNames = Object.keys(projects);
2166
- if (projectNames.length === 0) {
2167
- logWarn('No Angular projects found in angular.json.');
2168
- return;
2169
- }
2170
-
2171
- const patchPath = 'rsx-angular-webpack.cjs';
2172
- for (const projectName of projectNames) {
2173
- const project = projects[projectName];
2174
- const architect = project.architect ?? project.targets;
2175
- if (!architect?.build) {
2176
- continue;
2177
- }
2178
-
2179
- const build = architect.build;
2180
- if (build.builder !== '@angular-builders/custom-webpack:browser') {
2181
- build.builder = '@angular-builders/custom-webpack:browser';
2182
- }
2183
- build.options = build.options ?? {};
2184
- build.options.customWebpackConfig = build.options.customWebpackConfig ?? {};
2185
- build.options.customWebpackConfig.path = patchPath;
2186
-
2187
- if (architect.serve) {
2188
- const serve = architect.serve;
2189
- if (serve.builder !== '@angular-builders/custom-webpack:dev-server') {
2190
- serve.builder = '@angular-builders/custom-webpack:dev-server';
2191
- }
2192
- serve.options = serve.options ?? {};
2193
- serve.options.buildTarget =
2194
- serve.options.buildTarget ?? `${projectName}:build`;
2195
- serve.options.browserTarget =
2196
- serve.options.browserTarget ?? `${projectName}:build`;
2197
- }
2198
- }
2199
-
2200
- if (dryRun) {
2201
- logInfo(`[dry-run] patch ${angularJsonPath}`);
2202
- } else {
2203
- fs.writeFileSync(
2204
- angularJsonPath,
2205
- `${JSON.stringify(angularJson, null, 2)}\n`,
2206
- 'utf8',
2207
- );
2208
- logOk(`Patched ${angularJsonPath} for RS-X Angular webpack integration.`);
2209
- }
2210
- }
2211
-
2212
2712
  function runSetupReact(flags) {
2213
2713
  const dryRun = Boolean(flags['dry-run']);
2214
2714
  const pm = detectPackageManager(flags.pm);
2715
+ const tag = resolveInstallTag(flags);
2215
2716
  const projectRoot = process.cwd();
2717
+ const angularTsConfigPath = resolveAngularProjectTsConfig(projectRoot);
2718
+ const angularTsConfigRelative = path
2719
+ .relative(projectRoot, angularTsConfigPath)
2720
+ .replace(/\\/gu, '/');
2216
2721
  const packageJsonPath = path.join(projectRoot, 'package.json');
2217
2722
  if (!fs.existsSync(packageJsonPath)) {
2218
2723
  logError(`package.json not found in ${projectRoot}`);
@@ -2238,21 +2743,20 @@ function runSetupReact(flags) {
2238
2743
  installPackages(pm, ['@rs-x/react'], {
2239
2744
  dev: false,
2240
2745
  dryRun,
2746
+ tag,
2241
2747
  label: 'RS-X React bindings',
2242
2748
  });
2243
2749
  } else {
2244
2750
  logInfo('Skipping RS-X React bindings install (--skip-install).');
2245
2751
  }
2246
2752
  wireRsxVitePlugin(projectRoot, dryRun);
2247
- if (!Boolean(flags['skip-vscode'])) {
2248
- installVsCodeExtension(flags);
2249
- }
2250
2753
  logOk('RS-X React setup completed.');
2251
2754
  }
2252
2755
 
2253
2756
  function runSetupNext(flags) {
2254
2757
  const dryRun = Boolean(flags['dry-run']);
2255
2758
  const pm = detectPackageManager(flags.pm);
2759
+ const tag = resolveInstallTag(flags);
2256
2760
  runInit({
2257
2761
  ...flags,
2258
2762
  'skip-vscode': true,
@@ -2261,21 +2765,20 @@ function runSetupNext(flags) {
2261
2765
  installPackages(pm, ['@rs-x/react'], {
2262
2766
  dev: false,
2263
2767
  dryRun,
2768
+ tag,
2264
2769
  label: 'RS-X React bindings',
2265
2770
  });
2266
2771
  } else {
2267
2772
  logInfo('Skipping RS-X React bindings install (--skip-install).');
2268
2773
  }
2269
2774
  wireRsxNextWebpack(process.cwd(), dryRun);
2270
- if (!Boolean(flags['skip-vscode'])) {
2271
- installVsCodeExtension(flags);
2272
- }
2273
2775
  logOk('RS-X Next.js setup completed.');
2274
2776
  }
2275
2777
 
2276
2778
  function runSetupVue(flags) {
2277
2779
  const dryRun = Boolean(flags['dry-run']);
2278
2780
  const pm = detectPackageManager(flags.pm);
2781
+ const tag = resolveInstallTag(flags);
2279
2782
  runInit({
2280
2783
  ...flags,
2281
2784
  'skip-vscode': true,
@@ -2284,67 +2787,79 @@ function runSetupVue(flags) {
2284
2787
  installPackages(pm, ['@rs-x/vue'], {
2285
2788
  dev: false,
2286
2789
  dryRun,
2790
+ tag,
2287
2791
  label: 'RS-X Vue bindings',
2288
2792
  });
2289
2793
  } else {
2290
2794
  logInfo('Skipping RS-X Vue bindings install (--skip-install).');
2291
2795
  }
2292
2796
  wireRsxVitePlugin(process.cwd(), dryRun);
2293
- if (!Boolean(flags['skip-vscode'])) {
2294
- installVsCodeExtension(flags);
2295
- }
2296
2797
  logOk('RS-X Vue setup completed.');
2297
2798
  }
2298
2799
 
2299
2800
  function runSetupAngular(flags) {
2300
2801
  const dryRun = Boolean(flags['dry-run']);
2301
2802
  const pm = detectPackageManager(flags.pm);
2302
-
2303
- runInit({
2304
- ...flags,
2305
- 'skip-vscode': true,
2306
- });
2803
+ const tag = resolveInstallTag(flags);
2804
+ const projectRoot = process.cwd();
2307
2805
 
2308
2806
  if (!Boolean(flags['skip-install'])) {
2807
+ installRuntimePackages(pm, dryRun, tag);
2808
+ installCompilerPackages(pm, dryRun, tag);
2309
2809
  installPackages(pm, ['@rs-x/angular'], {
2310
2810
  dev: false,
2311
2811
  dryRun,
2812
+ tag,
2312
2813
  label: 'RS-X Angular bindings',
2313
2814
  });
2314
- installPackages(pm, ['@angular-builders/custom-webpack'], {
2315
- dev: true,
2316
- dryRun,
2317
- label: 'Angular custom webpack builder',
2318
- });
2319
2815
  } else {
2816
+ logInfo('Skipping package installation (--skip-install).');
2817
+ }
2818
+
2819
+ const entryFile = resolveEntryFile(projectRoot, 'angular', flags.entry);
2820
+ if (entryFile) {
2821
+ logInfo(`Using Angular entry file: ${entryFile}`);
2822
+ ensureAngularProvidersInEntry(entryFile, dryRun);
2823
+ } else {
2824
+ logWarn('Could not detect an Angular entry file automatically.');
2320
2825
  logInfo(
2321
- 'Skipping Angular custom webpack builder install (--skip-install).',
2826
+ 'Manual setup: add providexRsx() to bootstrapApplication(...) in your main entry file.',
2322
2827
  );
2323
2828
  }
2324
2829
 
2325
- wireRsxAngularWebpack(process.cwd(), dryRun);
2830
+ upsertRsxBuildConfigInPackageJson(projectRoot, dryRun);
2831
+
2326
2832
  upsertScriptInPackageJson(
2327
- process.cwd(),
2833
+ projectRoot,
2328
2834
  'build:rsx',
2329
- 'rsx build --project tsconfig.json',
2835
+ `rsx build --project ${angularTsConfigRelative} --no-emit --prod`,
2330
2836
  dryRun,
2331
2837
  );
2332
2838
  upsertScriptInPackageJson(
2333
- process.cwd(),
2839
+ projectRoot,
2334
2840
  'typecheck:rsx',
2335
- 'rsx typecheck --project tsconfig.json',
2841
+ `rsx typecheck --project ${angularTsConfigRelative}`,
2336
2842
  dryRun,
2337
2843
  );
2338
2844
 
2339
- if (!Boolean(flags['skip-vscode'])) {
2340
- installVsCodeExtension(flags);
2341
- }
2845
+ const rsxRegistrationFile = path.join(
2846
+ projectRoot,
2847
+ 'src/rsx-generated/rsx-aot-registration.generated.ts',
2848
+ );
2849
+ ensureAngularPolyfillsContainsFile({
2850
+ projectRoot,
2851
+ configPath: angularTsConfigPath,
2852
+ filePath: rsxRegistrationFile,
2853
+ dryRun,
2854
+ });
2855
+
2342
2856
  logOk('RS-X Angular setup completed.');
2343
2857
  }
2344
2858
 
2345
2859
  function runSetupAuto(flags) {
2346
2860
  const projectRoot = process.cwd();
2347
2861
  const context = detectProjectContext(projectRoot);
2862
+ const tag = resolveInstallTag(flags);
2348
2863
 
2349
2864
  if (context === 'react') {
2350
2865
  logInfo('Auto-detected framework: react');
@@ -2372,9 +2887,8 @@ function runSetupAuto(flags) {
2372
2887
 
2373
2888
  logInfo('No framework-specific setup detected; running generic setup.');
2374
2889
  const pm = detectPackageManager(flags.pm);
2375
- installRuntimePackages(pm, Boolean(flags['dry-run']));
2376
- installCompilerPackages(pm, Boolean(flags['dry-run']));
2377
- installVsCodeExtension(flags);
2890
+ installRuntimePackages(pm, Boolean(flags['dry-run']), tag);
2891
+ installCompilerPackages(pm, Boolean(flags['dry-run']), tag);
2378
2892
  }
2379
2893
 
2380
2894
  function resolveProjectModule(projectRoot, moduleName) {
@@ -3142,24 +3656,27 @@ function printInstallHelp(target) {
3142
3656
  if (target === 'compiler') {
3143
3657
  console.log('Usage:');
3144
3658
  console.log(
3145
- ' rsx install compiler [--pm <pnpm|npm|yarn|bun>] [--dry-run]',
3659
+ ' rsx install compiler [--pm <pnpm|npm|yarn|bun>] [--next] [--dry-run]',
3146
3660
  );
3147
3661
  console.log('');
3148
3662
  console.log('Options:');
3149
3663
  console.log(' --pm Explicit package manager');
3664
+ console.log(' --next Install prerelease versions (dist-tag next)');
3150
3665
  console.log(' --dry-run Print commands without executing them');
3151
3666
  return;
3152
3667
  }
3153
3668
 
3154
3669
  console.log('Usage:');
3155
3670
  console.log(' rsx install vscode [--force] [--local] [--dry-run]');
3156
- console.log(' rsx install compiler [--pm <pnpm|npm|yarn|bun>] [--dry-run]');
3671
+ console.log(
3672
+ ' rsx install compiler [--pm <pnpm|npm|yarn|bun>] [--next] [--dry-run]',
3673
+ );
3157
3674
  }
3158
3675
 
3159
3676
  function printSetupHelp() {
3160
3677
  console.log('Usage:');
3161
3678
  console.log(
3162
- ' rsx setup [--pm <pnpm|npm|yarn|bun>] [--force] [--local] [--dry-run]',
3679
+ ' rsx setup [--pm <pnpm|npm|yarn|bun>] [--next] [--force] [--local] [--dry-run]',
3163
3680
  );
3164
3681
  console.log('');
3165
3682
  console.log('What it does:');
@@ -3173,6 +3690,7 @@ function printSetupHelp() {
3173
3690
  console.log('');
3174
3691
  console.log('Options:');
3175
3692
  console.log(' --pm Explicit package manager');
3693
+ console.log(' --next Install prerelease versions (dist-tag next)');
3176
3694
  console.log(' --force Reinstall extension if already installed');
3177
3695
  console.log(' --local Build/install local VSIX from repo workspace');
3178
3696
  console.log(' --dry-run Print commands without executing them');
@@ -3181,7 +3699,7 @@ function printSetupHelp() {
3181
3699
  function printInitHelp() {
3182
3700
  console.log('Usage:');
3183
3701
  console.log(
3184
- ' rsx init [--pm <pnpm|npm|yarn|bun>] [--entry <path>] [--skip-install] [--skip-vscode] [--force] [--local] [--dry-run]',
3702
+ ' rsx init [--pm <pnpm|npm|yarn|bun>] [--entry <path>] [--next] [--skip-install] [--skip-vscode] [--force] [--local] [--dry-run]',
3185
3703
  );
3186
3704
  console.log('');
3187
3705
  console.log('What it does:');
@@ -3196,6 +3714,7 @@ function printInitHelp() {
3196
3714
  console.log('Options:');
3197
3715
  console.log(' --pm Explicit package manager');
3198
3716
  console.log(' --entry Explicit application entry file');
3717
+ console.log(' --next Install prerelease versions (dist-tag next)');
3199
3718
  console.log(' --skip-install Skip npm/pnpm/yarn/bun package installation');
3200
3719
  console.log(' --skip-vscode Skip VS Code extension installation');
3201
3720
  console.log(' --force Reinstall extension if already installed');
@@ -3206,18 +3725,23 @@ function printInitHelp() {
3206
3725
  function printProjectHelp() {
3207
3726
  console.log('Usage:');
3208
3727
  console.log(
3209
- ' rsx project [angular|vuejs|react|nextjs|nodejs] [--name <project-name>] [--pm <pnpm|npm|yarn|bun>] [--template <angular|vuejs|react|nextjs|nodejs>] [--tarballs-dir <path>] [--skip-install] [--skip-vscode] [--dry-run]',
3728
+ ' 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]',
3210
3729
  );
3211
3730
  console.log('');
3212
3731
  console.log('What it does:');
3213
3732
  console.log(' - Creates a new project folder');
3214
3733
  console.log(' - Supports templates: angular, vuejs, react, nextjs, nodejs');
3734
+ console.log(
3735
+ ' - Angular generates the RS-X virtual-table demo starter on top of the latest Angular scaffold',
3736
+ );
3215
3737
  console.log(' - Scaffolds framework app and wires RS-X bootstrap/setup');
3216
3738
  console.log(' - Writes package.json with RS-X dependencies');
3217
3739
  console.log(
3218
3740
  ' - Adds tsconfig + TypeScript plugin config for editor support',
3219
3741
  );
3220
- console.log(' - For Angular template: also installs @rs-x/angular');
3742
+ console.log(
3743
+ ' - For Angular template: uses the latest Angular CLI scaffold, then applies the RS-X demo starter',
3744
+ );
3221
3745
  console.log(' - For React/Next templates: also installs @rs-x/react');
3222
3746
  console.log(' - For Vue template: also installs @rs-x/vue');
3223
3747
  console.log(' - Installs dependencies (unless --skip-install)');
@@ -3228,6 +3752,7 @@ function printProjectHelp() {
3228
3752
  ' --template Project template (if omitted, asks interactively)',
3229
3753
  );
3230
3754
  console.log(' --pm Explicit package manager');
3755
+ console.log(' --next Install prerelease versions (dist-tag next)');
3231
3756
  console.log(
3232
3757
  ' --tarballs-dir Directory containing local RS-X package tarballs (*.tgz)',
3233
3758
  );
@@ -3449,7 +3974,8 @@ function main() {
3449
3974
 
3450
3975
  if (command === 'install' && target === 'compiler') {
3451
3976
  const pm = detectPackageManager(flags.pm);
3452
- installCompilerPackages(pm, Boolean(flags['dry-run']));
3977
+ const tag = resolveInstallTag(flags);
3978
+ installCompilerPackages(pm, Boolean(flags['dry-run']), tag);
3453
3979
  return;
3454
3980
  }
3455
3981