@rs-x/cli 2.0.0-next.19 → 2.0.0-next.20

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 (27) hide show
  1. package/bin/rsx.cjs +370 -188
  2. package/package.json +4 -1
  3. package/{rs-x-vscode-extension-2.0.0-next.19.vsix → rs-x-vscode-extension-2.0.0-next.20.vsix} +0 -0
  4. package/scripts/prepare-local-rsx-packages.sh +20 -0
  5. package/scripts/verify-rsx-cli-mutations.sh +258 -0
  6. package/scripts/verify-rsx-projects.sh +134 -0
  7. package/scripts/verify-rsx-setup.sh +186 -0
  8. package/templates/next-demo/components/demo-app.tsx +5 -5
  9. package/templates/next-demo/components/virtual-table-shell.tsx +2 -2
  10. package/templates/next-demo/lib/row-model.ts +1 -1
  11. package/templates/next-demo/lib/virtual-table-controller.ts +17 -5
  12. package/templates/next-demo/lib/virtual-table-data.service.ts +8 -2
  13. package/templates/react-demo/src/app/app.tsx +3 -3
  14. package/templates/react-demo/src/app/virtual-table/row-model.ts +1 -1
  15. package/templates/react-demo/src/app/virtual-table/virtual-table-controller.ts +17 -5
  16. package/templates/react-demo/src/app/virtual-table/virtual-table-data.service.ts +8 -2
  17. package/templates/react-demo/src/app/virtual-table/virtual-table-shell.tsx +1 -0
  18. package/templates/react-demo/src/main.tsx +1 -0
  19. package/templates/react-demo/vite.config.ts +1 -1
  20. package/templates/vue-demo/src/App.vue +5 -5
  21. package/templates/vue-demo/src/components/VirtualTableShell.vue +19 -6
  22. package/templates/vue-demo/src/composables/use-virtual-table-controller.ts +1 -1
  23. package/templates/vue-demo/src/env.d.ts +5 -1
  24. package/templates/vue-demo/src/lib/row-model.ts +1 -1
  25. package/templates/vue-demo/src/lib/virtual-table-controller.ts +17 -5
  26. package/templates/vue-demo/src/lib/virtual-table-data.service.ts +8 -2
  27. package/templates/vue-demo/src/main.ts +2 -1
package/bin/rsx.cjs CHANGED
@@ -151,7 +151,7 @@ function parseArgs(argv) {
151
151
  }
152
152
 
153
153
  function run(command, args, options = {}) {
154
- const { dryRun, cwd = process.cwd() } = options;
154
+ const { dryRun, cwd = process.cwd(), env } = options;
155
155
  const printable = [command, ...args].join(' ');
156
156
 
157
157
  if (dryRun) {
@@ -161,6 +161,7 @@ function run(command, args, options = {}) {
161
161
 
162
162
  const result = spawnSync(command, args, {
163
163
  cwd,
164
+ env: env ? { ...process.env, ...env } : process.env,
164
165
  stdio: 'inherit',
165
166
  });
166
167
 
@@ -249,7 +250,7 @@ function resolveInstallTag(flags) {
249
250
  }
250
251
 
251
252
  function installPackages(pm, packages, options = {}) {
252
- const { dev = false, dryRun = false, label = 'packages', tag } = options;
253
+ const { dev = false, dryRun = false, label = 'packages', tag, cwd } = options;
253
254
  const resolvedPackages = tag ? applyTagToPackages(packages, tag) : packages;
254
255
  const argsByPm = {
255
256
  pnpm: dev
@@ -274,24 +275,74 @@ function installPackages(pm, packages, options = {}) {
274
275
 
275
276
  const tagInfo = tag ? ` (tag: ${tag})` : '';
276
277
  logInfo(`Installing ${label} with ${pm}${tagInfo}...`);
277
- run(pm, installArgs, { dryRun });
278
+ run(pm, installArgs, { dryRun, cwd });
278
279
  logOk(`Installed ${label}.`);
279
280
  }
280
281
 
281
- function installRuntimePackages(pm, dryRun, tag) {
282
- installPackages(pm, RUNTIME_PACKAGES, {
282
+ function resolveLocalRsxSpecs(projectRoot, flags, options = {}) {
283
+ const tarballsDir =
284
+ typeof flags?.['tarballs-dir'] === 'string'
285
+ ? path.resolve(projectRoot, flags['tarballs-dir'])
286
+ : typeof process.env.RSX_TARBALLS_DIR === 'string' &&
287
+ process.env.RSX_TARBALLS_DIR.trim().length > 0
288
+ ? path.resolve(projectRoot, process.env.RSX_TARBALLS_DIR)
289
+ : null;
290
+ const workspaceRoot = findRepoRoot(projectRoot);
291
+ return resolveProjectRsxSpecs(
292
+ projectRoot,
293
+ workspaceRoot,
294
+ tarballsDir,
295
+ options,
296
+ );
297
+ }
298
+
299
+ function installResolvedPackages(pm, packageNames, options = {}) {
300
+ const {
301
+ dryRun = false,
302
+ label = 'packages',
303
+ tag,
304
+ cwd,
305
+ specs,
306
+ dev = false,
307
+ } = options;
308
+ const resolvedPackages = packageNames.map((packageName) => {
309
+ const spec = specs?.[packageName];
310
+ return spec ? `${packageName}@${spec}` : packageName;
311
+ });
312
+
313
+ installPackages(pm, resolvedPackages, {
314
+ dev,
315
+ dryRun,
316
+ label,
317
+ tag: specs ? undefined : tag,
318
+ cwd,
319
+ });
320
+ }
321
+
322
+ function installRuntimePackages(pm, dryRun, tag, projectRoot, flags) {
323
+ const specs = resolveLocalRsxSpecs(projectRoot ?? process.cwd(), flags, {
324
+ tag,
325
+ });
326
+ installResolvedPackages(pm, RUNTIME_PACKAGES, {
283
327
  dev: false,
284
328
  dryRun,
285
329
  tag,
330
+ specs,
331
+ cwd: projectRoot,
286
332
  label: 'runtime RS-X packages',
287
333
  });
288
334
  }
289
335
 
290
- function installCompilerPackages(pm, dryRun, tag) {
291
- installPackages(pm, COMPILER_PACKAGES, {
336
+ function installCompilerPackages(pm, dryRun, tag, projectRoot, flags) {
337
+ const specs = resolveLocalRsxSpecs(projectRoot ?? process.cwd(), flags, {
338
+ tag,
339
+ });
340
+ installResolvedPackages(pm, COMPILER_PACKAGES, {
292
341
  dev: true,
293
342
  dryRun,
294
343
  tag,
344
+ specs,
345
+ cwd: projectRoot,
295
346
  label: 'compiler tooling',
296
347
  });
297
348
  }
@@ -822,7 +873,11 @@ function upsertTypescriptPluginInTsConfig(configPath, dryRun) {
822
873
  return;
823
874
  }
824
875
 
825
- fs.writeFileSync(configPath, `${JSON.stringify(tsConfig, null, 2)}\n`, 'utf8');
876
+ fs.writeFileSync(
877
+ configPath,
878
+ `${JSON.stringify(tsConfig, null, 2)}\n`,
879
+ 'utf8',
880
+ );
826
881
  }
827
882
 
828
883
  function ensureTsConfigIncludePattern(configPath, pattern, dryRun) {
@@ -842,7 +897,37 @@ function ensureTsConfigIncludePattern(configPath, pattern, dryRun) {
842
897
  return;
843
898
  }
844
899
 
845
- fs.writeFileSync(configPath, `${JSON.stringify(tsConfig, null, 2)}\n`, 'utf8');
900
+ fs.writeFileSync(
901
+ configPath,
902
+ `${JSON.stringify(tsConfig, null, 2)}\n`,
903
+ 'utf8',
904
+ );
905
+ }
906
+
907
+ function ensureVueEnvTypes(projectRoot, dryRun) {
908
+ const envTypesPath = path.join(projectRoot, 'src', 'vite-env.d.ts');
909
+ const envTypesSource = `/// <reference types="vite/client" />
910
+
911
+ declare module '*.vue' {
912
+ import type { DefineComponent } from 'vue';
913
+
914
+ const component: DefineComponent<{}, {}, any>;
915
+ export default component;
916
+ }
917
+ `;
918
+
919
+ if (fs.existsSync(envTypesPath)) {
920
+ return;
921
+ }
922
+
923
+ if (dryRun) {
924
+ logInfo(`[dry-run] create ${envTypesPath}`);
925
+ return;
926
+ }
927
+
928
+ fs.mkdirSync(path.dirname(envTypesPath), { recursive: true });
929
+ fs.writeFileSync(envTypesPath, envTypesSource, 'utf8');
930
+ logOk(`Created ${envTypesPath}`);
846
931
  }
847
932
 
848
933
  function toFileDependencySpec(fromDir, targetPath) {
@@ -987,10 +1072,11 @@ function resolveProjectRsxSpecs(
987
1072
  ),
988
1073
  ...(includeAngularPackage
989
1074
  ? {
990
- '@rs-x/angular': path.join(
991
- workspaceRoot,
992
- 'rs-x-angular/projects/rsx',
993
- ),
1075
+ '@rs-x/angular': fs.existsSync(
1076
+ path.join(workspaceRoot, 'rs-x-angular/dist/rsx'),
1077
+ )
1078
+ ? path.join(workspaceRoot, 'rs-x-angular/dist/rsx')
1079
+ : path.join(workspaceRoot, 'rs-x-angular/projects/rsx'),
994
1080
  }
995
1081
  : {}),
996
1082
  ...(includeReactPackage
@@ -1063,6 +1149,22 @@ function createProjectPackageJson(projectName, rsxSpecs) {
1063
1149
  );
1064
1150
  }
1065
1151
 
1152
+ function resolveProjectRoot(projectName, flags) {
1153
+ const parentDir =
1154
+ typeof flags?.['project-parent-dir'] === 'string'
1155
+ ? flags['project-parent-dir']
1156
+ : typeof process.env.RSX_PROJECT_PARENT_DIR === 'string' &&
1157
+ process.env.RSX_PROJECT_PARENT_DIR.trim().length > 0
1158
+ ? process.env.RSX_PROJECT_PARENT_DIR
1159
+ : null;
1160
+
1161
+ if (parentDir) {
1162
+ return path.resolve(parentDir, projectName);
1163
+ }
1164
+
1165
+ return path.resolve(process.cwd(), projectName);
1166
+ }
1167
+
1066
1168
  function createProjectTsConfig() {
1067
1169
  return (
1068
1170
  JSON.stringify(
@@ -1247,7 +1349,7 @@ async function runProject(flags) {
1247
1349
  }
1248
1350
  }
1249
1351
 
1250
- const projectRoot = path.resolve(process.cwd(), projectName);
1352
+ const projectRoot = resolveProjectRoot(projectName, flags);
1251
1353
  const tarballsDir =
1252
1354
  typeof flags['tarballs-dir'] === 'string'
1253
1355
  ? path.resolve(process.cwd(), flags['tarballs-dir'])
@@ -1401,9 +1503,24 @@ async function resolveProjectName(nameFromFlags, fallbackName) {
1401
1503
  }
1402
1504
  }
1403
1505
 
1404
- function scaffoldProjectTemplate(template, projectName, pm, flags) {
1506
+ function scaffoldProjectTemplate(
1507
+ template,
1508
+ projectName,
1509
+ projectRoot,
1510
+ pm,
1511
+ flags,
1512
+ ) {
1405
1513
  const dryRun = Boolean(flags['dry-run']);
1406
1514
  const skipInstall = Boolean(flags['skip-install']);
1515
+ const scaffoldCwd = path.dirname(projectRoot);
1516
+ const scaffoldProjectArg = `./${projectName}`;
1517
+ const scaffoldEnv = {
1518
+ INIT_CWD: scaffoldCwd,
1519
+ npm_config_local_prefix: scaffoldCwd,
1520
+ npm_prefix: scaffoldCwd,
1521
+ PWD: scaffoldCwd,
1522
+ CI: 'true',
1523
+ };
1407
1524
 
1408
1525
  if (template === 'angular') {
1409
1526
  const args = [
@@ -1411,6 +1528,8 @@ function scaffoldProjectTemplate(template, projectName, pm, flags) {
1411
1528
  '@angular/cli@latest',
1412
1529
  'new',
1413
1530
  projectName,
1531
+ '--directory',
1532
+ scaffoldProjectArg,
1414
1533
  '--defaults',
1415
1534
  '--standalone',
1416
1535
  '--routing',
@@ -1421,7 +1540,7 @@ function scaffoldProjectTemplate(template, projectName, pm, flags) {
1421
1540
  if (skipInstall) {
1422
1541
  args.push('--skip-install');
1423
1542
  }
1424
- run('npx', args, { dryRun });
1543
+ run('npx', args, { dryRun, cwd: scaffoldCwd, env: scaffoldEnv });
1425
1544
  return;
1426
1545
  }
1427
1546
 
@@ -1430,13 +1549,15 @@ function scaffoldProjectTemplate(template, projectName, pm, flags) {
1430
1549
  'npx',
1431
1550
  [
1432
1551
  'create-vite@latest',
1433
- projectName,
1552
+ scaffoldProjectArg,
1434
1553
  '--no-interactive',
1435
1554
  '--template',
1436
1555
  'react-ts',
1437
1556
  ],
1438
1557
  {
1439
1558
  dryRun,
1559
+ cwd: scaffoldCwd,
1560
+ env: scaffoldEnv,
1440
1561
  },
1441
1562
  );
1442
1563
  return;
@@ -1447,13 +1568,15 @@ function scaffoldProjectTemplate(template, projectName, pm, flags) {
1447
1568
  'npx',
1448
1569
  [
1449
1570
  'create-vite@latest',
1450
- projectName,
1571
+ scaffoldProjectArg,
1451
1572
  '--no-interactive',
1452
1573
  '--template',
1453
1574
  'vue-ts',
1454
1575
  ],
1455
1576
  {
1456
1577
  dryRun,
1578
+ cwd: scaffoldCwd,
1579
+ env: scaffoldEnv,
1457
1580
  },
1458
1581
  );
1459
1582
  return;
@@ -1468,7 +1591,7 @@ function scaffoldProjectTemplate(template, projectName, pm, flags) {
1468
1591
  };
1469
1592
  const args = [
1470
1593
  'create-next-app@latest',
1471
- projectName,
1594
+ scaffoldProjectArg,
1472
1595
  '--yes',
1473
1596
  '--ts',
1474
1597
  '--app',
@@ -1480,7 +1603,7 @@ function scaffoldProjectTemplate(template, projectName, pm, flags) {
1480
1603
  if (skipInstall) {
1481
1604
  args.push('--skip-install');
1482
1605
  }
1483
- run('npx', args, { dryRun });
1606
+ run('npx', args, { dryRun, cwd: scaffoldCwd, env: scaffoldEnv });
1484
1607
  return;
1485
1608
  }
1486
1609
 
@@ -1730,7 +1853,9 @@ function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
1730
1853
 
1731
1854
  const packageJsonPath = path.join(projectRoot, 'package.json');
1732
1855
  if (!fs.existsSync(packageJsonPath)) {
1733
- logError(`package.json not found in generated React app: ${packageJsonPath}`);
1856
+ logError(
1857
+ `package.json not found in generated React app: ${packageJsonPath}`,
1858
+ );
1734
1859
  process.exit(1);
1735
1860
  }
1736
1861
 
@@ -1957,7 +2082,9 @@ function applyNextDemoStarter(projectRoot, projectName, pm, flags) {
1957
2082
 
1958
2083
  const packageJsonPath = path.join(projectRoot, 'package.json');
1959
2084
  if (!fs.existsSync(packageJsonPath)) {
1960
- logError(`package.json not found in generated Next.js app: ${packageJsonPath}`);
2085
+ logError(
2086
+ `package.json not found in generated Next.js app: ${packageJsonPath}`,
2087
+ );
1961
2088
  process.exit(1);
1962
2089
  }
1963
2090
 
@@ -2033,13 +2160,19 @@ async function runProjectWithTemplate(template, flags) {
2033
2160
 
2034
2161
  const pm = detectPackageManager(flags.pm);
2035
2162
  const projectName = await resolveProjectName(flags.name, flags._nameHint);
2036
- const projectRoot = path.resolve(process.cwd(), projectName);
2163
+ const projectRoot = resolveProjectRoot(projectName, flags);
2037
2164
  if (fs.existsSync(projectRoot) && fs.readdirSync(projectRoot).length > 0) {
2038
2165
  logError(`Target directory is not empty: ${projectRoot}`);
2039
2166
  process.exit(1);
2040
2167
  }
2041
2168
 
2042
- scaffoldProjectTemplate(normalizedTemplate, projectName, pm, flags);
2169
+ scaffoldProjectTemplate(
2170
+ normalizedTemplate,
2171
+ projectName,
2172
+ projectRoot,
2173
+ pm,
2174
+ flags,
2175
+ );
2043
2176
  const dryRun = Boolean(flags['dry-run']);
2044
2177
  if (dryRun) {
2045
2178
  logInfo(`[dry-run] setup RS-X in ${projectRoot}`);
@@ -2335,12 +2468,12 @@ function ensureNextGateFile(gateFile, bootstrapFile, dryRun) {
2335
2468
  const content = useTypeScript
2336
2469
  ? `'use client';
2337
2470
 
2338
- import { type ReactNode, useEffect, useState } from 'react';
2471
+ import { type ReactElement, type ReactNode, useEffect, useState } from 'react';
2339
2472
 
2340
2473
  import { initRsx } from '${importPath}';
2341
2474
 
2342
2475
  // Generated by rsx init
2343
- export function RsxBootstrapGate(props: { children: ReactNode }): JSX.Element | null {
2476
+ export function RsxBootstrapGate(props: { children: ReactNode }): ReactElement | null {
2344
2477
  const [ready, setReady] = useState(false);
2345
2478
 
2346
2479
  useEffect(() => {
@@ -2561,8 +2694,8 @@ function runInit(flags) {
2561
2694
  const projectRoot = process.cwd();
2562
2695
 
2563
2696
  if (!skipInstall) {
2564
- installRuntimePackages(pm, dryRun, tag);
2565
- installCompilerPackages(pm, dryRun, tag);
2697
+ installRuntimePackages(pm, dryRun, tag, projectRoot, flags);
2698
+ installCompilerPackages(pm, dryRun, tag, projectRoot, flags);
2566
2699
  } else {
2567
2700
  logInfo('Skipping package installation (--skip-install).');
2568
2701
  }
@@ -2687,7 +2820,12 @@ function ensureAngularProvidersInEntry(entryFile, dryRun) {
2687
2820
  );
2688
2821
 
2689
2822
  let updated = sourceWithImport;
2690
- if (
2823
+ if (/bootstrapApplication\([\s\S]*?,\s*appConfig\s*\)/mu.test(updated)) {
2824
+ updated = updated.replace(
2825
+ /bootstrapApplication\(([\s\S]*?),\s*appConfig\s*\)/mu,
2826
+ 'bootstrapApplication($1, {\n ...appConfig,\n providers: [...(appConfig.providers ?? []), ...providexRsx()],\n})',
2827
+ );
2828
+ } else if (
2691
2829
  /bootstrapApplication\([\s\S]*?,\s*\{[\s\S]*?providers\s*:/mu.test(updated)
2692
2830
  ) {
2693
2831
  updated = updated.replace(
@@ -2835,109 +2973,6 @@ module.exports = function rsxWebpackLoader(source) {
2835
2973
  }
2836
2974
 
2837
2975
  function wireRsxVitePlugin(projectRoot, dryRun) {
2838
- const pluginFile = path.join(projectRoot, 'rsx-vite-plugin.mjs');
2839
- const pluginSource = `import path from 'node:path';
2840
-
2841
- import ts from 'typescript';
2842
-
2843
- import { createExpressionCachePreloadTransformer } from '@rs-x/compiler';
2844
-
2845
- function normalizeFileName(fileName) {
2846
- return path.resolve(fileName).replace(/\\\\/gu, '/');
2847
- }
2848
-
2849
- function buildTransformedSourceMap(tsconfigPath) {
2850
- const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
2851
- if (configFile.error) {
2852
- return new Map();
2853
- }
2854
-
2855
- const parsed = ts.parseJsonConfigFileContent(
2856
- configFile.config,
2857
- ts.sys,
2858
- path.dirname(tsconfigPath),
2859
- undefined,
2860
- tsconfigPath,
2861
- );
2862
- if (parsed.errors.length > 0) {
2863
- return new Map();
2864
- }
2865
-
2866
- const program = ts.createProgram({
2867
- rootNames: parsed.fileNames,
2868
- options: parsed.options,
2869
- });
2870
- const transformer = createExpressionCachePreloadTransformer(program);
2871
- const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
2872
- const transformedByFile = new Map();
2873
-
2874
- for (const sourceFile of program.getSourceFiles()) {
2875
- if (sourceFile.isDeclarationFile) {
2876
- continue;
2877
- }
2878
-
2879
- if (sourceFile.fileName.includes('/node_modules/')) {
2880
- continue;
2881
- }
2882
-
2883
- const transformed = ts.transform(sourceFile, [transformer]);
2884
- const transformedSource = transformed.transformed[0];
2885
- const transformedText = printer.printFile(transformedSource);
2886
- transformed.dispose();
2887
-
2888
- transformedByFile.set(normalizeFileName(sourceFile.fileName), transformedText);
2889
- }
2890
-
2891
- return transformedByFile;
2892
- }
2893
-
2894
- export function rsxVitePlugin(tsconfigPath = 'tsconfig.json') {
2895
- let transformedByFile = new Map();
2896
- let resolvedTsConfigPath = '';
2897
-
2898
- const refresh = () => {
2899
- transformedByFile = buildTransformedSourceMap(resolvedTsConfigPath);
2900
- };
2901
-
2902
- return {
2903
- name: 'rsx-vite-transform',
2904
- enforce: 'pre',
2905
- configResolved(config) {
2906
- resolvedTsConfigPath = normalizeFileName(path.resolve(config.root, tsconfigPath));
2907
- refresh();
2908
- },
2909
- buildStart() {
2910
- if (!resolvedTsConfigPath) {
2911
- resolvedTsConfigPath = normalizeFileName(path.resolve(process.cwd(), tsconfigPath));
2912
- }
2913
- refresh();
2914
- },
2915
- handleHotUpdate() {
2916
- refresh();
2917
- },
2918
- transform(_code, id) {
2919
- const normalizedId = normalizeFileName(id.split('?')[0]);
2920
- const transformed = transformedByFile.get(normalizedId);
2921
- if (!transformed) {
2922
- return null;
2923
- }
2924
-
2925
- return {
2926
- code: transformed,
2927
- map: null,
2928
- };
2929
- },
2930
- };
2931
- }
2932
- `;
2933
-
2934
- if (dryRun) {
2935
- logInfo(`[dry-run] create ${pluginFile}`);
2936
- } else {
2937
- fs.writeFileSync(pluginFile, pluginSource, 'utf8');
2938
- logOk(`Created ${pluginFile}`);
2939
- }
2940
-
2941
2976
  const viteConfigCandidates = [
2942
2977
  'vite.config.ts',
2943
2978
  'vite.config.mts',
@@ -2947,61 +2982,47 @@ export function rsxVitePlugin(tsconfigPath = 'tsconfig.json') {
2947
2982
  const viteConfigPath = viteConfigCandidates.find((candidate) =>
2948
2983
  fs.existsSync(candidate),
2949
2984
  );
2985
+ const stalePluginFiles = [
2986
+ path.join(projectRoot, 'rsx-vite-plugin.ts'),
2987
+ path.join(projectRoot, 'rsx-vite-plugin.mjs'),
2988
+ path.join(projectRoot, 'rsx-vite-plugin.d.ts'),
2989
+ ];
2990
+
2950
2991
  if (!viteConfigPath) {
2951
- logWarn(
2952
- 'No vite.config.[ts|mts|js|mjs] found. RS-X Vite plugin file was created, but config patch was skipped.',
2953
- );
2954
- logInfo(
2955
- "Add it manually: import { rsxVitePlugin } from './rsx-vite-plugin.mjs' and include rsxVitePlugin() in plugins.",
2956
- );
2992
+ for (const staleFile of stalePluginFiles) {
2993
+ removeFileOrDirectoryWithDryRun(staleFile, dryRun);
2994
+ }
2957
2995
  return;
2958
2996
  }
2959
2997
 
2960
2998
  const original = fs.readFileSync(viteConfigPath, 'utf8');
2961
- if (original.includes('rsxVitePlugin(')) {
2962
- logInfo(`Vite config already includes RS-X plugin: ${viteConfigPath}`);
2963
- return;
2964
- }
2999
+ const updated = original
3000
+ .replace(
3001
+ /import\s+\{\s*rsxVitePlugin\s*\}\s+from\s+['"]\.\/rsx-vite-plugin(?:\.mjs)?['"];\n?/gu,
3002
+ '',
3003
+ )
3004
+ .replace(/rsxVitePlugin\(\)\s*,\s*/gu, '')
3005
+ .replace(/,\s*rsxVitePlugin\(\)/gu, '')
3006
+ .replace(/\[\s*rsxVitePlugin\(\)\s*\]/gu, '[]');
2965
3007
 
2966
- let updated = original;
2967
- const importStatement =
2968
- "import { rsxVitePlugin } from './rsx-vite-plugin.mjs';";
2969
- if (!updated.includes(importStatement)) {
2970
- const lines = updated.split('\n');
2971
- let insertAt = 0;
2972
- while (
2973
- insertAt < lines.length &&
2974
- lines[insertAt].trim().startsWith('import ')
2975
- ) {
2976
- insertAt += 1;
3008
+ if (updated !== original) {
3009
+ if (dryRun) {
3010
+ logInfo(
3011
+ `[dry-run] patch ${viteConfigPath} (remove legacy RS-X Vite plugin)`,
3012
+ );
3013
+ } else {
3014
+ fs.writeFileSync(viteConfigPath, updated, 'utf8');
3015
+ logOk(`Patched ${viteConfigPath} (removed legacy RS-X Vite plugin).`);
2977
3016
  }
2978
- lines.splice(insertAt, 0, importStatement);
2979
- updated = lines.join('\n');
2980
- }
2981
-
2982
- if (/plugins\s*:\s*\[/u.test(updated)) {
2983
- updated = updated.replace(
2984
- /plugins\s*:\s*\[/u,
2985
- 'plugins: [rsxVitePlugin(), ',
2986
- );
2987
- } else if (/defineConfig\s*\(\s*\{/u.test(updated)) {
2988
- updated = updated.replace(
2989
- /defineConfig\s*\(\s*\{/u,
2990
- 'defineConfig({\n plugins: [rsxVitePlugin()],',
2991
- );
2992
3017
  } else {
2993
- logWarn(`Could not patch Vite config automatically: ${viteConfigPath}`);
2994
- logInfo('Add `rsxVitePlugin()` to your Vite plugins manually.');
2995
- return;
3018
+ logInfo(
3019
+ `Vite config already uses the default plugin list: ${viteConfigPath}`,
3020
+ );
2996
3021
  }
2997
3022
 
2998
- if (dryRun) {
2999
- logInfo(`[dry-run] patch ${viteConfigPath}`);
3000
- return;
3023
+ for (const staleFile of stalePluginFiles) {
3024
+ removeFileOrDirectoryWithDryRun(staleFile, dryRun);
3001
3025
  }
3002
-
3003
- fs.writeFileSync(viteConfigPath, updated, 'utf8');
3004
- logOk(`Patched ${viteConfigPath} with RS-X Vite plugin.`);
3005
3026
  }
3006
3027
 
3007
3028
  function wireRsxNextWebpack(projectRoot, dryRun) {
@@ -3110,15 +3131,47 @@ function runSetupReact(flags) {
3110
3131
  'skip-vscode': true,
3111
3132
  });
3112
3133
  if (!Boolean(flags['skip-install'])) {
3113
- installPackages(pm, ['@rs-x/react'], {
3134
+ const specs = resolveLocalRsxSpecs(projectRoot, flags, {
3135
+ tag,
3136
+ includeReactPackage: true,
3137
+ });
3138
+ installResolvedPackages(pm, ['@rs-x/react'], {
3114
3139
  dev: false,
3115
3140
  dryRun,
3116
3141
  tag,
3142
+ specs,
3143
+ cwd: projectRoot,
3117
3144
  label: 'RS-X React bindings',
3118
3145
  });
3146
+ installResolvedPackages(pm, ['@rs-x/cli'], {
3147
+ dev: true,
3148
+ dryRun,
3149
+ tag,
3150
+ specs,
3151
+ cwd: projectRoot,
3152
+ label: 'RS-X CLI',
3153
+ });
3119
3154
  } else {
3120
3155
  logInfo('Skipping RS-X React bindings install (--skip-install).');
3121
3156
  }
3157
+ upsertScriptInPackageJson(
3158
+ projectRoot,
3159
+ 'build:rsx',
3160
+ 'rsx build --project tsconfig.json --no-emit --prod',
3161
+ dryRun,
3162
+ );
3163
+ upsertScriptInPackageJson(
3164
+ projectRoot,
3165
+ 'dev',
3166
+ 'npm run build:rsx && vite',
3167
+ dryRun,
3168
+ );
3169
+ upsertScriptInPackageJson(
3170
+ projectRoot,
3171
+ 'build',
3172
+ 'npm run build:rsx && vite build',
3173
+ dryRun,
3174
+ );
3122
3175
  wireRsxVitePlugin(projectRoot, dryRun);
3123
3176
  logOk('RS-X React setup completed.');
3124
3177
  }
@@ -3127,21 +3180,28 @@ function runSetupNext(flags) {
3127
3180
  const dryRun = Boolean(flags['dry-run']);
3128
3181
  const pm = detectPackageManager(flags.pm);
3129
3182
  const tag = resolveInstallTag(flags);
3183
+ const projectRoot = process.cwd();
3130
3184
  runInit({
3131
3185
  ...flags,
3132
3186
  'skip-vscode': true,
3133
3187
  });
3134
3188
  if (!Boolean(flags['skip-install'])) {
3135
- installPackages(pm, ['@rs-x/react'], {
3189
+ const specs = resolveLocalRsxSpecs(projectRoot, flags, {
3190
+ tag,
3191
+ includeReactPackage: true,
3192
+ });
3193
+ installResolvedPackages(pm, ['@rs-x/react'], {
3136
3194
  dev: false,
3137
3195
  dryRun,
3138
3196
  tag,
3197
+ specs,
3198
+ cwd: projectRoot,
3139
3199
  label: 'RS-X React bindings',
3140
3200
  });
3141
3201
  } else {
3142
3202
  logInfo('Skipping RS-X React bindings install (--skip-install).');
3143
3203
  }
3144
- wireRsxNextWebpack(process.cwd(), dryRun);
3204
+ wireRsxNextWebpack(projectRoot, dryRun);
3145
3205
  logOk('RS-X Next.js setup completed.');
3146
3206
  }
3147
3207
 
@@ -3149,21 +3209,64 @@ function runSetupVue(flags) {
3149
3209
  const dryRun = Boolean(flags['dry-run']);
3150
3210
  const pm = detectPackageManager(flags.pm);
3151
3211
  const tag = resolveInstallTag(flags);
3212
+ const projectRoot = process.cwd();
3152
3213
  runInit({
3153
3214
  ...flags,
3154
3215
  'skip-vscode': true,
3155
3216
  });
3156
3217
  if (!Boolean(flags['skip-install'])) {
3157
- installPackages(pm, ['@rs-x/vue'], {
3218
+ const specs = resolveLocalRsxSpecs(projectRoot, flags, {
3219
+ tag,
3220
+ includeVuePackage: true,
3221
+ });
3222
+ installResolvedPackages(pm, ['@rs-x/vue'], {
3158
3223
  dev: false,
3159
3224
  dryRun,
3160
3225
  tag,
3226
+ specs,
3227
+ cwd: projectRoot,
3161
3228
  label: 'RS-X Vue bindings',
3162
3229
  });
3230
+ installResolvedPackages(pm, ['@rs-x/cli'], {
3231
+ dev: true,
3232
+ dryRun,
3233
+ tag,
3234
+ specs,
3235
+ cwd: projectRoot,
3236
+ label: 'RS-X CLI',
3237
+ });
3163
3238
  } else {
3164
3239
  logInfo('Skipping RS-X Vue bindings install (--skip-install).');
3165
3240
  }
3166
- wireRsxVitePlugin(process.cwd(), dryRun);
3241
+ upsertScriptInPackageJson(
3242
+ projectRoot,
3243
+ 'build:rsx',
3244
+ 'rsx build --project tsconfig.app.json --no-emit --prod',
3245
+ dryRun,
3246
+ );
3247
+ upsertScriptInPackageJson(
3248
+ projectRoot,
3249
+ 'typecheck:rsx',
3250
+ 'rsx typecheck --project tsconfig.app.json',
3251
+ dryRun,
3252
+ );
3253
+ upsertScriptInPackageJson(
3254
+ projectRoot,
3255
+ 'dev',
3256
+ 'npm run build:rsx && vite',
3257
+ dryRun,
3258
+ );
3259
+ upsertScriptInPackageJson(
3260
+ projectRoot,
3261
+ 'build',
3262
+ 'npm run build:rsx && vue-tsc -b && vite build',
3263
+ dryRun,
3264
+ );
3265
+ const vueTsConfigPath = path.join(projectRoot, 'tsconfig.app.json');
3266
+ upsertTypescriptPluginInTsConfig(vueTsConfigPath, dryRun);
3267
+ ensureTsConfigIncludePattern(vueTsConfigPath, 'src/**/*.d.ts', dryRun);
3268
+ ensureVueEnvTypes(projectRoot, dryRun);
3269
+ wireRsxVitePlugin(projectRoot, dryRun);
3167
3270
  logOk('RS-X Vue setup completed.');
3168
3271
  }
3169
3272
 
@@ -3178,14 +3281,28 @@ function runSetupAngular(flags) {
3178
3281
  .replace(/\\/gu, '/');
3179
3282
 
3180
3283
  if (!Boolean(flags['skip-install'])) {
3181
- installRuntimePackages(pm, dryRun, tag);
3182
- installCompilerPackages(pm, dryRun, tag);
3183
- installPackages(pm, ['@rs-x/angular'], {
3284
+ installRuntimePackages(pm, dryRun, tag, projectRoot, flags);
3285
+ installCompilerPackages(pm, dryRun, tag, projectRoot, flags);
3286
+ const specs = resolveLocalRsxSpecs(projectRoot, flags, {
3287
+ tag,
3288
+ includeAngularPackage: true,
3289
+ });
3290
+ installResolvedPackages(pm, ['@rs-x/angular'], {
3184
3291
  dev: false,
3185
3292
  dryRun,
3186
3293
  tag,
3294
+ specs,
3295
+ cwd: projectRoot,
3187
3296
  label: 'RS-X Angular bindings',
3188
3297
  });
3298
+ installResolvedPackages(pm, ['@rs-x/cli'], {
3299
+ dev: true,
3300
+ dryRun,
3301
+ tag,
3302
+ specs,
3303
+ cwd: projectRoot,
3304
+ label: 'RS-X CLI',
3305
+ });
3189
3306
  } else {
3190
3307
  logInfo('Skipping package installation (--skip-install).');
3191
3308
  }
@@ -3215,11 +3332,53 @@ function runSetupAngular(flags) {
3215
3332
  `rsx typecheck --project ${angularTsConfigRelative}`,
3216
3333
  dryRun,
3217
3334
  );
3335
+ upsertScriptInPackageJson(
3336
+ projectRoot,
3337
+ 'prebuild',
3338
+ 'npm run build:rsx',
3339
+ dryRun,
3340
+ );
3341
+ upsertScriptInPackageJson(
3342
+ projectRoot,
3343
+ 'start',
3344
+ 'npm run build:rsx && ng serve',
3345
+ dryRun,
3346
+ );
3218
3347
 
3219
3348
  const rsxRegistrationFile = path.join(
3220
3349
  projectRoot,
3221
3350
  'src/rsx-generated/rsx-aot-registration.generated.ts',
3222
3351
  );
3352
+ const angularJsonPath = path.join(projectRoot, 'angular.json');
3353
+ if (fs.existsSync(angularJsonPath)) {
3354
+ const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf8'));
3355
+ const projects = angularJson.projects ?? {};
3356
+ for (const projectConfig of Object.values(projects)) {
3357
+ const buildOptions = projectConfig?.architect?.build?.options;
3358
+ if (buildOptions && typeof buildOptions === 'object') {
3359
+ buildOptions.preserveSymlinks = true;
3360
+ }
3361
+ if (
3362
+ projectConfig?.architect?.build?.configurations?.production?.budgets
3363
+ ) {
3364
+ delete projectConfig.architect.build.configurations.production.budgets;
3365
+ }
3366
+ }
3367
+ if (dryRun) {
3368
+ logInfo(
3369
+ `[dry-run] patch ${angularJsonPath} (preserveSymlinks, production budgets)`,
3370
+ );
3371
+ } else {
3372
+ fs.writeFileSync(
3373
+ angularJsonPath,
3374
+ `${JSON.stringify(angularJson, null, 2)}\n`,
3375
+ 'utf8',
3376
+ );
3377
+ logOk(
3378
+ `Patched ${angularJsonPath} (preserveSymlinks, production budgets).`,
3379
+ );
3380
+ }
3381
+ }
3223
3382
  ensureAngularPolyfillsContainsFile({
3224
3383
  projectRoot,
3225
3384
  configPath: angularTsConfigPath,
@@ -3261,8 +3420,20 @@ function runSetupAuto(flags) {
3261
3420
 
3262
3421
  logInfo('No framework-specific setup detected; running generic setup.');
3263
3422
  const pm = detectPackageManager(flags.pm);
3264
- installRuntimePackages(pm, Boolean(flags['dry-run']), tag);
3265
- installCompilerPackages(pm, Boolean(flags['dry-run']), tag);
3423
+ installRuntimePackages(
3424
+ pm,
3425
+ Boolean(flags['dry-run']),
3426
+ tag,
3427
+ projectRoot,
3428
+ flags,
3429
+ );
3430
+ installCompilerPackages(
3431
+ pm,
3432
+ Boolean(flags['dry-run']),
3433
+ tag,
3434
+ projectRoot,
3435
+ flags,
3436
+ );
3266
3437
  }
3267
3438
 
3268
3439
  function resolveProjectModule(projectRoot, moduleName) {
@@ -3827,9 +3998,14 @@ function ensureAngularPolyfillsContainsFile({
3827
3998
  });
3828
3999
 
3829
4000
  const selectedEntries = targetEntries.length > 0 ? targetEntries : entries;
3830
- const polyfillsPath = path
4001
+ const polyfillsRelativePath = path
3831
4002
  .relative(projectRoot, filePath)
3832
4003
  .replace(/\\/g, '/');
4004
+ const polyfillsPath =
4005
+ polyfillsRelativePath.startsWith('./') ||
4006
+ polyfillsRelativePath.startsWith('../')
4007
+ ? polyfillsRelativePath
4008
+ : `./${polyfillsRelativePath}`;
3833
4009
 
3834
4010
  let changed = false;
3835
4011
  const isRsxAotRegistrationEntry = (entry) =>
@@ -4352,7 +4528,13 @@ function main() {
4352
4528
  if (command === 'install' && target === 'compiler') {
4353
4529
  const pm = detectPackageManager(flags.pm);
4354
4530
  const tag = resolveInstallTag(flags);
4355
- installCompilerPackages(pm, Boolean(flags['dry-run']), tag);
4531
+ installCompilerPackages(
4532
+ pm,
4533
+ Boolean(flags['dry-run']),
4534
+ tag,
4535
+ process.cwd(),
4536
+ flags,
4537
+ );
4356
4538
  return;
4357
4539
  }
4358
4540