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

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/bin/rsx.cjs CHANGED
@@ -5,8 +5,22 @@ 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
+ );
10
24
  const RUNTIME_PACKAGES = [
11
25
  '@rs-x/core',
12
26
  '@rs-x/state-manager',
@@ -178,15 +192,38 @@ function detectPackageManager(explicitPm) {
178
192
  return 'npm';
179
193
  }
180
194
 
195
+ function applyTagToPackages(packages, tag) {
196
+ return packages.map((pkg) => {
197
+ const lastAt = pkg.lastIndexOf('@');
198
+ const slashIndex = pkg.indexOf('/');
199
+ const hasVersion = pkg.startsWith('@') ? lastAt > slashIndex : lastAt > 0;
200
+ if (hasVersion) {
201
+ return pkg;
202
+ }
203
+ return `${pkg}@${tag}`;
204
+ });
205
+ }
206
+
207
+ function resolveInstallTag(flags) {
208
+ return parseBooleanFlag(flags.next, false) ? 'next' : undefined;
209
+ }
210
+
181
211
  function installPackages(pm, packages, options = {}) {
182
- const { dev = false, dryRun = false, label = 'packages' } = options;
212
+ const { dev = false, dryRun = false, label = 'packages', tag } = options;
213
+ const resolvedPackages = tag ? applyTagToPackages(packages, tag) : packages;
183
214
  const argsByPm = {
184
- pnpm: dev ? ['add', '-D', ...packages] : ['add', ...packages],
215
+ pnpm: dev
216
+ ? ['add', '-D', ...resolvedPackages]
217
+ : ['add', ...resolvedPackages],
185
218
  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],
219
+ ? ['install', '--save-dev', ...resolvedPackages]
220
+ : ['install', '--save', ...resolvedPackages],
221
+ yarn: dev
222
+ ? ['add', '--dev', ...resolvedPackages]
223
+ : ['add', ...resolvedPackages],
224
+ bun: dev
225
+ ? ['add', '--dev', ...resolvedPackages]
226
+ : ['add', ...resolvedPackages],
190
227
  };
191
228
 
192
229
  const installArgs = argsByPm[pm];
@@ -195,23 +232,26 @@ function installPackages(pm, packages, options = {}) {
195
232
  process.exit(1);
196
233
  }
197
234
 
198
- logInfo(`Installing ${label} with ${pm}...`);
235
+ const tagInfo = tag ? ` (tag: ${tag})` : '';
236
+ logInfo(`Installing ${label} with ${pm}${tagInfo}...`);
199
237
  run(pm, installArgs, { dryRun });
200
238
  logOk(`Installed ${label}.`);
201
239
  }
202
240
 
203
- function installRuntimePackages(pm, dryRun) {
241
+ function installRuntimePackages(pm, dryRun, tag) {
204
242
  installPackages(pm, RUNTIME_PACKAGES, {
205
243
  dev: false,
206
244
  dryRun,
245
+ tag,
207
246
  label: 'runtime RS-X packages',
208
247
  });
209
248
  }
210
249
 
211
- function installCompilerPackages(pm, dryRun) {
250
+ function installCompilerPackages(pm, dryRun, tag) {
212
251
  installPackages(pm, COMPILER_PACKAGES, {
213
252
  dev: true,
214
253
  dryRun,
254
+ tag,
215
255
  label: 'compiler tooling',
216
256
  });
217
257
  }
@@ -236,14 +276,50 @@ function installVsCodeExtension(flags) {
236
276
  return;
237
277
  }
238
278
 
239
- const args = ['--install-extension', VS_CODE_EXTENSION_ID];
279
+ installBundledVsix(dryRun, force);
280
+ }
281
+
282
+ function resolveBundledVsix() {
283
+ const packageRoot = path.resolve(__dirname, '..');
284
+ const candidates = fs
285
+ .readdirSync(packageRoot)
286
+ .filter((name) => /^rs-x-vscode-extension-.*\.vsix$/u.test(name))
287
+ .map((name) => path.join(packageRoot, name));
288
+
289
+ if (candidates.length === 0) {
290
+ return null;
291
+ }
292
+
293
+ const latest = candidates
294
+ .map((fullPath) => ({
295
+ fullPath,
296
+ mtimeMs: fs.statSync(fullPath).mtimeMs,
297
+ }))
298
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
299
+
300
+ return latest?.fullPath ?? null;
301
+ }
302
+
303
+ function installBundledVsix(dryRun, force) {
304
+ const bundledVsix = resolveBundledVsix();
305
+ if (!bundledVsix) {
306
+ logWarn(
307
+ 'No bundled VSIX found in @rs-x/cli. Skipping VS Code extension install.',
308
+ );
309
+ logInfo(
310
+ 'If you are developing in the rs-x repo, use `rsx install vscode --local` instead.',
311
+ );
312
+ return;
313
+ }
314
+
315
+ const args = ['--install-extension', bundledVsix];
240
316
  if (force) {
241
317
  args.push('--force');
242
318
  }
243
319
 
244
- logInfo(`Installing ${VS_CODE_EXTENSION_ID} from VS Code marketplace...`);
320
+ logInfo(`Installing bundled VSIX from ${bundledVsix}...`);
245
321
  run('code', args, { dryRun });
246
- logOk('VS Code extension installed.');
322
+ logOk('VS Code extension installed from bundled VSIX.');
247
323
  }
248
324
 
249
325
  function installLocalVsix(dryRun, force) {
@@ -559,6 +635,51 @@ function writeFileWithDryRun(filePath, content, dryRun) {
559
635
  fs.writeFileSync(filePath, content, 'utf8');
560
636
  }
561
637
 
638
+ function copyPathWithDryRun(sourcePath, targetPath, dryRun) {
639
+ if (dryRun) {
640
+ logInfo(`[dry-run] copy ${sourcePath} -> ${targetPath}`);
641
+ return;
642
+ }
643
+
644
+ const stat = fs.statSync(sourcePath);
645
+ if (stat.isDirectory()) {
646
+ fs.mkdirSync(targetPath, { recursive: true });
647
+ for (const entry of fs.readdirSync(sourcePath, { withFileTypes: true })) {
648
+ copyPathWithDryRun(
649
+ path.join(sourcePath, entry.name),
650
+ path.join(targetPath, entry.name),
651
+ false,
652
+ );
653
+ }
654
+ return;
655
+ }
656
+
657
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
658
+ fs.copyFileSync(sourcePath, targetPath);
659
+ }
660
+
661
+ function removeFileOrDirectoryWithDryRun(targetPath, dryRun) {
662
+ if (!fs.existsSync(targetPath)) {
663
+ return;
664
+ }
665
+
666
+ if (dryRun) {
667
+ logInfo(`[dry-run] remove ${targetPath}`);
668
+ return;
669
+ }
670
+
671
+ fs.rmSync(targetPath, { recursive: true, force: true });
672
+ }
673
+
674
+ function resolveAngularProjectTsConfig(projectRoot) {
675
+ const appTsConfigPath = path.join(projectRoot, 'tsconfig.app.json');
676
+ if (fs.existsSync(appTsConfigPath)) {
677
+ return appTsConfigPath;
678
+ }
679
+
680
+ return path.join(projectRoot, 'tsconfig.json');
681
+ }
682
+
562
683
  function toFileDependencySpec(fromDir, targetPath) {
563
684
  const relative = path.relative(fromDir, targetPath).replace(/\\/gu, '/');
564
685
  const normalized = relative.startsWith('.') ? relative : `./${relative}`;
@@ -608,14 +729,15 @@ function resolveProjectRsxSpecs(
608
729
  options = {},
609
730
  ) {
610
731
  const includeAngularPackage = Boolean(options.includeAngularPackage);
732
+ const versionSpec = options.tag ? options.tag : RSX_PACKAGE_VERSION;
611
733
  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,
734
+ '@rs-x/core': versionSpec,
735
+ '@rs-x/state-manager': versionSpec,
736
+ '@rs-x/expression-parser': versionSpec,
737
+ '@rs-x/compiler': versionSpec,
738
+ '@rs-x/typescript-plugin': versionSpec,
739
+ ...(includeAngularPackage ? { '@rs-x/angular': versionSpec } : {}),
740
+ '@rs-x/cli': versionSpec,
619
741
  };
620
742
 
621
743
  const tarballSlugs = {
@@ -919,6 +1041,7 @@ async function runProject(flags) {
919
1041
  const dryRun = Boolean(flags['dry-run']);
920
1042
  const skipInstall = Boolean(flags['skip-install']);
921
1043
  const pm = detectPackageManager(flags.pm);
1044
+ const tag = resolveInstallTag(flags);
922
1045
  let projectName = typeof flags.name === 'string' ? flags.name.trim() : '';
923
1046
 
924
1047
  if (!projectName) {
@@ -946,6 +1069,7 @@ async function runProject(flags) {
946
1069
  projectRoot,
947
1070
  workspaceRoot,
948
1071
  tarballsDir,
1072
+ { tag },
949
1073
  );
950
1074
  if (fs.existsSync(projectRoot) && fs.readdirSync(projectRoot).length > 0) {
951
1075
  logError(`Target directory is not empty: ${projectRoot}`);
@@ -1153,6 +1277,183 @@ function scaffoldProjectTemplate(template, projectName, pm, flags) {
1153
1277
  process.exit(1);
1154
1278
  }
1155
1279
 
1280
+ function applyAngularDemoStarter(projectRoot, projectName, pm, flags) {
1281
+ const dryRun = Boolean(flags['dry-run']);
1282
+ const tag = resolveInstallTag(flags);
1283
+ const tarballsDir =
1284
+ typeof flags['tarballs-dir'] === 'string'
1285
+ ? path.resolve(process.cwd(), flags['tarballs-dir'])
1286
+ : typeof process.env.RSX_TARBALLS_DIR === 'string' &&
1287
+ process.env.RSX_TARBALLS_DIR.trim().length > 0
1288
+ ? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
1289
+ : null;
1290
+ const workspaceRoot = findRepoRoot(projectRoot);
1291
+ const rsxSpecs = resolveProjectRsxSpecs(
1292
+ projectRoot,
1293
+ workspaceRoot,
1294
+ tarballsDir,
1295
+ { tag, includeAngularPackage: true },
1296
+ );
1297
+
1298
+ const templateFiles = ['README.md', 'src'];
1299
+ for (const entry of templateFiles) {
1300
+ copyPathWithDryRun(
1301
+ path.join(ANGULAR_DEMO_TEMPLATE_DIR, entry),
1302
+ path.join(projectRoot, entry),
1303
+ dryRun,
1304
+ );
1305
+ }
1306
+
1307
+ const staleAngularFiles = [
1308
+ path.join(projectRoot, 'src/app/app.ts'),
1309
+ path.join(projectRoot, 'src/app/app.spec.ts'),
1310
+ path.join(projectRoot, 'src/app/app.html'),
1311
+ path.join(projectRoot, 'src/app/app.css'),
1312
+ path.join(projectRoot, 'src/app/app.routes.ts'),
1313
+ path.join(projectRoot, 'src/app/app.config.ts'),
1314
+ ];
1315
+ for (const stalePath of staleAngularFiles) {
1316
+ removeFileOrDirectoryWithDryRun(stalePath, dryRun);
1317
+ }
1318
+
1319
+ const readmePath = path.join(projectRoot, 'README.md');
1320
+ if (fs.existsSync(readmePath)) {
1321
+ const readmeSource = fs.readFileSync(readmePath, 'utf8');
1322
+ const nextReadme = readmeSource.replace(
1323
+ /^#\s+rsx-angular-example/mu,
1324
+ `# ${projectName}`,
1325
+ );
1326
+ if (dryRun) {
1327
+ logInfo(`[dry-run] patch ${readmePath}`);
1328
+ } else {
1329
+ fs.writeFileSync(readmePath, nextReadme, 'utf8');
1330
+ }
1331
+ }
1332
+
1333
+ const packageJsonPath = path.join(projectRoot, 'package.json');
1334
+ if (!fs.existsSync(packageJsonPath)) {
1335
+ logError(
1336
+ `package.json not found in generated Angular app: ${packageJsonPath}`,
1337
+ );
1338
+ process.exit(1);
1339
+ }
1340
+
1341
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
1342
+ const angularTsConfigPath = resolveAngularProjectTsConfig(projectRoot);
1343
+ const angularTsConfigRelative = path
1344
+ .relative(projectRoot, angularTsConfigPath)
1345
+ .replace(/\\/gu, '/');
1346
+ packageJson.name = projectName;
1347
+ packageJson.private = true;
1348
+ packageJson.version = '0.1.0';
1349
+ packageJson.scripts = {
1350
+ 'build:rsx': `rsx build --project ${angularTsConfigRelative} --no-emit --prod`,
1351
+ 'typecheck:rsx': `rsx typecheck --project ${angularTsConfigRelative}`,
1352
+ prebuild: 'npm run build:rsx',
1353
+ start: 'npm run build:rsx && ng serve',
1354
+ build: 'ng build',
1355
+ };
1356
+ packageJson.rsx = {
1357
+ build: {
1358
+ preparse: true,
1359
+ preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
1360
+ compiled: true,
1361
+ compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
1362
+ registrationFile: 'src/rsx-generated/rsx-aot-registration.generated.ts',
1363
+ compiledResolvedEvaluator: false,
1364
+ },
1365
+ };
1366
+ packageJson.dependencies = {
1367
+ ...(packageJson.dependencies ?? {}),
1368
+ '@rs-x/angular': rsxSpecs['@rs-x/angular'],
1369
+ '@rs-x/core': rsxSpecs['@rs-x/core'],
1370
+ '@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
1371
+ '@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
1372
+ };
1373
+ packageJson.devDependencies = {
1374
+ ...(packageJson.devDependencies ?? {}),
1375
+ '@rs-x/cli': rsxSpecs['@rs-x/cli'],
1376
+ '@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
1377
+ '@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
1378
+ };
1379
+
1380
+ if (dryRun) {
1381
+ logInfo(`[dry-run] patch ${packageJsonPath}`);
1382
+ } else {
1383
+ fs.writeFileSync(
1384
+ packageJsonPath,
1385
+ `${JSON.stringify(packageJson, null, 2)}\n`,
1386
+ 'utf8',
1387
+ );
1388
+ }
1389
+
1390
+ const angularJsonPath = path.join(projectRoot, 'angular.json');
1391
+ if (!fs.existsSync(angularJsonPath)) {
1392
+ logError(
1393
+ `angular.json not found in generated Angular app: ${angularJsonPath}`,
1394
+ );
1395
+ process.exit(1);
1396
+ }
1397
+
1398
+ const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8'));
1399
+ const projects = angularJson.projects ?? {};
1400
+ const [angularProjectName] = Object.keys(projects);
1401
+ if (!angularProjectName) {
1402
+ logError('Generated angular.json does not define any projects.');
1403
+ process.exit(1);
1404
+ }
1405
+
1406
+ const angularProject = projects[angularProjectName];
1407
+ const architect = angularProject.architect ?? angularProject.targets;
1408
+ const build = architect?.build;
1409
+ if (!build) {
1410
+ logError('Generated Angular project is missing a build target.');
1411
+ process.exit(1);
1412
+ }
1413
+
1414
+ const buildOptions = build.options ?? {};
1415
+ const styles = Array.isArray(buildOptions.styles) ? buildOptions.styles : [];
1416
+ if (!styles.includes('src/styles.css')) {
1417
+ styles.push('src/styles.css');
1418
+ }
1419
+ buildOptions.styles = styles;
1420
+ buildOptions.preserveSymlinks = true;
1421
+
1422
+ const registrationFile =
1423
+ 'src/rsx-generated/rsx-aot-registration.generated.ts';
1424
+ let polyfills = buildOptions.polyfills;
1425
+ if (typeof polyfills === 'string') {
1426
+ polyfills = [polyfills];
1427
+ } else if (!Array.isArray(polyfills)) {
1428
+ polyfills = [];
1429
+ }
1430
+ if (!polyfills.includes(registrationFile)) {
1431
+ polyfills.push(registrationFile);
1432
+ }
1433
+ buildOptions.polyfills = polyfills;
1434
+ build.options = buildOptions;
1435
+
1436
+ if (build.configurations?.production?.budgets) {
1437
+ delete build.configurations.production.budgets;
1438
+ }
1439
+
1440
+ if (dryRun) {
1441
+ logInfo(`[dry-run] patch ${angularJsonPath}`);
1442
+ } else {
1443
+ fs.writeFileSync(
1444
+ angularJsonPath,
1445
+ `${JSON.stringify(angularJson, null, 2)}\n`,
1446
+ 'utf8',
1447
+ );
1448
+ }
1449
+
1450
+ if (!Boolean(flags['skip-install'])) {
1451
+ logInfo(`Refreshing ${pm} dependencies for the RS-X Angular starter...`);
1452
+ run(pm, ['install'], { dryRun });
1453
+ logOk('Angular starter dependencies are up to date.');
1454
+ }
1455
+ }
1456
+
1156
1457
  async function runProjectWithTemplate(template, flags) {
1157
1458
  const normalizedTemplate = normalizeProjectTemplate(template);
1158
1459
  if (!normalizedTemplate) {
@@ -1184,7 +1485,7 @@ async function runProjectWithTemplate(template, flags) {
1184
1485
 
1185
1486
  withWorkingDirectory(projectRoot, () => {
1186
1487
  if (normalizedTemplate === 'angular') {
1187
- runSetupAngular(flags);
1488
+ applyAngularDemoStarter(projectRoot, projectName, pm, flags);
1188
1489
  return;
1189
1490
  }
1190
1491
  if (normalizedTemplate === 'react') {
@@ -1700,11 +2001,12 @@ function runInit(flags) {
1700
2001
  const skipVscode = Boolean(flags['skip-vscode']);
1701
2002
  const skipInstall = Boolean(flags['skip-install']);
1702
2003
  const pm = detectPackageManager(flags.pm);
2004
+ const tag = resolveInstallTag(flags);
1703
2005
  const projectRoot = process.cwd();
1704
2006
 
1705
2007
  if (!skipInstall) {
1706
- installRuntimePackages(pm, dryRun);
1707
- installCompilerPackages(pm, dryRun);
2008
+ installRuntimePackages(pm, dryRun, tag);
2009
+ installCompilerPackages(pm, dryRun, tag);
1708
2010
  } else {
1709
2011
  logInfo('Skipping package installation (--skip-install).');
1710
2012
  }
@@ -1758,11 +2060,112 @@ function runInit(flags) {
1758
2060
  }
1759
2061
  }
1760
2062
 
1761
- if (!skipVscode) {
1762
- installVsCodeExtension(flags);
2063
+ logOk('RS-X init completed.');
2064
+ }
2065
+
2066
+ function upsertRsxBuildConfigInPackageJson(projectRoot, dryRun) {
2067
+ const packageJsonPath = path.join(projectRoot, 'package.json');
2068
+ if (!fs.existsSync(packageJsonPath)) {
2069
+ return false;
1763
2070
  }
1764
2071
 
1765
- logOk('RS-X init completed.');
2072
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
2073
+ const currentRsx = packageJson.rsx ?? {};
2074
+ const currentBuild = currentRsx.build ?? {};
2075
+ const nextBuild = {
2076
+ preparse: true,
2077
+ preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
2078
+ compiled: true,
2079
+ compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
2080
+ registrationFile: 'src/rsx-generated/rsx-aot-registration.generated.ts',
2081
+ compiledResolvedEvaluator: false,
2082
+ ...currentBuild,
2083
+ };
2084
+
2085
+ const nextPackageJson = {
2086
+ ...packageJson,
2087
+ rsx: {
2088
+ ...currentRsx,
2089
+ build: nextBuild,
2090
+ },
2091
+ };
2092
+
2093
+ if (dryRun) {
2094
+ logInfo(`[dry-run] patch ${packageJsonPath} (rsx.build)`);
2095
+ return true;
2096
+ }
2097
+
2098
+ fs.writeFileSync(
2099
+ packageJsonPath,
2100
+ `${JSON.stringify(nextPackageJson, null, 2)}\n`,
2101
+ 'utf8',
2102
+ );
2103
+ logOk(`Patched ${packageJsonPath} (rsx.build)`);
2104
+ return true;
2105
+ }
2106
+
2107
+ function ensureAngularProvidersInEntry(entryFile, dryRun) {
2108
+ if (!fs.existsSync(entryFile)) {
2109
+ return false;
2110
+ }
2111
+
2112
+ const original = fs.readFileSync(entryFile, 'utf8');
2113
+ if (original.includes('providexRsx')) {
2114
+ logInfo(`Angular entry already includes providexRsx: ${entryFile}`);
2115
+ return true;
2116
+ }
2117
+
2118
+ if (!original.includes('bootstrapApplication(')) {
2119
+ logWarn(
2120
+ `Could not automatically patch Angular providers in ${entryFile}. Expected bootstrapApplication(...).`,
2121
+ );
2122
+ logInfo(
2123
+ "Manual setup: import { providexRsx } from '@rs-x/angular' and add providers: [...providexRsx()] to bootstrapApplication(...).",
2124
+ );
2125
+ return false;
2126
+ }
2127
+
2128
+ const sourceWithImport = injectImport(
2129
+ original,
2130
+ "import { providexRsx } from '@rs-x/angular';",
2131
+ );
2132
+
2133
+ let updated = sourceWithImport;
2134
+ if (
2135
+ /bootstrapApplication\([\s\S]*?,\s*\{[\s\S]*?providers\s*:/mu.test(updated)
2136
+ ) {
2137
+ updated = updated.replace(
2138
+ /providers\s*:\s*\[/mu,
2139
+ 'providers: [...providexRsx(), ',
2140
+ );
2141
+ } else if (/bootstrapApplication\([\s\S]*?,\s*\{/mu.test(updated)) {
2142
+ updated = updated.replace(
2143
+ /bootstrapApplication\(([\s\S]*?),\s*\{/mu,
2144
+ 'bootstrapApplication($1, {\n providers: [...providexRsx()],',
2145
+ );
2146
+ } else {
2147
+ updated = updated.replace(
2148
+ /bootstrapApplication\(([\s\S]*?)\)\s*(?:\.catch\([\s\S]*?\))?\s*;/mu,
2149
+ 'bootstrapApplication($1, {\n providers: [...providexRsx()],\n}).catch((error) => {\n console.error(error);\n});',
2150
+ );
2151
+ }
2152
+
2153
+ if (updated === sourceWithImport) {
2154
+ logWarn(`Could not automatically inject providexRsx into ${entryFile}.`);
2155
+ logInfo(
2156
+ "Manual setup: import { providexRsx } from '@rs-x/angular' and add providers: [...providexRsx()] to bootstrapApplication(...).",
2157
+ );
2158
+ return false;
2159
+ }
2160
+
2161
+ if (dryRun) {
2162
+ logInfo(`[dry-run] patch ${entryFile} (providexRsx)`);
2163
+ return true;
2164
+ }
2165
+
2166
+ fs.writeFileSync(entryFile, updated, 'utf8');
2167
+ logOk(`Patched ${entryFile} to include providexRsx.`);
2168
+ return true;
1766
2169
  }
1767
2170
 
1768
2171
  function upsertScriptInPackageJson(
@@ -2124,95 +2527,15 @@ ${patchBlock}
2124
2527
  logOk(`Patched ${nextConfigJs} with RS-X webpack loader.`);
2125
2528
  }
2126
2529
 
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
2530
  function runSetupReact(flags) {
2213
2531
  const dryRun = Boolean(flags['dry-run']);
2214
2532
  const pm = detectPackageManager(flags.pm);
2533
+ const tag = resolveInstallTag(flags);
2215
2534
  const projectRoot = process.cwd();
2535
+ const angularTsConfigPath = resolveAngularProjectTsConfig(projectRoot);
2536
+ const angularTsConfigRelative = path
2537
+ .relative(projectRoot, angularTsConfigPath)
2538
+ .replace(/\\/gu, '/');
2216
2539
  const packageJsonPath = path.join(projectRoot, 'package.json');
2217
2540
  if (!fs.existsSync(packageJsonPath)) {
2218
2541
  logError(`package.json not found in ${projectRoot}`);
@@ -2238,21 +2561,20 @@ function runSetupReact(flags) {
2238
2561
  installPackages(pm, ['@rs-x/react'], {
2239
2562
  dev: false,
2240
2563
  dryRun,
2564
+ tag,
2241
2565
  label: 'RS-X React bindings',
2242
2566
  });
2243
2567
  } else {
2244
2568
  logInfo('Skipping RS-X React bindings install (--skip-install).');
2245
2569
  }
2246
2570
  wireRsxVitePlugin(projectRoot, dryRun);
2247
- if (!Boolean(flags['skip-vscode'])) {
2248
- installVsCodeExtension(flags);
2249
- }
2250
2571
  logOk('RS-X React setup completed.');
2251
2572
  }
2252
2573
 
2253
2574
  function runSetupNext(flags) {
2254
2575
  const dryRun = Boolean(flags['dry-run']);
2255
2576
  const pm = detectPackageManager(flags.pm);
2577
+ const tag = resolveInstallTag(flags);
2256
2578
  runInit({
2257
2579
  ...flags,
2258
2580
  'skip-vscode': true,
@@ -2261,21 +2583,20 @@ function runSetupNext(flags) {
2261
2583
  installPackages(pm, ['@rs-x/react'], {
2262
2584
  dev: false,
2263
2585
  dryRun,
2586
+ tag,
2264
2587
  label: 'RS-X React bindings',
2265
2588
  });
2266
2589
  } else {
2267
2590
  logInfo('Skipping RS-X React bindings install (--skip-install).');
2268
2591
  }
2269
2592
  wireRsxNextWebpack(process.cwd(), dryRun);
2270
- if (!Boolean(flags['skip-vscode'])) {
2271
- installVsCodeExtension(flags);
2272
- }
2273
2593
  logOk('RS-X Next.js setup completed.');
2274
2594
  }
2275
2595
 
2276
2596
  function runSetupVue(flags) {
2277
2597
  const dryRun = Boolean(flags['dry-run']);
2278
2598
  const pm = detectPackageManager(flags.pm);
2599
+ const tag = resolveInstallTag(flags);
2279
2600
  runInit({
2280
2601
  ...flags,
2281
2602
  'skip-vscode': true,
@@ -2284,67 +2605,79 @@ function runSetupVue(flags) {
2284
2605
  installPackages(pm, ['@rs-x/vue'], {
2285
2606
  dev: false,
2286
2607
  dryRun,
2608
+ tag,
2287
2609
  label: 'RS-X Vue bindings',
2288
2610
  });
2289
2611
  } else {
2290
2612
  logInfo('Skipping RS-X Vue bindings install (--skip-install).');
2291
2613
  }
2292
2614
  wireRsxVitePlugin(process.cwd(), dryRun);
2293
- if (!Boolean(flags['skip-vscode'])) {
2294
- installVsCodeExtension(flags);
2295
- }
2296
2615
  logOk('RS-X Vue setup completed.');
2297
2616
  }
2298
2617
 
2299
2618
  function runSetupAngular(flags) {
2300
2619
  const dryRun = Boolean(flags['dry-run']);
2301
2620
  const pm = detectPackageManager(flags.pm);
2302
-
2303
- runInit({
2304
- ...flags,
2305
- 'skip-vscode': true,
2306
- });
2621
+ const tag = resolveInstallTag(flags);
2622
+ const projectRoot = process.cwd();
2307
2623
 
2308
2624
  if (!Boolean(flags['skip-install'])) {
2625
+ installRuntimePackages(pm, dryRun, tag);
2626
+ installCompilerPackages(pm, dryRun, tag);
2309
2627
  installPackages(pm, ['@rs-x/angular'], {
2310
2628
  dev: false,
2311
2629
  dryRun,
2630
+ tag,
2312
2631
  label: 'RS-X Angular bindings',
2313
2632
  });
2314
- installPackages(pm, ['@angular-builders/custom-webpack'], {
2315
- dev: true,
2316
- dryRun,
2317
- label: 'Angular custom webpack builder',
2318
- });
2319
2633
  } else {
2634
+ logInfo('Skipping package installation (--skip-install).');
2635
+ }
2636
+
2637
+ const entryFile = resolveEntryFile(projectRoot, 'angular', flags.entry);
2638
+ if (entryFile) {
2639
+ logInfo(`Using Angular entry file: ${entryFile}`);
2640
+ ensureAngularProvidersInEntry(entryFile, dryRun);
2641
+ } else {
2642
+ logWarn('Could not detect an Angular entry file automatically.');
2320
2643
  logInfo(
2321
- 'Skipping Angular custom webpack builder install (--skip-install).',
2644
+ 'Manual setup: add providexRsx() to bootstrapApplication(...) in your main entry file.',
2322
2645
  );
2323
2646
  }
2324
2647
 
2325
- wireRsxAngularWebpack(process.cwd(), dryRun);
2648
+ upsertRsxBuildConfigInPackageJson(projectRoot, dryRun);
2649
+
2326
2650
  upsertScriptInPackageJson(
2327
- process.cwd(),
2651
+ projectRoot,
2328
2652
  'build:rsx',
2329
- 'rsx build --project tsconfig.json',
2653
+ `rsx build --project ${angularTsConfigRelative} --no-emit --prod`,
2330
2654
  dryRun,
2331
2655
  );
2332
2656
  upsertScriptInPackageJson(
2333
- process.cwd(),
2657
+ projectRoot,
2334
2658
  'typecheck:rsx',
2335
- 'rsx typecheck --project tsconfig.json',
2659
+ `rsx typecheck --project ${angularTsConfigRelative}`,
2336
2660
  dryRun,
2337
2661
  );
2338
2662
 
2339
- if (!Boolean(flags['skip-vscode'])) {
2340
- installVsCodeExtension(flags);
2341
- }
2663
+ const rsxRegistrationFile = path.join(
2664
+ projectRoot,
2665
+ 'src/rsx-generated/rsx-aot-registration.generated.ts',
2666
+ );
2667
+ ensureAngularPolyfillsContainsFile({
2668
+ projectRoot,
2669
+ configPath: angularTsConfigPath,
2670
+ filePath: rsxRegistrationFile,
2671
+ dryRun,
2672
+ });
2673
+
2342
2674
  logOk('RS-X Angular setup completed.');
2343
2675
  }
2344
2676
 
2345
2677
  function runSetupAuto(flags) {
2346
2678
  const projectRoot = process.cwd();
2347
2679
  const context = detectProjectContext(projectRoot);
2680
+ const tag = resolveInstallTag(flags);
2348
2681
 
2349
2682
  if (context === 'react') {
2350
2683
  logInfo('Auto-detected framework: react');
@@ -2372,9 +2705,8 @@ function runSetupAuto(flags) {
2372
2705
 
2373
2706
  logInfo('No framework-specific setup detected; running generic setup.');
2374
2707
  const pm = detectPackageManager(flags.pm);
2375
- installRuntimePackages(pm, Boolean(flags['dry-run']));
2376
- installCompilerPackages(pm, Boolean(flags['dry-run']));
2377
- installVsCodeExtension(flags);
2708
+ installRuntimePackages(pm, Boolean(flags['dry-run']), tag);
2709
+ installCompilerPackages(pm, Boolean(flags['dry-run']), tag);
2378
2710
  }
2379
2711
 
2380
2712
  function resolveProjectModule(projectRoot, moduleName) {
@@ -3142,24 +3474,27 @@ function printInstallHelp(target) {
3142
3474
  if (target === 'compiler') {
3143
3475
  console.log('Usage:');
3144
3476
  console.log(
3145
- ' rsx install compiler [--pm <pnpm|npm|yarn|bun>] [--dry-run]',
3477
+ ' rsx install compiler [--pm <pnpm|npm|yarn|bun>] [--next] [--dry-run]',
3146
3478
  );
3147
3479
  console.log('');
3148
3480
  console.log('Options:');
3149
3481
  console.log(' --pm Explicit package manager');
3482
+ console.log(' --next Install prerelease versions (dist-tag next)');
3150
3483
  console.log(' --dry-run Print commands without executing them');
3151
3484
  return;
3152
3485
  }
3153
3486
 
3154
3487
  console.log('Usage:');
3155
3488
  console.log(' rsx install vscode [--force] [--local] [--dry-run]');
3156
- console.log(' rsx install compiler [--pm <pnpm|npm|yarn|bun>] [--dry-run]');
3489
+ console.log(
3490
+ ' rsx install compiler [--pm <pnpm|npm|yarn|bun>] [--next] [--dry-run]',
3491
+ );
3157
3492
  }
3158
3493
 
3159
3494
  function printSetupHelp() {
3160
3495
  console.log('Usage:');
3161
3496
  console.log(
3162
- ' rsx setup [--pm <pnpm|npm|yarn|bun>] [--force] [--local] [--dry-run]',
3497
+ ' rsx setup [--pm <pnpm|npm|yarn|bun>] [--next] [--force] [--local] [--dry-run]',
3163
3498
  );
3164
3499
  console.log('');
3165
3500
  console.log('What it does:');
@@ -3173,6 +3508,7 @@ function printSetupHelp() {
3173
3508
  console.log('');
3174
3509
  console.log('Options:');
3175
3510
  console.log(' --pm Explicit package manager');
3511
+ console.log(' --next Install prerelease versions (dist-tag next)');
3176
3512
  console.log(' --force Reinstall extension if already installed');
3177
3513
  console.log(' --local Build/install local VSIX from repo workspace');
3178
3514
  console.log(' --dry-run Print commands without executing them');
@@ -3181,7 +3517,7 @@ function printSetupHelp() {
3181
3517
  function printInitHelp() {
3182
3518
  console.log('Usage:');
3183
3519
  console.log(
3184
- ' rsx init [--pm <pnpm|npm|yarn|bun>] [--entry <path>] [--skip-install] [--skip-vscode] [--force] [--local] [--dry-run]',
3520
+ ' rsx init [--pm <pnpm|npm|yarn|bun>] [--entry <path>] [--next] [--skip-install] [--skip-vscode] [--force] [--local] [--dry-run]',
3185
3521
  );
3186
3522
  console.log('');
3187
3523
  console.log('What it does:');
@@ -3196,6 +3532,7 @@ function printInitHelp() {
3196
3532
  console.log('Options:');
3197
3533
  console.log(' --pm Explicit package manager');
3198
3534
  console.log(' --entry Explicit application entry file');
3535
+ console.log(' --next Install prerelease versions (dist-tag next)');
3199
3536
  console.log(' --skip-install Skip npm/pnpm/yarn/bun package installation');
3200
3537
  console.log(' --skip-vscode Skip VS Code extension installation');
3201
3538
  console.log(' --force Reinstall extension if already installed');
@@ -3206,18 +3543,23 @@ function printInitHelp() {
3206
3543
  function printProjectHelp() {
3207
3544
  console.log('Usage:');
3208
3545
  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]',
3546
+ ' 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
3547
  );
3211
3548
  console.log('');
3212
3549
  console.log('What it does:');
3213
3550
  console.log(' - Creates a new project folder');
3214
3551
  console.log(' - Supports templates: angular, vuejs, react, nextjs, nodejs');
3552
+ console.log(
3553
+ ' - Angular generates the RS-X virtual-table demo starter on top of the latest Angular scaffold',
3554
+ );
3215
3555
  console.log(' - Scaffolds framework app and wires RS-X bootstrap/setup');
3216
3556
  console.log(' - Writes package.json with RS-X dependencies');
3217
3557
  console.log(
3218
3558
  ' - Adds tsconfig + TypeScript plugin config for editor support',
3219
3559
  );
3220
- console.log(' - For Angular template: also installs @rs-x/angular');
3560
+ console.log(
3561
+ ' - For Angular template: uses the latest Angular CLI scaffold, then applies the RS-X demo starter',
3562
+ );
3221
3563
  console.log(' - For React/Next templates: also installs @rs-x/react');
3222
3564
  console.log(' - For Vue template: also installs @rs-x/vue');
3223
3565
  console.log(' - Installs dependencies (unless --skip-install)');
@@ -3228,6 +3570,7 @@ function printProjectHelp() {
3228
3570
  ' --template Project template (if omitted, asks interactively)',
3229
3571
  );
3230
3572
  console.log(' --pm Explicit package manager');
3573
+ console.log(' --next Install prerelease versions (dist-tag next)');
3231
3574
  console.log(
3232
3575
  ' --tarballs-dir Directory containing local RS-X package tarballs (*.tgz)',
3233
3576
  );
@@ -3449,7 +3792,8 @@ function main() {
3449
3792
 
3450
3793
  if (command === 'install' && target === 'compiler') {
3451
3794
  const pm = detectPackageManager(flags.pm);
3452
- installCompilerPackages(pm, Boolean(flags['dry-run']));
3795
+ const tag = resolveInstallTag(flags);
3796
+ installCompilerPackages(pm, Boolean(flags['dry-run']), tag);
3453
3797
  return;
3454
3798
  }
3455
3799