@rs-x/cli 2.0.0-next.15 → 2.0.0-next.17

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 (31) hide show
  1. package/bin/rsx.cjs +337 -7
  2. package/package.json +1 -1
  3. package/{rs-x-vscode-extension-2.0.0-next.15.vsix → rs-x-vscode-extension-2.0.0-next.17.vsix} +0 -0
  4. package/templates/next-demo/README.md +26 -0
  5. package/templates/next-demo/app/globals.css +431 -0
  6. package/templates/next-demo/app/layout.tsx +22 -0
  7. package/templates/next-demo/app/page.tsx +5 -0
  8. package/templates/next-demo/components/demo-app.tsx +114 -0
  9. package/templates/next-demo/components/virtual-table-row.tsx +40 -0
  10. package/templates/next-demo/components/virtual-table-shell.tsx +86 -0
  11. package/templates/next-demo/hooks/use-virtual-table-controller.ts +26 -0
  12. package/templates/next-demo/hooks/use-virtual-table-viewport.ts +41 -0
  13. package/templates/next-demo/lib/row-data.ts +35 -0
  14. package/templates/next-demo/lib/row-model.ts +45 -0
  15. package/templates/next-demo/lib/rsx-bootstrap.ts +46 -0
  16. package/templates/next-demo/lib/virtual-table-controller.ts +247 -0
  17. package/templates/next-demo/lib/virtual-table-data.service.ts +126 -0
  18. package/templates/vue-demo/README.md +27 -0
  19. package/templates/vue-demo/src/App.vue +89 -0
  20. package/templates/vue-demo/src/components/VirtualTableRow.vue +33 -0
  21. package/templates/vue-demo/src/components/VirtualTableShell.vue +58 -0
  22. package/templates/vue-demo/src/composables/use-virtual-table-controller.ts +33 -0
  23. package/templates/vue-demo/src/composables/use-virtual-table-viewport.ts +40 -0
  24. package/templates/vue-demo/src/env.d.ts +6 -0
  25. package/templates/vue-demo/src/lib/row-data.ts +35 -0
  26. package/templates/vue-demo/src/lib/row-model.ts +45 -0
  27. package/templates/vue-demo/src/lib/rsx-bootstrap.ts +46 -0
  28. package/templates/vue-demo/src/lib/virtual-table-controller.ts +247 -0
  29. package/templates/vue-demo/src/lib/virtual-table-data.service.ts +126 -0
  30. package/templates/vue-demo/src/main.ts +12 -0
  31. package/templates/vue-demo/src/style.css +440 -0
package/bin/rsx.cjs CHANGED
@@ -27,6 +27,18 @@ const REACT_DEMO_TEMPLATE_DIR = path.join(
27
27
  'templates',
28
28
  'react-demo',
29
29
  );
30
+ const VUE_DEMO_TEMPLATE_DIR = path.join(
31
+ __dirname,
32
+ '..',
33
+ 'templates',
34
+ 'vue-demo',
35
+ );
36
+ const NEXT_DEMO_TEMPLATE_DIR = path.join(
37
+ __dirname,
38
+ '..',
39
+ 'templates',
40
+ 'next-demo',
41
+ );
30
42
  const RUNTIME_PACKAGES = [
31
43
  '@rs-x/core',
32
44
  '@rs-x/state-manager',
@@ -663,6 +675,77 @@ function writeFileWithDryRun(filePath, content, dryRun) {
663
675
  fs.writeFileSync(filePath, content, 'utf8');
664
676
  }
665
677
 
678
+ function stripJsonComments(content) {
679
+ let result = '';
680
+ let inString = false;
681
+ let stringDelimiter = '"';
682
+ let inLineComment = false;
683
+ let inBlockComment = false;
684
+
685
+ for (let index = 0; index < content.length; index += 1) {
686
+ const current = content[index];
687
+ const next = content[index + 1];
688
+
689
+ if (inLineComment) {
690
+ if (current === '\n') {
691
+ inLineComment = false;
692
+ result += current;
693
+ }
694
+ continue;
695
+ }
696
+
697
+ if (inBlockComment) {
698
+ if (current === '*' && next === '/') {
699
+ inBlockComment = false;
700
+ index += 1;
701
+ }
702
+ continue;
703
+ }
704
+
705
+ if (inString) {
706
+ result += current;
707
+ if (current === '\\') {
708
+ index += 1;
709
+ if (index < content.length) {
710
+ result += content[index];
711
+ }
712
+ continue;
713
+ }
714
+ if (current === stringDelimiter) {
715
+ inString = false;
716
+ }
717
+ continue;
718
+ }
719
+
720
+ if (current === '"' || current === "'") {
721
+ inString = true;
722
+ stringDelimiter = current;
723
+ result += current;
724
+ continue;
725
+ }
726
+
727
+ if (current === '/' && next === '/') {
728
+ inLineComment = true;
729
+ index += 1;
730
+ continue;
731
+ }
732
+
733
+ if (current === '/' && next === '*') {
734
+ inBlockComment = true;
735
+ index += 1;
736
+ continue;
737
+ }
738
+
739
+ result += current;
740
+ }
741
+
742
+ return result;
743
+ }
744
+
745
+ function parseJsonc(content) {
746
+ return JSON.parse(stripJsonComments(content.replace(/^\uFEFF/u, '')));
747
+ }
748
+
666
749
  function copyPathWithDryRun(sourcePath, targetPath, dryRun) {
667
750
  if (dryRun) {
668
751
  logInfo(`[dry-run] copy ${sourcePath} -> ${targetPath}`);
@@ -714,7 +797,7 @@ function upsertTypescriptPluginInTsConfig(configPath, dryRun) {
714
797
  return;
715
798
  }
716
799
 
717
- const tsConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
800
+ const tsConfig = parseJsonc(fs.readFileSync(configPath, 'utf8'));
718
801
  const compilerOptions = tsConfig.compilerOptions ?? {};
719
802
  const plugins = Array.isArray(compilerOptions.plugins)
720
803
  ? compilerOptions.plugins
@@ -742,6 +825,26 @@ function upsertTypescriptPluginInTsConfig(configPath, dryRun) {
742
825
  fs.writeFileSync(configPath, `${JSON.stringify(tsConfig, null, 2)}\n`, 'utf8');
743
826
  }
744
827
 
828
+ function ensureTsConfigIncludePattern(configPath, pattern, dryRun) {
829
+ if (!fs.existsSync(configPath)) {
830
+ return;
831
+ }
832
+
833
+ const tsConfig = parseJsonc(fs.readFileSync(configPath, 'utf8'));
834
+ const include = Array.isArray(tsConfig.include) ? tsConfig.include : [];
835
+ if (!include.includes(pattern)) {
836
+ include.push(pattern);
837
+ }
838
+ tsConfig.include = include;
839
+
840
+ if (dryRun) {
841
+ logInfo(`[dry-run] patch ${configPath}`);
842
+ return;
843
+ }
844
+
845
+ fs.writeFileSync(configPath, `${JSON.stringify(tsConfig, null, 2)}\n`, 'utf8');
846
+ }
847
+
745
848
  function toFileDependencySpec(fromDir, targetPath) {
746
849
  const relative = path.relative(fromDir, targetPath).replace(/\\/gu, '/');
747
850
  const normalized = relative.startsWith('.') ? relative : `./${relative}`;
@@ -792,6 +895,7 @@ function resolveProjectRsxSpecs(
792
895
  ) {
793
896
  const includeAngularPackage = Boolean(options.includeAngularPackage);
794
897
  const includeReactPackage = Boolean(options.includeReactPackage);
898
+ const includeVuePackage = Boolean(options.includeVuePackage);
795
899
  const versionSpec = options.tag ? options.tag : RSX_PACKAGE_VERSION;
796
900
  const defaults = {
797
901
  '@rs-x/core': versionSpec,
@@ -801,6 +905,7 @@ function resolveProjectRsxSpecs(
801
905
  '@rs-x/typescript-plugin': versionSpec,
802
906
  ...(includeAngularPackage ? { '@rs-x/angular': versionSpec } : {}),
803
907
  ...(includeReactPackage ? { '@rs-x/react': versionSpec } : {}),
908
+ ...(includeVuePackage ? { '@rs-x/vue': versionSpec } : {}),
804
909
  '@rs-x/cli': versionSpec,
805
910
  };
806
911
 
@@ -812,6 +917,7 @@ function resolveProjectRsxSpecs(
812
917
  '@rs-x/typescript-plugin': 'rs-x-typescript-plugin',
813
918
  ...(includeAngularPackage ? { '@rs-x/angular': 'rs-x-angular' } : {}),
814
919
  ...(includeReactPackage ? { '@rs-x/react': 'rs-x-react' } : {}),
920
+ ...(includeVuePackage ? { '@rs-x/vue': 'rs-x-vue' } : {}),
815
921
  '@rs-x/cli': 'rs-x-cli',
816
922
  };
817
923
 
@@ -839,6 +945,11 @@ function resolveProjectRsxSpecs(
839
945
  'rs-x-react': path.join(tarballsDir, 'rs-x-react'),
840
946
  }
841
947
  : {}),
948
+ ...(includeVuePackage
949
+ ? {
950
+ 'rs-x-vue': path.join(tarballsDir, 'rs-x-vue'),
951
+ }
952
+ : {}),
842
953
  'rs-x-cli': path.join(tarballsDir, 'rs-x-cli'),
843
954
  };
844
955
 
@@ -887,6 +998,11 @@ function resolveProjectRsxSpecs(
887
998
  '@rs-x/react': path.join(workspaceRoot, 'rs-x-react'),
888
999
  }
889
1000
  : {}),
1001
+ ...(includeVuePackage
1002
+ ? {
1003
+ '@rs-x/vue': path.join(workspaceRoot, 'rs-x-vue'),
1004
+ }
1005
+ : {}),
890
1006
  '@rs-x/cli': path.join(workspaceRoot, 'rs-x-cli'),
891
1007
  };
892
1008
 
@@ -1681,6 +1797,224 @@ function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
1681
1797
  }
1682
1798
  }
1683
1799
 
1800
+ function applyVueDemoStarter(projectRoot, projectName, pm, flags) {
1801
+ const dryRun = Boolean(flags['dry-run']);
1802
+ const tag = resolveInstallTag(flags);
1803
+ const tarballsDir =
1804
+ typeof flags['tarballs-dir'] === 'string'
1805
+ ? path.resolve(process.cwd(), flags['tarballs-dir'])
1806
+ : typeof process.env.RSX_TARBALLS_DIR === 'string' &&
1807
+ process.env.RSX_TARBALLS_DIR.trim().length > 0
1808
+ ? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
1809
+ : null;
1810
+ const workspaceRoot = findRepoRoot(projectRoot);
1811
+ const rsxSpecs = resolveProjectRsxSpecs(
1812
+ projectRoot,
1813
+ workspaceRoot,
1814
+ tarballsDir,
1815
+ { tag, includeVuePackage: true },
1816
+ );
1817
+
1818
+ const templateFiles = ['README.md', 'src'];
1819
+ for (const entry of templateFiles) {
1820
+ copyPathWithDryRun(
1821
+ path.join(VUE_DEMO_TEMPLATE_DIR, entry),
1822
+ path.join(projectRoot, entry),
1823
+ dryRun,
1824
+ );
1825
+ }
1826
+
1827
+ const staleVueFiles = [
1828
+ path.join(projectRoot, 'public'),
1829
+ ];
1830
+ for (const stalePath of staleVueFiles) {
1831
+ removeFileOrDirectoryWithDryRun(stalePath, dryRun);
1832
+ }
1833
+
1834
+ const readmePath = path.join(projectRoot, 'README.md');
1835
+ if (fs.existsSync(readmePath)) {
1836
+ const readmeSource = fs.readFileSync(readmePath, 'utf8');
1837
+ const nextReadme = readmeSource.replace(
1838
+ /^#\s+rsx-vue-example/mu,
1839
+ `# ${projectName}`,
1840
+ );
1841
+ if (dryRun) {
1842
+ logInfo(`[dry-run] patch ${readmePath}`);
1843
+ } else {
1844
+ fs.writeFileSync(readmePath, nextReadme, 'utf8');
1845
+ }
1846
+ }
1847
+
1848
+ const packageJsonPath = path.join(projectRoot, 'package.json');
1849
+ if (!fs.existsSync(packageJsonPath)) {
1850
+ logError(`package.json not found in generated Vue app: ${packageJsonPath}`);
1851
+ process.exit(1);
1852
+ }
1853
+
1854
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
1855
+ packageJson.name = projectName;
1856
+ packageJson.private = true;
1857
+ packageJson.version = '0.1.0';
1858
+ packageJson.type = 'module';
1859
+ packageJson.scripts = {
1860
+ 'build:rsx': 'rsx build --project tsconfig.app.json --no-emit --prod',
1861
+ 'typecheck:rsx': 'rsx typecheck --project tsconfig.app.json',
1862
+ dev: 'npm run build:rsx && vite',
1863
+ build: 'npm run build:rsx && vue-tsc -b && vite build',
1864
+ preview: 'vite preview',
1865
+ };
1866
+ packageJson.rsx = {
1867
+ build: {
1868
+ preparse: true,
1869
+ preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
1870
+ compiled: true,
1871
+ compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
1872
+ compiledResolvedEvaluator: false,
1873
+ },
1874
+ };
1875
+ packageJson.dependencies = {
1876
+ vue: packageJson.dependencies?.vue ?? '^3.5.30',
1877
+ '@rs-x/core': rsxSpecs['@rs-x/core'],
1878
+ '@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
1879
+ '@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
1880
+ '@rs-x/vue': rsxSpecs['@rs-x/vue'],
1881
+ };
1882
+ packageJson.devDependencies = {
1883
+ ...(packageJson.devDependencies ?? {}),
1884
+ '@rs-x/cli': rsxSpecs['@rs-x/cli'],
1885
+ '@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
1886
+ '@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
1887
+ };
1888
+
1889
+ if (dryRun) {
1890
+ logInfo(`[dry-run] patch ${packageJsonPath}`);
1891
+ } else {
1892
+ fs.writeFileSync(
1893
+ packageJsonPath,
1894
+ `${JSON.stringify(packageJson, null, 2)}\n`,
1895
+ 'utf8',
1896
+ );
1897
+ }
1898
+
1899
+ const tsConfigAppPath = path.join(projectRoot, 'tsconfig.app.json');
1900
+ if (fs.existsSync(tsConfigAppPath)) {
1901
+ upsertTypescriptPluginInTsConfig(tsConfigAppPath, dryRun);
1902
+ ensureTsConfigIncludePattern(tsConfigAppPath, 'src/**/*.d.ts', dryRun);
1903
+ }
1904
+
1905
+ if (!Boolean(flags['skip-install'])) {
1906
+ logInfo(`Refreshing ${pm} dependencies for the RS-X Vue starter...`);
1907
+ run(pm, ['install'], { dryRun });
1908
+ logOk('Vue starter dependencies are up to date.');
1909
+ }
1910
+ }
1911
+
1912
+ function applyNextDemoStarter(projectRoot, projectName, pm, flags) {
1913
+ const dryRun = Boolean(flags['dry-run']);
1914
+ const tag = resolveInstallTag(flags);
1915
+ const tarballsDir =
1916
+ typeof flags['tarballs-dir'] === 'string'
1917
+ ? path.resolve(process.cwd(), flags['tarballs-dir'])
1918
+ : typeof process.env.RSX_TARBALLS_DIR === 'string' &&
1919
+ process.env.RSX_TARBALLS_DIR.trim().length > 0
1920
+ ? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
1921
+ : null;
1922
+ const workspaceRoot = findRepoRoot(projectRoot);
1923
+ const rsxSpecs = resolveProjectRsxSpecs(
1924
+ projectRoot,
1925
+ workspaceRoot,
1926
+ tarballsDir,
1927
+ { tag, includeReactPackage: true },
1928
+ );
1929
+
1930
+ const templateFiles = ['README.md', 'app', 'components', 'hooks', 'lib'];
1931
+ for (const entry of templateFiles) {
1932
+ copyPathWithDryRun(
1933
+ path.join(NEXT_DEMO_TEMPLATE_DIR, entry),
1934
+ path.join(projectRoot, entry),
1935
+ dryRun,
1936
+ );
1937
+ }
1938
+
1939
+ const readmePath = path.join(projectRoot, 'README.md');
1940
+ if (fs.existsSync(readmePath)) {
1941
+ const readmeSource = fs.readFileSync(readmePath, 'utf8');
1942
+ const nextReadme = readmeSource.replace(
1943
+ /^#\s+rsx-next-example/mu,
1944
+ `# ${projectName}`,
1945
+ );
1946
+ if (dryRun) {
1947
+ logInfo(`[dry-run] patch ${readmePath}`);
1948
+ } else {
1949
+ fs.writeFileSync(readmePath, nextReadme, 'utf8');
1950
+ }
1951
+ }
1952
+
1953
+ const publicDir = path.join(projectRoot, 'public');
1954
+ removeFileOrDirectoryWithDryRun(publicDir, dryRun);
1955
+
1956
+ const packageJsonPath = path.join(projectRoot, 'package.json');
1957
+ if (!fs.existsSync(packageJsonPath)) {
1958
+ logError(`package.json not found in generated Next.js app: ${packageJsonPath}`);
1959
+ process.exit(1);
1960
+ }
1961
+
1962
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
1963
+ packageJson.name = projectName;
1964
+ packageJson.private = true;
1965
+ packageJson.version = '0.1.0';
1966
+ packageJson.scripts = {
1967
+ ...packageJson.scripts,
1968
+ 'build:rsx': 'rsx build --project tsconfig.json --no-emit --prod',
1969
+ dev: 'npm run build:rsx && next dev',
1970
+ build: 'npm run build:rsx && next build',
1971
+ start: 'next start',
1972
+ };
1973
+ packageJson.rsx = {
1974
+ build: {
1975
+ preparse: true,
1976
+ preparseFile: 'app/rsx-generated/rsx-aot-preparsed.generated.ts',
1977
+ compiled: true,
1978
+ compiledFile: 'app/rsx-generated/rsx-aot-compiled.generated.ts',
1979
+ compiledResolvedEvaluator: false,
1980
+ },
1981
+ };
1982
+ packageJson.dependencies = {
1983
+ ...(packageJson.dependencies ?? {}),
1984
+ '@rs-x/core': rsxSpecs['@rs-x/core'],
1985
+ '@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
1986
+ '@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
1987
+ '@rs-x/react': rsxSpecs['@rs-x/react'],
1988
+ };
1989
+ packageJson.devDependencies = {
1990
+ ...(packageJson.devDependencies ?? {}),
1991
+ '@rs-x/cli': rsxSpecs['@rs-x/cli'],
1992
+ '@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
1993
+ '@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
1994
+ };
1995
+
1996
+ if (dryRun) {
1997
+ logInfo(`[dry-run] patch ${packageJsonPath}`);
1998
+ } else {
1999
+ fs.writeFileSync(
2000
+ packageJsonPath,
2001
+ `${JSON.stringify(packageJson, null, 2)}\n`,
2002
+ 'utf8',
2003
+ );
2004
+ }
2005
+
2006
+ const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
2007
+ if (fs.existsSync(tsConfigPath)) {
2008
+ upsertTypescriptPluginInTsConfig(tsConfigPath, dryRun);
2009
+ }
2010
+
2011
+ if (!Boolean(flags['skip-install'])) {
2012
+ logInfo(`Refreshing ${pm} dependencies for the RS-X Next.js starter...`);
2013
+ run(pm, ['install'], { dryRun });
2014
+ logOk('Next.js starter dependencies are up to date.');
2015
+ }
2016
+ }
2017
+
1684
2018
  async function runProjectWithTemplate(template, flags) {
1685
2019
  const normalizedTemplate = normalizeProjectTemplate(template);
1686
2020
  if (!normalizedTemplate) {
@@ -1720,15 +2054,11 @@ async function runProjectWithTemplate(template, flags) {
1720
2054
  return;
1721
2055
  }
1722
2056
  if (normalizedTemplate === 'nextjs') {
1723
- runSetupNext(flags);
2057
+ applyNextDemoStarter(projectRoot, projectName, pm, flags);
1724
2058
  return;
1725
2059
  }
1726
2060
  if (normalizedTemplate === 'vuejs') {
1727
- runSetupVue({
1728
- ...flags,
1729
- entry: flags.entry ?? 'src/main.ts',
1730
- });
1731
- applyVueRsxTemplate(projectRoot, dryRun);
2061
+ applyVueDemoStarter(projectRoot, projectName, pm, flags);
1732
2062
  }
1733
2063
  });
1734
2064
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rs-x/cli",
3
- "version": "2.0.0-next.15",
3
+ "version": "2.0.0-next.17",
4
4
  "description": "CLI for installing RS-X compiler tooling and VS Code integration",
5
5
  "bin": {
6
6
  "rsx": "./bin/rsx.cjs"
@@ -0,0 +1,26 @@
1
+ # rsx-next-example
2
+
3
+ Website & docs: https://www.rsxjs.com/
4
+
5
+ This starter shows how to use RS-X in a Next.js app-router project with a
6
+ million-row virtual table that keeps rendering and expression memory bounded.
7
+
8
+ ## Scripts
9
+
10
+ - `npm run dev` starts the Next.js dev server after running the RS-X build step
11
+ - `npm run build` generates RS-X artifacts and builds the production app
12
+ - `npm run start` starts the production server
13
+
14
+ ## Structure
15
+
16
+ - `app/` contains the Next.js route files and global styles
17
+ - `components/` contains the client-side UI components
18
+ - `hooks/` contains reusable React hooks
19
+ - `lib/` contains the RS-X bootstrap and virtual-table state/data utilities
20
+
21
+ ## Notes
22
+
23
+ - The demo defaults to dark mode.
24
+ - The UI uses `@rs-x/react` hooks in a Next.js client component tree.
25
+ - The generated RS-X cache files in `app/rsx-generated` are created by
26
+ `npm run build:rsx`; they are not checked into the starter template.