@rs-x/cli 2.0.0-next.20 → 2.0.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/rsx.cjs
CHANGED
|
@@ -211,6 +211,22 @@ function detectPackageManager(explicitPm) {
|
|
|
211
211
|
return 'npm';
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
function resolveCliPackageManager(projectRoot, explicitPm) {
|
|
215
|
+
if (explicitPm) {
|
|
216
|
+
return explicitPm;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const cliConfig = resolveRsxCliConfig(projectRoot);
|
|
220
|
+
if (
|
|
221
|
+
typeof cliConfig.packageManager === 'string' &&
|
|
222
|
+
['pnpm', 'npm', 'yarn', 'bun'].includes(cliConfig.packageManager)
|
|
223
|
+
) {
|
|
224
|
+
return cliConfig.packageManager;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return detectPackageManager(undefined);
|
|
228
|
+
}
|
|
229
|
+
|
|
214
230
|
function applyTagToPackages(packages, tag) {
|
|
215
231
|
return packages.map((pkg) => {
|
|
216
232
|
const lastAt = pkg.lastIndexOf('@');
|
|
@@ -249,6 +265,41 @@ function resolveInstallTag(flags) {
|
|
|
249
265
|
return undefined;
|
|
250
266
|
}
|
|
251
267
|
|
|
268
|
+
function resolveConfiguredInstallTag(projectRoot, flags) {
|
|
269
|
+
const explicitTag = resolveInstallTag(flags);
|
|
270
|
+
if (explicitTag) {
|
|
271
|
+
return explicitTag;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const cliConfig = resolveRsxCliConfig(projectRoot);
|
|
275
|
+
if (
|
|
276
|
+
typeof cliConfig.installTag === 'string' &&
|
|
277
|
+
['latest', 'next'].includes(cliConfig.installTag)
|
|
278
|
+
) {
|
|
279
|
+
return cliConfig.installTag;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveCliVerifyFlag(projectRoot, flags, sectionName) {
|
|
286
|
+
if (flags.verify !== undefined) {
|
|
287
|
+
return parseBooleanFlag(flags.verify, false);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const cliConfig = resolveRsxCliConfig(projectRoot);
|
|
291
|
+
const sectionConfig = cliConfig[sectionName];
|
|
292
|
+
if (
|
|
293
|
+
typeof sectionConfig === 'object' &&
|
|
294
|
+
sectionConfig &&
|
|
295
|
+
typeof sectionConfig.verify === 'boolean'
|
|
296
|
+
) {
|
|
297
|
+
return sectionConfig.verify;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
252
303
|
function installPackages(pm, packages, options = {}) {
|
|
253
304
|
const { dev = false, dryRun = false, label = 'packages', tag, cwd } = options;
|
|
254
305
|
const resolvedPackages = tag ? applyTagToPackages(packages, tag) : packages;
|
|
@@ -484,6 +535,13 @@ function findRepoRoot(startDir) {
|
|
|
484
535
|
function runDoctor() {
|
|
485
536
|
const nodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
486
537
|
const hasCode = hasCommand('code');
|
|
538
|
+
const availablePackageManagers = ['pnpm', 'npm', 'yarn', 'bun'].filter((pm) =>
|
|
539
|
+
hasCommand(pm),
|
|
540
|
+
);
|
|
541
|
+
const packageRoot = findNearestPackageRoot(process.cwd());
|
|
542
|
+
const duplicateRsxPackages = packageRoot
|
|
543
|
+
? findDuplicateRsxPackages(packageRoot)
|
|
544
|
+
: [];
|
|
487
545
|
const checks = [
|
|
488
546
|
{
|
|
489
547
|
name: 'Node.js >= 20',
|
|
@@ -497,12 +555,25 @@ function runDoctor() {
|
|
|
497
555
|
},
|
|
498
556
|
{
|
|
499
557
|
name: 'Package manager (pnpm/npm/yarn/bun)',
|
|
500
|
-
ok:
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
558
|
+
ok: availablePackageManagers.length > 0,
|
|
559
|
+
details:
|
|
560
|
+
availablePackageManagers.length > 0
|
|
561
|
+
? `available: ${availablePackageManagers.join(', ')}`
|
|
562
|
+
: 'required for compiler package installation',
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: 'Duplicate @rs-x package versions',
|
|
566
|
+
ok: duplicateRsxPackages.length === 0,
|
|
567
|
+
details: packageRoot
|
|
568
|
+
? duplicateRsxPackages.length === 0
|
|
569
|
+
? `not detected in ${path.relative(process.cwd(), packageRoot) || '.'}`
|
|
570
|
+
: duplicateRsxPackages
|
|
571
|
+
.map(
|
|
572
|
+
({ name, versions }) =>
|
|
573
|
+
`${name} (${Array.from(versions).join(', ')})`,
|
|
574
|
+
)
|
|
575
|
+
.join('; ')
|
|
576
|
+
: 'no package.json found in current directory tree',
|
|
506
577
|
},
|
|
507
578
|
];
|
|
508
579
|
|
|
@@ -510,6 +581,149 @@ function runDoctor() {
|
|
|
510
581
|
const tag = check.ok ? '[OK]' : '[WARN]';
|
|
511
582
|
console.log(`${tag} ${check.name} - ${check.details}`);
|
|
512
583
|
}
|
|
584
|
+
|
|
585
|
+
const failingChecks = checks.filter((check) => !check.ok);
|
|
586
|
+
if (failingChecks.length > 0) {
|
|
587
|
+
console.log('');
|
|
588
|
+
console.log('Suggested next steps:');
|
|
589
|
+
for (const check of failingChecks) {
|
|
590
|
+
if (check.name === 'Node.js >= 20') {
|
|
591
|
+
console.log(
|
|
592
|
+
' - Install Node.js 20 or newer before running `rsx setup` or `rsx project`.',
|
|
593
|
+
);
|
|
594
|
+
} else if (check.name === 'VS Code CLI (code)') {
|
|
595
|
+
console.log(
|
|
596
|
+
' - Install the VS Code shell command or use `rsx install vscode --force` later.',
|
|
597
|
+
);
|
|
598
|
+
} else if (check.name === 'Package manager (pnpm/npm/yarn/bun)') {
|
|
599
|
+
console.log(
|
|
600
|
+
' - Install npm, pnpm, yarn, or bun so the CLI can add RS-X packages.',
|
|
601
|
+
);
|
|
602
|
+
} else if (check.name === 'Duplicate @rs-x package versions') {
|
|
603
|
+
console.log(
|
|
604
|
+
' - Reinstall dependencies so all `@rs-x/*` packages resolve to a single version, then rerun `rsx doctor`.',
|
|
605
|
+
);
|
|
606
|
+
console.log(
|
|
607
|
+
' - If you are linking local packages, remove nested `node_modules` inside linked RS-X packages.',
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
console.log('');
|
|
615
|
+
console.log('Suggested next steps:');
|
|
616
|
+
console.log(
|
|
617
|
+
' - Run `rsx project <framework>` to scaffold a starter, or `rsx setup` inside an existing app.',
|
|
618
|
+
);
|
|
619
|
+
console.log(' - Use `rsx add` to create your first expression file.');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function findNearestPackageRoot(startDirectory) {
|
|
623
|
+
let currentDirectory = path.resolve(startDirectory);
|
|
624
|
+
while (true) {
|
|
625
|
+
if (fs.existsSync(path.join(currentDirectory, 'package.json'))) {
|
|
626
|
+
return currentDirectory;
|
|
627
|
+
}
|
|
628
|
+
const parentDirectory = path.dirname(currentDirectory);
|
|
629
|
+
if (parentDirectory === currentDirectory) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
currentDirectory = parentDirectory;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function readRsxPackageVersionsFromNodeModules(
|
|
637
|
+
nodeModulesDirectory,
|
|
638
|
+
versionsByPackage,
|
|
639
|
+
) {
|
|
640
|
+
const scopeDirectory = path.join(nodeModulesDirectory, '@rs-x');
|
|
641
|
+
if (!fs.existsSync(scopeDirectory)) {
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const packageDirectories = fs
|
|
646
|
+
.readdirSync(scopeDirectory, { withFileTypes: true })
|
|
647
|
+
.filter((entry) => entry.isDirectory())
|
|
648
|
+
.map((entry) => path.join(scopeDirectory, entry.name));
|
|
649
|
+
|
|
650
|
+
for (const packageDirectory of packageDirectories) {
|
|
651
|
+
const packageJsonPath = path.join(packageDirectory, 'package.json');
|
|
652
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
658
|
+
const packageName = packageJson.name;
|
|
659
|
+
const packageVersion = packageJson.version;
|
|
660
|
+
if (
|
|
661
|
+
typeof packageName === 'string' &&
|
|
662
|
+
typeof packageVersion === 'string'
|
|
663
|
+
) {
|
|
664
|
+
if (!versionsByPackage.has(packageName)) {
|
|
665
|
+
versionsByPackage.set(packageName, new Set());
|
|
666
|
+
}
|
|
667
|
+
versionsByPackage.get(packageName).add(packageVersion);
|
|
668
|
+
}
|
|
669
|
+
} catch {
|
|
670
|
+
// Ignore malformed package.json files during doctor output.
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return packageDirectories;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function collectNestedRsxPackageVersions(
|
|
678
|
+
packageDirectory,
|
|
679
|
+
versionsByPackage,
|
|
680
|
+
depth,
|
|
681
|
+
) {
|
|
682
|
+
if (depth <= 0) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const nestedNodeModulesDirectory = path.join(
|
|
687
|
+
packageDirectory,
|
|
688
|
+
'node_modules',
|
|
689
|
+
);
|
|
690
|
+
if (!fs.existsSync(nestedNodeModulesDirectory)) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const nestedPackageDirectories = readRsxPackageVersionsFromNodeModules(
|
|
695
|
+
nestedNodeModulesDirectory,
|
|
696
|
+
versionsByPackage,
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
for (const nestedPackageDirectory of nestedPackageDirectories) {
|
|
700
|
+
collectNestedRsxPackageVersions(
|
|
701
|
+
nestedPackageDirectory,
|
|
702
|
+
versionsByPackage,
|
|
703
|
+
depth - 1,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function findDuplicateRsxPackages(projectRoot) {
|
|
709
|
+
const rootNodeModulesDirectory = path.join(projectRoot, 'node_modules');
|
|
710
|
+
if (!fs.existsSync(rootNodeModulesDirectory)) {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const versionsByPackage = new Map();
|
|
715
|
+
const rootPackageDirectories = readRsxPackageVersionsFromNodeModules(
|
|
716
|
+
rootNodeModulesDirectory,
|
|
717
|
+
versionsByPackage,
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
for (const packageDirectory of rootPackageDirectories) {
|
|
721
|
+
collectNestedRsxPackageVersions(packageDirectory, versionsByPackage, 3);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return Array.from(versionsByPackage.entries())
|
|
725
|
+
.filter(([, versions]) => versions.size > 1)
|
|
726
|
+
.map(([name, versions]) => ({ name, versions }));
|
|
513
727
|
}
|
|
514
728
|
|
|
515
729
|
function isValidTsIdentifier(input) {
|
|
@@ -587,25 +801,311 @@ function stripTsLikeExtension(fileName) {
|
|
|
587
801
|
return fileName.replace(/\.[cm]?[jt]sx?$/u, '');
|
|
588
802
|
}
|
|
589
803
|
|
|
590
|
-
function
|
|
804
|
+
function inferModelKeysFromExpression(expressionSource) {
|
|
805
|
+
const matches = expressionSource.matchAll(
|
|
806
|
+
/(?<![\w$.])([A-Za-z_$][A-Za-z0-9_$]*)(?![\w$])/gu,
|
|
807
|
+
);
|
|
808
|
+
const identifiers = [];
|
|
809
|
+
const ignoredIdentifiers = new Set([
|
|
810
|
+
'undefined',
|
|
811
|
+
'null',
|
|
812
|
+
'true',
|
|
813
|
+
'false',
|
|
814
|
+
'Math',
|
|
815
|
+
'Date',
|
|
816
|
+
'Number',
|
|
817
|
+
'String',
|
|
818
|
+
'Boolean',
|
|
819
|
+
'Array',
|
|
820
|
+
'Object',
|
|
821
|
+
'JSON',
|
|
822
|
+
'console',
|
|
823
|
+
]);
|
|
824
|
+
|
|
825
|
+
for (const match of matches) {
|
|
826
|
+
const identifier = match[1];
|
|
827
|
+
if (
|
|
828
|
+
TS_RESERVED_WORDS.has(identifier) ||
|
|
829
|
+
ignoredIdentifiers.has(identifier)
|
|
830
|
+
) {
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (!identifiers.includes(identifier)) {
|
|
834
|
+
identifiers.push(identifier);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return identifiers;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function createModelTemplate(expressionSource) {
|
|
842
|
+
const modelKeys = inferModelKeysFromExpression(expressionSource);
|
|
843
|
+
const modelBody =
|
|
844
|
+
modelKeys.length > 0
|
|
845
|
+
? modelKeys.map((key) => ` ${key}: 1,`).join('\n')
|
|
846
|
+
: ' a: 1,';
|
|
847
|
+
|
|
591
848
|
return `export const model = {
|
|
592
|
-
|
|
849
|
+
${modelBody}
|
|
593
850
|
};
|
|
594
851
|
`;
|
|
595
852
|
}
|
|
596
853
|
|
|
854
|
+
function createInlineExpressionTemplate(expressionName, expressionSource) {
|
|
855
|
+
return `import { rsx } from '@rs-x/expression-parser';
|
|
856
|
+
|
|
857
|
+
${createModelTemplate(expressionSource).trim()}
|
|
858
|
+
|
|
859
|
+
export const ${expressionName} = rsx(${JSON.stringify(expressionSource)})(model);
|
|
860
|
+
`;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function createInlineExpressionAppendTemplate(
|
|
864
|
+
expressionName,
|
|
865
|
+
expressionSource,
|
|
866
|
+
fileContent,
|
|
867
|
+
) {
|
|
868
|
+
const hasRsxImport = fileContent.includes("from '@rs-x/expression-parser'");
|
|
869
|
+
const hasModelExport = /\bexport\s+const\s+model\s*=/u.test(fileContent);
|
|
870
|
+
const sections = [];
|
|
871
|
+
|
|
872
|
+
if (!hasRsxImport) {
|
|
873
|
+
sections.push("import { rsx } from '@rs-x/expression-parser';");
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (!hasModelExport) {
|
|
877
|
+
sections.push(createModelTemplate(expressionSource).trim());
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
sections.push(
|
|
881
|
+
`export const ${expressionName} = rsx(${JSON.stringify(expressionSource)})(model);`,
|
|
882
|
+
);
|
|
883
|
+
return `\n${sections.join('\n\n')}\n`;
|
|
884
|
+
}
|
|
885
|
+
|
|
597
886
|
function createExpressionTemplate(
|
|
598
887
|
expressionName,
|
|
888
|
+
expressionSource,
|
|
599
889
|
modelImportPath,
|
|
600
890
|
modelExportName,
|
|
601
891
|
) {
|
|
602
892
|
return `import { rsx } from '@rs-x/expression-parser';
|
|
603
893
|
import { ${modelExportName} } from '${modelImportPath}';
|
|
604
894
|
|
|
605
|
-
export const ${expressionName} = rsx(
|
|
895
|
+
export const ${expressionName} = rsx(${JSON.stringify(expressionSource)})(${modelExportName});
|
|
606
896
|
`;
|
|
607
897
|
}
|
|
608
898
|
|
|
899
|
+
function findExpressionFiles(searchRoot) {
|
|
900
|
+
const results = [];
|
|
901
|
+
const expressionPattern =
|
|
902
|
+
/\b(?:export\s+)?const\s+[A-Za-z_$][\w$]*\s*=\s*rsx\(/u;
|
|
903
|
+
|
|
904
|
+
function visit(currentPath) {
|
|
905
|
+
if (!fs.existsSync(currentPath)) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const stat = fs.statSync(currentPath);
|
|
910
|
+
if (stat.isDirectory()) {
|
|
911
|
+
const baseName = path.basename(currentPath);
|
|
912
|
+
if (
|
|
913
|
+
baseName === 'node_modules' ||
|
|
914
|
+
baseName === 'dist' ||
|
|
915
|
+
baseName === 'build' ||
|
|
916
|
+
baseName === '.git' ||
|
|
917
|
+
baseName === '.next' ||
|
|
918
|
+
baseName === 'coverage' ||
|
|
919
|
+
baseName === '.tests' ||
|
|
920
|
+
baseName === 'tmp'
|
|
921
|
+
) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
for (const entry of fs.readdirSync(currentPath)) {
|
|
926
|
+
visit(path.join(currentPath, entry));
|
|
927
|
+
}
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (!/\.[cm]?[jt]sx?$/u.test(currentPath)) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const content = fs.readFileSync(currentPath, 'utf8');
|
|
936
|
+
if (
|
|
937
|
+
content.includes("from '@rs-x/expression-parser'") &&
|
|
938
|
+
expressionPattern.test(content)
|
|
939
|
+
) {
|
|
940
|
+
results.push(currentPath);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
visit(searchRoot);
|
|
945
|
+
return results.sort((left, right) => left.localeCompare(right));
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function resolveRsxCliAddConfig(projectRoot) {
|
|
949
|
+
const cliConfig = resolveRsxCliConfig(projectRoot);
|
|
950
|
+
const addConfig = cliConfig.add ?? {};
|
|
951
|
+
const defaultSearchRoots = ['src', 'app', 'expressions'];
|
|
952
|
+
const configuredSearchRoots = Array.isArray(addConfig.searchRoots)
|
|
953
|
+
? addConfig.searchRoots.filter(
|
|
954
|
+
(value) => typeof value === 'string' && value.trim() !== '',
|
|
955
|
+
)
|
|
956
|
+
: defaultSearchRoots;
|
|
957
|
+
const defaultDirectory =
|
|
958
|
+
typeof addConfig.defaultDirectory === 'string' &&
|
|
959
|
+
addConfig.defaultDirectory.trim() !== ''
|
|
960
|
+
? addConfig.defaultDirectory.trim()
|
|
961
|
+
: 'src/expressions';
|
|
962
|
+
|
|
963
|
+
return {
|
|
964
|
+
defaultDirectory,
|
|
965
|
+
searchRoots: configuredSearchRoots,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function filterPreferredExpressionFiles(candidates, projectRoot, addConfig) {
|
|
970
|
+
const preferredRootPrefixes = addConfig.searchRoots
|
|
971
|
+
.map((rootPath) =>
|
|
972
|
+
path.isAbsolute(rootPath) ? rootPath : path.join(projectRoot, rootPath),
|
|
973
|
+
)
|
|
974
|
+
.map((value) => `${value.replace(/\\/gu, '/')}/`);
|
|
975
|
+
|
|
976
|
+
const preferredCandidates = candidates.filter((candidate) => {
|
|
977
|
+
const normalizedCandidate = candidate.replace(/\\/gu, '/');
|
|
978
|
+
return preferredRootPrefixes.some((prefix) =>
|
|
979
|
+
normalizedCandidate.startsWith(prefix),
|
|
980
|
+
);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
return preferredCandidates.length > 0 ? preferredCandidates : candidates;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function rankExpressionFiles(candidates, resolvedDirectory, projectRoot) {
|
|
987
|
+
const sourceRootPatterns = ['/src/', '/app/', '/expressions/'];
|
|
988
|
+
|
|
989
|
+
function score(filePath) {
|
|
990
|
+
const normalized = filePath.replace(/\\/gu, '/');
|
|
991
|
+
let value = 0;
|
|
992
|
+
|
|
993
|
+
if (normalized.startsWith(resolvedDirectory.replace(/\\/gu, '/'))) {
|
|
994
|
+
value += 1000;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (sourceRootPatterns.some((pattern) => normalized.includes(pattern))) {
|
|
998
|
+
value += 100;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const relativeSegments = path
|
|
1002
|
+
.relative(projectRoot, filePath)
|
|
1003
|
+
.replace(/\\/gu, '/')
|
|
1004
|
+
.split('/').length;
|
|
1005
|
+
value -= relativeSegments;
|
|
1006
|
+
return value;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
return [...candidates].sort((left, right) => {
|
|
1010
|
+
const scoreDifference = score(right) - score(left);
|
|
1011
|
+
if (scoreDifference !== 0) {
|
|
1012
|
+
return scoreDifference;
|
|
1013
|
+
}
|
|
1014
|
+
return left.localeCompare(right);
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
async function askForExistingExpressionFile(rl, resolvedDirectory) {
|
|
1019
|
+
const projectRoot = process.cwd();
|
|
1020
|
+
const addConfig = resolveRsxCliAddConfig(projectRoot);
|
|
1021
|
+
const directoryCandidates = filterPreferredExpressionFiles(
|
|
1022
|
+
findExpressionFiles(resolvedDirectory),
|
|
1023
|
+
projectRoot,
|
|
1024
|
+
addConfig,
|
|
1025
|
+
);
|
|
1026
|
+
const fallbackCandidates =
|
|
1027
|
+
directoryCandidates.length > 0
|
|
1028
|
+
? []
|
|
1029
|
+
: rankExpressionFiles(
|
|
1030
|
+
filterPreferredExpressionFiles(
|
|
1031
|
+
findExpressionFiles(projectRoot),
|
|
1032
|
+
projectRoot,
|
|
1033
|
+
addConfig,
|
|
1034
|
+
),
|
|
1035
|
+
resolvedDirectory,
|
|
1036
|
+
projectRoot,
|
|
1037
|
+
);
|
|
1038
|
+
const candidates =
|
|
1039
|
+
directoryCandidates.length > 0 ? directoryCandidates : fallbackCandidates;
|
|
1040
|
+
|
|
1041
|
+
if (candidates.length > 0) {
|
|
1042
|
+
console.log(
|
|
1043
|
+
directoryCandidates.length > 0
|
|
1044
|
+
? 'Existing expression files in the selected directory:'
|
|
1045
|
+
: 'Existing expression files in the current project:',
|
|
1046
|
+
);
|
|
1047
|
+
candidates.forEach((candidate, index) => {
|
|
1048
|
+
console.log(
|
|
1049
|
+
` ${index + 1}. ${path.relative(process.cwd(), candidate).replace(/\\/gu, '/')}`,
|
|
1050
|
+
);
|
|
1051
|
+
});
|
|
1052
|
+
console.log(' 0. Enter a custom path');
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
while (true) {
|
|
1056
|
+
const answer = (
|
|
1057
|
+
await rl.question(
|
|
1058
|
+
candidates.length > 0
|
|
1059
|
+
? 'Choose a file number or enter a file path: '
|
|
1060
|
+
: 'Existing file path (relative to output dir or absolute): ',
|
|
1061
|
+
)
|
|
1062
|
+
).trim();
|
|
1063
|
+
|
|
1064
|
+
if (!answer && candidates.length > 0) {
|
|
1065
|
+
logWarn('Please choose a file or enter a path.');
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (candidates.length > 0 && /^\d+$/u.test(answer)) {
|
|
1070
|
+
const selectedIndex = Number.parseInt(answer, 10);
|
|
1071
|
+
if (selectedIndex === 0) {
|
|
1072
|
+
const customPath = await askUntilNonEmpty(
|
|
1073
|
+
rl,
|
|
1074
|
+
'Existing file path (relative to output dir or absolute): ',
|
|
1075
|
+
);
|
|
1076
|
+
const resolvedPath = path.isAbsolute(customPath)
|
|
1077
|
+
? customPath
|
|
1078
|
+
: path.resolve(resolvedDirectory, customPath);
|
|
1079
|
+
|
|
1080
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
1081
|
+
logWarn(`File not found: ${resolvedPath}`);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return resolvedPath;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
if (selectedIndex >= 1 && selectedIndex <= candidates.length) {
|
|
1089
|
+
return candidates[selectedIndex - 1];
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
logWarn(`"${answer}" is not a valid file number.`);
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const resolvedPath = path.isAbsolute(answer)
|
|
1097
|
+
? answer
|
|
1098
|
+
: path.resolve(resolvedDirectory, answer);
|
|
1099
|
+
|
|
1100
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
1101
|
+
logWarn(`File not found: ${resolvedPath}`);
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
return resolvedPath;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
609
1109
|
async function askForIdentifierWithDefault(rl, prompt, defaultValue) {
|
|
610
1110
|
while (true) {
|
|
611
1111
|
const answer = (await rl.question(prompt)).trim();
|
|
@@ -621,6 +1121,64 @@ async function askForIdentifierWithDefault(rl, prompt, defaultValue) {
|
|
|
621
1121
|
}
|
|
622
1122
|
}
|
|
623
1123
|
|
|
1124
|
+
async function askForExpressionSource(rl) {
|
|
1125
|
+
while (true) {
|
|
1126
|
+
const answer = (
|
|
1127
|
+
await rl.question("Initial expression string ['a']: ")
|
|
1128
|
+
).trim();
|
|
1129
|
+
|
|
1130
|
+
if (!answer) {
|
|
1131
|
+
return 'a';
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
return answer;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
async function askForDirectoryPath(rl, defaultDirectory) {
|
|
1139
|
+
while (true) {
|
|
1140
|
+
const prompt = defaultDirectory
|
|
1141
|
+
? `Directory path (relative or absolute) [${defaultDirectory}]: `
|
|
1142
|
+
: 'Directory path (relative or absolute): ';
|
|
1143
|
+
const answer = (await rl.question(prompt)).trim();
|
|
1144
|
+
|
|
1145
|
+
if (answer) {
|
|
1146
|
+
return answer;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (defaultDirectory) {
|
|
1150
|
+
return defaultDirectory;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
logWarn('Please enter a directory path.');
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
async function askForAddMode(rl) {
|
|
1158
|
+
console.log('Choose add mode:');
|
|
1159
|
+
console.log(
|
|
1160
|
+
' 1. Create new expression file (model in same file) [Recommended]',
|
|
1161
|
+
);
|
|
1162
|
+
console.log(' 2. Create new expression file with separate model');
|
|
1163
|
+
console.log(' 3. Update an existing expression file');
|
|
1164
|
+
|
|
1165
|
+
while (true) {
|
|
1166
|
+
const answer = (await rl.question('Add mode [1]: ')).trim();
|
|
1167
|
+
|
|
1168
|
+
if (!answer || answer === '1') {
|
|
1169
|
+
return 'create-inline';
|
|
1170
|
+
}
|
|
1171
|
+
if (answer === '2') {
|
|
1172
|
+
return 'create-separate';
|
|
1173
|
+
}
|
|
1174
|
+
if (answer === '3') {
|
|
1175
|
+
return 'update-existing';
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
logWarn(`"${answer}" is not a valid add mode. Use 1, 2, or 3.`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
624
1182
|
async function runAdd() {
|
|
625
1183
|
const rl = readline.createInterface({
|
|
626
1184
|
input: process.stdin,
|
|
@@ -628,18 +1186,21 @@ async function runAdd() {
|
|
|
628
1186
|
});
|
|
629
1187
|
|
|
630
1188
|
try {
|
|
1189
|
+
const projectRoot = process.cwd();
|
|
1190
|
+
const addConfig = resolveRsxCliAddConfig(projectRoot);
|
|
631
1191
|
const expressionName = await askUntilValidIdentifier(rl);
|
|
1192
|
+
const expressionSource = await askForExpressionSource(rl);
|
|
632
1193
|
|
|
633
1194
|
const kebabAnswer = await rl.question('Use kebab-case file name? [Y/n]: ');
|
|
634
1195
|
const useKebabCase = normalizeYesNo(kebabAnswer, true);
|
|
635
1196
|
|
|
636
|
-
const directoryInput = await
|
|
1197
|
+
const directoryInput = await askForDirectoryPath(
|
|
637
1198
|
rl,
|
|
638
|
-
|
|
1199
|
+
addConfig.defaultDirectory,
|
|
639
1200
|
);
|
|
640
1201
|
const resolvedDirectory = path.isAbsolute(directoryInput)
|
|
641
1202
|
? directoryInput
|
|
642
|
-
: path.resolve(
|
|
1203
|
+
: path.resolve(projectRoot, directoryInput);
|
|
643
1204
|
|
|
644
1205
|
const baseFileName = useKebabCase
|
|
645
1206
|
? toKebabCase(expressionName)
|
|
@@ -649,14 +1210,93 @@ async function runAdd() {
|
|
|
649
1210
|
const modelFileName = `${expressionFileBase}.model.ts`;
|
|
650
1211
|
const expressionPath = path.join(resolvedDirectory, expressionFileName);
|
|
651
1212
|
const modelPath = path.join(resolvedDirectory, modelFileName);
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1213
|
+
const addMode = await askForAddMode(rl);
|
|
1214
|
+
const updateExistingFile = addMode === 'update-existing';
|
|
1215
|
+
const keepModelInSameFile =
|
|
1216
|
+
addMode === 'create-inline'
|
|
1217
|
+
? true
|
|
1218
|
+
: addMode === 'create-separate'
|
|
1219
|
+
? false
|
|
1220
|
+
: normalizeYesNo(
|
|
1221
|
+
await rl.question('Keep model in the same file? [Y/n]: '),
|
|
1222
|
+
true,
|
|
1223
|
+
);
|
|
1224
|
+
|
|
1225
|
+
if (updateExistingFile) {
|
|
1226
|
+
const resolvedExistingFilePath = await askForExistingExpressionFile(
|
|
1227
|
+
rl,
|
|
1228
|
+
resolvedDirectory,
|
|
1229
|
+
);
|
|
1230
|
+
const existingFileContent = fs.readFileSync(
|
|
1231
|
+
resolvedExistingFilePath,
|
|
1232
|
+
'utf8',
|
|
1233
|
+
);
|
|
1234
|
+
let appendContent;
|
|
1235
|
+
|
|
1236
|
+
if (keepModelInSameFile) {
|
|
1237
|
+
appendContent = createInlineExpressionAppendTemplate(
|
|
1238
|
+
expressionName,
|
|
1239
|
+
expressionSource,
|
|
1240
|
+
existingFileContent,
|
|
1241
|
+
);
|
|
1242
|
+
} else {
|
|
1243
|
+
const useExistingModelAnswer = await rl.question(
|
|
1244
|
+
'Use existing model file? [y/N]: ',
|
|
1245
|
+
);
|
|
1246
|
+
const useExistingModel = normalizeYesNo(useExistingModelAnswer, false);
|
|
1247
|
+
let modelImportPath = './model';
|
|
1248
|
+
let modelExportName = 'model';
|
|
1249
|
+
|
|
1250
|
+
if (useExistingModel) {
|
|
1251
|
+
const existingModelPathInput = await askUntilNonEmpty(
|
|
1252
|
+
rl,
|
|
1253
|
+
'Existing model file path (relative to output dir or absolute): ',
|
|
1254
|
+
);
|
|
1255
|
+
const resolvedExistingModelPath = path.isAbsolute(
|
|
1256
|
+
existingModelPathInput,
|
|
1257
|
+
)
|
|
1258
|
+
? existingModelPathInput
|
|
1259
|
+
: path.resolve(resolvedDirectory, existingModelPathInput);
|
|
1260
|
+
|
|
1261
|
+
if (!fs.existsSync(resolvedExistingModelPath)) {
|
|
1262
|
+
logError(`Model file not found: ${resolvedExistingModelPath}`);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
modelImportPath = toModuleImportPath(
|
|
1267
|
+
resolvedExistingFilePath,
|
|
1268
|
+
resolvedExistingModelPath,
|
|
1269
|
+
);
|
|
1270
|
+
modelExportName = await askForIdentifierWithDefault(
|
|
1271
|
+
rl,
|
|
1272
|
+
'Model export name [model]: ',
|
|
1273
|
+
'model',
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
appendContent = `\n${createExpressionTemplate(
|
|
1278
|
+
expressionName,
|
|
1279
|
+
expressionSource,
|
|
1280
|
+
modelImportPath,
|
|
1281
|
+
modelExportName,
|
|
1282
|
+
)}`;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
fs.appendFileSync(resolvedExistingFilePath, appendContent, 'utf8');
|
|
1286
|
+
logOk(`Updated ${resolvedExistingFilePath}`);
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const useExistingModel = !keepModelInSameFile
|
|
1291
|
+
? normalizeYesNo(
|
|
1292
|
+
await rl.question('Use existing model file? [y/N]: '),
|
|
1293
|
+
false,
|
|
1294
|
+
)
|
|
1295
|
+
: false;
|
|
656
1296
|
|
|
657
1297
|
if (
|
|
658
1298
|
fs.existsSync(expressionPath) ||
|
|
659
|
-
(!useExistingModel && fs.existsSync(modelPath))
|
|
1299
|
+
(!keepModelInSameFile && !useExistingModel && fs.existsSync(modelPath))
|
|
660
1300
|
) {
|
|
661
1301
|
const overwriteAnswer = await rl.question(
|
|
662
1302
|
`One or more target files already exist. Overwrite? [y/N]: `,
|
|
@@ -672,6 +1312,16 @@ async function runAdd() {
|
|
|
672
1312
|
let modelImportPath = `./${expressionFileBase}.model`;
|
|
673
1313
|
let modelExportName = 'model';
|
|
674
1314
|
|
|
1315
|
+
if (keepModelInSameFile) {
|
|
1316
|
+
fs.writeFileSync(
|
|
1317
|
+
expressionPath,
|
|
1318
|
+
createInlineExpressionTemplate(expressionName, expressionSource),
|
|
1319
|
+
'utf8',
|
|
1320
|
+
);
|
|
1321
|
+
logOk(`Created ${expressionPath}`);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
675
1325
|
if (useExistingModel) {
|
|
676
1326
|
const existingModelPathInput = await askUntilNonEmpty(
|
|
677
1327
|
rl,
|
|
@@ -696,7 +1346,11 @@ async function runAdd() {
|
|
|
696
1346
|
'model',
|
|
697
1347
|
);
|
|
698
1348
|
} else {
|
|
699
|
-
fs.writeFileSync(
|
|
1349
|
+
fs.writeFileSync(
|
|
1350
|
+
modelPath,
|
|
1351
|
+
createModelTemplate(expressionSource),
|
|
1352
|
+
'utf8',
|
|
1353
|
+
);
|
|
700
1354
|
logOk(`Created ${modelPath}`);
|
|
701
1355
|
}
|
|
702
1356
|
|
|
@@ -704,6 +1358,7 @@ async function runAdd() {
|
|
|
704
1358
|
expressionPath,
|
|
705
1359
|
createExpressionTemplate(
|
|
706
1360
|
expressionName,
|
|
1361
|
+
expressionSource,
|
|
707
1362
|
modelImportPath,
|
|
708
1363
|
modelExportName,
|
|
709
1364
|
),
|
|
@@ -833,7 +1488,7 @@ function removeFileOrDirectoryWithDryRun(targetPath, dryRun) {
|
|
|
833
1488
|
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
834
1489
|
}
|
|
835
1490
|
|
|
836
|
-
function
|
|
1491
|
+
function resolveProjectTsConfig(projectRoot) {
|
|
837
1492
|
const appTsConfigPath = path.join(projectRoot, 'tsconfig.app.json');
|
|
838
1493
|
if (fs.existsSync(appTsConfigPath)) {
|
|
839
1494
|
return appTsConfigPath;
|
|
@@ -842,6 +1497,10 @@ function resolveAngularProjectTsConfig(projectRoot) {
|
|
|
842
1497
|
return path.join(projectRoot, 'tsconfig.json');
|
|
843
1498
|
}
|
|
844
1499
|
|
|
1500
|
+
function resolveAngularProjectTsConfig(projectRoot) {
|
|
1501
|
+
return resolveProjectTsConfig(projectRoot);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
845
1504
|
function upsertTypescriptPluginInTsConfig(configPath, dryRun) {
|
|
846
1505
|
if (!fs.existsSync(configPath)) {
|
|
847
1506
|
logWarn(`TypeScript config not found: ${configPath}`);
|
|
@@ -1333,8 +1992,9 @@ function applyVueRsxTemplate(projectRoot, dryRun) {
|
|
|
1333
1992
|
async function runProject(flags) {
|
|
1334
1993
|
const dryRun = Boolean(flags['dry-run']);
|
|
1335
1994
|
const skipInstall = Boolean(flags['skip-install']);
|
|
1336
|
-
const
|
|
1337
|
-
const
|
|
1995
|
+
const invocationRoot = process.cwd();
|
|
1996
|
+
const pm = resolveCliPackageManager(invocationRoot, flags.pm);
|
|
1997
|
+
const tag = resolveConfiguredInstallTag(invocationRoot, flags);
|
|
1338
1998
|
let projectName = typeof flags.name === 'string' ? flags.name.trim() : '';
|
|
1339
1999
|
|
|
1340
2000
|
if (!projectName) {
|
|
@@ -1613,6 +2273,7 @@ function scaffoldProjectTemplate(
|
|
|
1613
2273
|
|
|
1614
2274
|
function applyAngularDemoStarter(projectRoot, projectName, pm, flags) {
|
|
1615
2275
|
const dryRun = Boolean(flags['dry-run']);
|
|
2276
|
+
ensureRsxConfigFile(projectRoot, 'angular', dryRun);
|
|
1616
2277
|
const tag = resolveInstallTag(flags);
|
|
1617
2278
|
const tarballsDir =
|
|
1618
2279
|
typeof flags['tarballs-dir'] === 'string'
|
|
@@ -1687,16 +2348,6 @@ function applyAngularDemoStarter(projectRoot, projectName, pm, flags) {
|
|
|
1687
2348
|
start: 'npm run build:rsx && ng serve',
|
|
1688
2349
|
build: 'ng build',
|
|
1689
2350
|
};
|
|
1690
|
-
packageJson.rsx = {
|
|
1691
|
-
build: {
|
|
1692
|
-
preparse: true,
|
|
1693
|
-
preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
1694
|
-
compiled: true,
|
|
1695
|
-
compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
1696
|
-
registrationFile: 'src/rsx-generated/rsx-aot-registration.generated.ts',
|
|
1697
|
-
compiledResolvedEvaluator: false,
|
|
1698
|
-
},
|
|
1699
|
-
};
|
|
1700
2351
|
packageJson.dependencies = {
|
|
1701
2352
|
...(packageJson.dependencies ?? {}),
|
|
1702
2353
|
'@rs-x/angular': rsxSpecs['@rs-x/angular'],
|
|
@@ -1790,6 +2441,7 @@ function applyAngularDemoStarter(projectRoot, projectName, pm, flags) {
|
|
|
1790
2441
|
|
|
1791
2442
|
function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
|
|
1792
2443
|
const dryRun = Boolean(flags['dry-run']);
|
|
2444
|
+
ensureRsxConfigFile(projectRoot, 'react', dryRun);
|
|
1793
2445
|
const tag = resolveInstallTag(flags);
|
|
1794
2446
|
const tarballsDir =
|
|
1795
2447
|
typeof flags['tarballs-dir'] === 'string'
|
|
@@ -1870,15 +2522,6 @@ function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
|
|
|
1870
2522
|
build: 'npm run build:rsx && vite build',
|
|
1871
2523
|
preview: 'vite preview',
|
|
1872
2524
|
};
|
|
1873
|
-
packageJson.rsx = {
|
|
1874
|
-
build: {
|
|
1875
|
-
preparse: true,
|
|
1876
|
-
preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
1877
|
-
compiled: true,
|
|
1878
|
-
compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
1879
|
-
compiledResolvedEvaluator: false,
|
|
1880
|
-
},
|
|
1881
|
-
};
|
|
1882
2525
|
packageJson.dependencies = {
|
|
1883
2526
|
react: packageJson.dependencies?.react ?? '^19.2.4',
|
|
1884
2527
|
'react-dom': packageJson.dependencies?.['react-dom'] ?? '^19.2.4',
|
|
@@ -1924,6 +2567,7 @@ function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
|
|
|
1924
2567
|
|
|
1925
2568
|
function applyVueDemoStarter(projectRoot, projectName, pm, flags) {
|
|
1926
2569
|
const dryRun = Boolean(flags['dry-run']);
|
|
2570
|
+
ensureRsxConfigFile(projectRoot, 'vuejs', dryRun);
|
|
1927
2571
|
const tag = resolveInstallTag(flags);
|
|
1928
2572
|
const tarballsDir =
|
|
1929
2573
|
typeof flags['tarballs-dir'] === 'string'
|
|
@@ -1990,15 +2634,6 @@ function applyVueDemoStarter(projectRoot, projectName, pm, flags) {
|
|
|
1990
2634
|
build: 'npm run build:rsx && vue-tsc -b && vite build',
|
|
1991
2635
|
preview: 'vite preview',
|
|
1992
2636
|
};
|
|
1993
|
-
packageJson.rsx = {
|
|
1994
|
-
build: {
|
|
1995
|
-
preparse: true,
|
|
1996
|
-
preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
1997
|
-
compiled: true,
|
|
1998
|
-
compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
1999
|
-
compiledResolvedEvaluator: false,
|
|
2000
|
-
},
|
|
2001
|
-
};
|
|
2002
2637
|
packageJson.dependencies = {
|
|
2003
2638
|
vue: packageJson.dependencies?.vue ?? '^3.5.30',
|
|
2004
2639
|
'@rs-x/core': rsxSpecs['@rs-x/core'],
|
|
@@ -2038,6 +2673,7 @@ function applyVueDemoStarter(projectRoot, projectName, pm, flags) {
|
|
|
2038
2673
|
|
|
2039
2674
|
function applyNextDemoStarter(projectRoot, projectName, pm, flags) {
|
|
2040
2675
|
const dryRun = Boolean(flags['dry-run']);
|
|
2676
|
+
ensureRsxConfigFile(projectRoot, 'nextjs', dryRun);
|
|
2041
2677
|
const tag = resolveInstallTag(flags);
|
|
2042
2678
|
const tarballsDir =
|
|
2043
2679
|
typeof flags['tarballs-dir'] === 'string'
|
|
@@ -2099,15 +2735,6 @@ function applyNextDemoStarter(projectRoot, projectName, pm, flags) {
|
|
|
2099
2735
|
build: 'npm run build:rsx && next build',
|
|
2100
2736
|
start: 'next start',
|
|
2101
2737
|
};
|
|
2102
|
-
packageJson.rsx = {
|
|
2103
|
-
build: {
|
|
2104
|
-
preparse: true,
|
|
2105
|
-
preparseFile: 'app/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
2106
|
-
compiled: true,
|
|
2107
|
-
compiledFile: 'app/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
2108
|
-
compiledResolvedEvaluator: false,
|
|
2109
|
-
},
|
|
2110
|
-
};
|
|
2111
2738
|
packageJson.dependencies = {
|
|
2112
2739
|
...(packageJson.dependencies ?? {}),
|
|
2113
2740
|
'@rs-x/core': rsxSpecs['@rs-x/core'],
|
|
@@ -2158,7 +2785,8 @@ async function runProjectWithTemplate(template, flags) {
|
|
|
2158
2785
|
return;
|
|
2159
2786
|
}
|
|
2160
2787
|
|
|
2161
|
-
const
|
|
2788
|
+
const invocationRoot = process.cwd();
|
|
2789
|
+
const pm = resolveCliPackageManager(invocationRoot, flags.pm);
|
|
2162
2790
|
const projectName = await resolveProjectName(flags.name, flags._nameHint);
|
|
2163
2791
|
const projectRoot = resolveProjectRoot(projectName, flags);
|
|
2164
2792
|
if (fs.existsSync(projectRoot) && fs.readdirSync(projectRoot).length > 0) {
|
|
@@ -2197,17 +2825,205 @@ async function runProjectWithTemplate(template, flags) {
|
|
|
2197
2825
|
}
|
|
2198
2826
|
});
|
|
2199
2827
|
|
|
2828
|
+
verifyGeneratedProject(projectRoot, normalizedTemplate);
|
|
2829
|
+
if (resolveCliVerifyFlag(invocationRoot, flags, 'project')) {
|
|
2830
|
+
logInfo('Re-running starter verification (--verify)...');
|
|
2831
|
+
verifyGeneratedProject(projectRoot, normalizedTemplate);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2200
2834
|
logOk(`Created RS-X ${normalizedTemplate} project: ${projectRoot}`);
|
|
2201
2835
|
logInfo('Next steps:');
|
|
2202
2836
|
console.log(` cd ${projectName}`);
|
|
2203
2837
|
if (Boolean(flags['skip-install'])) {
|
|
2204
2838
|
console.log(` ${pm} install`);
|
|
2205
2839
|
}
|
|
2206
|
-
if (normalizedTemplate === 'angular') {
|
|
2207
|
-
console.log(` ${pm} run start`);
|
|
2208
|
-
} else {
|
|
2209
|
-
console.log(` ${pm} run dev`);
|
|
2840
|
+
if (normalizedTemplate === 'angular') {
|
|
2841
|
+
console.log(` ${pm} run start`);
|
|
2842
|
+
} else {
|
|
2843
|
+
console.log(` ${pm} run dev`);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
function verifyGeneratedProject(projectRoot, template) {
|
|
2848
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2849
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2850
|
+
logError(`Generated project is missing package.json: ${packageJsonPath}`);
|
|
2851
|
+
process.exit(1);
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2855
|
+
const scripts = packageJson.scripts ?? {};
|
|
2856
|
+
const dependencies = {
|
|
2857
|
+
...(packageJson.dependencies ?? {}),
|
|
2858
|
+
...(packageJson.devDependencies ?? {}),
|
|
2859
|
+
};
|
|
2860
|
+
|
|
2861
|
+
const expectationsByTemplate = {
|
|
2862
|
+
angular: {
|
|
2863
|
+
scripts: ['build:rsx', 'start'],
|
|
2864
|
+
dependencies: [
|
|
2865
|
+
'@rs-x/angular',
|
|
2866
|
+
'@rs-x/compiler',
|
|
2867
|
+
'@rs-x/typescript-plugin',
|
|
2868
|
+
],
|
|
2869
|
+
files: [
|
|
2870
|
+
'src/main.ts',
|
|
2871
|
+
'src/app/app.component.ts',
|
|
2872
|
+
'src/app/virtual-table/virtual-table.component.ts',
|
|
2873
|
+
],
|
|
2874
|
+
},
|
|
2875
|
+
react: {
|
|
2876
|
+
scripts: ['build:rsx', 'dev', 'build'],
|
|
2877
|
+
dependencies: [
|
|
2878
|
+
'@rs-x/react',
|
|
2879
|
+
'@rs-x/compiler',
|
|
2880
|
+
'@rs-x/typescript-plugin',
|
|
2881
|
+
],
|
|
2882
|
+
files: [
|
|
2883
|
+
'src/main.tsx',
|
|
2884
|
+
'src/rsx-bootstrap.ts',
|
|
2885
|
+
'src/app/app.tsx',
|
|
2886
|
+
'src/app/virtual-table/virtual-table-shell.tsx',
|
|
2887
|
+
],
|
|
2888
|
+
},
|
|
2889
|
+
vuejs: {
|
|
2890
|
+
scripts: ['build:rsx', 'dev', 'build'],
|
|
2891
|
+
dependencies: ['@rs-x/vue', '@rs-x/compiler', '@rs-x/typescript-plugin'],
|
|
2892
|
+
files: [
|
|
2893
|
+
'src/main.ts',
|
|
2894
|
+
'src/App.vue',
|
|
2895
|
+
'src/lib/rsx-bootstrap.ts',
|
|
2896
|
+
'src/components/VirtualTableShell.vue',
|
|
2897
|
+
],
|
|
2898
|
+
},
|
|
2899
|
+
nextjs: {
|
|
2900
|
+
scripts: ['build:rsx', 'dev', 'build'],
|
|
2901
|
+
dependencies: [
|
|
2902
|
+
'@rs-x/react',
|
|
2903
|
+
'@rs-x/compiler',
|
|
2904
|
+
'@rs-x/typescript-plugin',
|
|
2905
|
+
],
|
|
2906
|
+
files: [
|
|
2907
|
+
'app/layout.tsx',
|
|
2908
|
+
'app/page.tsx',
|
|
2909
|
+
'components/demo-app.tsx',
|
|
2910
|
+
'lib/rsx-bootstrap.ts',
|
|
2911
|
+
],
|
|
2912
|
+
},
|
|
2913
|
+
};
|
|
2914
|
+
|
|
2915
|
+
const expected = expectationsByTemplate[template];
|
|
2916
|
+
if (!expected) {
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
for (const scriptName of expected.scripts) {
|
|
2921
|
+
if (
|
|
2922
|
+
typeof scripts[scriptName] !== 'string' ||
|
|
2923
|
+
scripts[scriptName].trim() === ''
|
|
2924
|
+
) {
|
|
2925
|
+
logError(
|
|
2926
|
+
`Generated ${template} project is missing script "${scriptName}" in package.json.`,
|
|
2927
|
+
);
|
|
2928
|
+
process.exit(1);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
for (const dependencyName of expected.dependencies) {
|
|
2933
|
+
if (typeof dependencies[dependencyName] !== 'string') {
|
|
2934
|
+
logError(
|
|
2935
|
+
`Generated ${template} project is missing dependency "${dependencyName}" in package.json.`,
|
|
2936
|
+
);
|
|
2937
|
+
process.exit(1);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
for (const relativeFilePath of expected.files) {
|
|
2942
|
+
const absoluteFilePath = path.join(projectRoot, relativeFilePath);
|
|
2943
|
+
if (!fs.existsSync(absoluteFilePath)) {
|
|
2944
|
+
logError(
|
|
2945
|
+
`Generated ${template} project is missing expected file: ${absoluteFilePath}`,
|
|
2946
|
+
);
|
|
2947
|
+
process.exit(1);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
const rsxConfigPath = path.join(projectRoot, 'rsx.config.json');
|
|
2952
|
+
if (!fs.existsSync(rsxConfigPath)) {
|
|
2953
|
+
logError(
|
|
2954
|
+
`Generated ${template} project is missing expected file: ${rsxConfigPath}`,
|
|
2955
|
+
);
|
|
2956
|
+
process.exit(1);
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
logOk(`Verified generated ${template} project structure.`);
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
function verifySetupOutput(projectRoot, template) {
|
|
2963
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2964
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2965
|
+
logError(`Project is missing package.json: ${packageJsonPath}`);
|
|
2966
|
+
process.exit(1);
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2970
|
+
const scripts = packageJson.scripts ?? {};
|
|
2971
|
+
|
|
2972
|
+
const setupExpectations = {
|
|
2973
|
+
react: {
|
|
2974
|
+
scripts: ['build:rsx', 'typecheck:rsx', 'dev', 'build'],
|
|
2975
|
+
files: ['vite.config.ts', 'src/rsx-bootstrap.ts'],
|
|
2976
|
+
},
|
|
2977
|
+
vuejs: {
|
|
2978
|
+
scripts: ['build:rsx', 'typecheck:rsx', 'dev', 'build'],
|
|
2979
|
+
files: ['vite.config.ts', 'src/rsx-bootstrap.ts'],
|
|
2980
|
+
},
|
|
2981
|
+
next: {
|
|
2982
|
+
scripts: ['build:rsx', 'typecheck:rsx', 'dev', 'build'],
|
|
2983
|
+
files: [
|
|
2984
|
+
'next.config.js',
|
|
2985
|
+
'rsx-webpack-loader.cjs',
|
|
2986
|
+
'app/rsx-bootstrap.ts',
|
|
2987
|
+
],
|
|
2988
|
+
},
|
|
2989
|
+
angular: {
|
|
2990
|
+
scripts: ['build:rsx', 'typecheck:rsx', 'prebuild', 'start'],
|
|
2991
|
+
files: ['src/main.ts', 'angular.json'],
|
|
2992
|
+
},
|
|
2993
|
+
};
|
|
2994
|
+
|
|
2995
|
+
const expected = setupExpectations[template];
|
|
2996
|
+
if (!expected) {
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
for (const scriptName of expected.scripts) {
|
|
3001
|
+
if (
|
|
3002
|
+
typeof scripts[scriptName] !== 'string' ||
|
|
3003
|
+
scripts[scriptName].trim() === ''
|
|
3004
|
+
) {
|
|
3005
|
+
logError(
|
|
3006
|
+
`Setup output is missing script "${scriptName}" in package.json.`,
|
|
3007
|
+
);
|
|
3008
|
+
process.exit(1);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
for (const relativeFilePath of expected.files) {
|
|
3013
|
+
const absoluteFilePath = path.join(projectRoot, relativeFilePath);
|
|
3014
|
+
if (!fs.existsSync(absoluteFilePath)) {
|
|
3015
|
+
logError(`Setup output is missing expected file: ${absoluteFilePath}`);
|
|
3016
|
+
process.exit(1);
|
|
3017
|
+
}
|
|
2210
3018
|
}
|
|
3019
|
+
|
|
3020
|
+
const rsxConfigPath = path.join(projectRoot, 'rsx.config.json');
|
|
3021
|
+
if (!fs.existsSync(rsxConfigPath)) {
|
|
3022
|
+
logError(`Setup output is missing expected file: ${rsxConfigPath}`);
|
|
3023
|
+
process.exit(1);
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
logOk(`Verified ${template} setup output.`);
|
|
2211
3027
|
}
|
|
2212
3028
|
|
|
2213
3029
|
function detectProjectContext(projectRoot) {
|
|
@@ -2687,11 +3503,10 @@ function patchEntryFileForRsx(entryFile, bootstrapFile, context, dryRun) {
|
|
|
2687
3503
|
|
|
2688
3504
|
function runInit(flags) {
|
|
2689
3505
|
const dryRun = Boolean(flags['dry-run']);
|
|
2690
|
-
const skipVscode = Boolean(flags['skip-vscode']);
|
|
2691
3506
|
const skipInstall = Boolean(flags['skip-install']);
|
|
2692
|
-
const pm = detectPackageManager(flags.pm);
|
|
2693
|
-
const tag = resolveInstallTag(flags);
|
|
2694
3507
|
const projectRoot = process.cwd();
|
|
3508
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
3509
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
2695
3510
|
|
|
2696
3511
|
if (!skipInstall) {
|
|
2697
3512
|
installRuntimePackages(pm, dryRun, tag, projectRoot, flags);
|
|
@@ -2749,48 +3564,9 @@ function runInit(flags) {
|
|
|
2749
3564
|
}
|
|
2750
3565
|
}
|
|
2751
3566
|
|
|
2752
|
-
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2755
|
-
function upsertRsxBuildConfigInPackageJson(projectRoot, dryRun) {
|
|
2756
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
2757
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
2758
|
-
return false;
|
|
2759
|
-
}
|
|
2760
|
-
|
|
2761
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
2762
|
-
const currentRsx = packageJson.rsx ?? {};
|
|
2763
|
-
const currentBuild = currentRsx.build ?? {};
|
|
2764
|
-
const nextBuild = {
|
|
2765
|
-
preparse: true,
|
|
2766
|
-
preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
2767
|
-
compiled: true,
|
|
2768
|
-
compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
2769
|
-
registrationFile: 'src/rsx-generated/rsx-aot-registration.generated.ts',
|
|
2770
|
-
compiledResolvedEvaluator: false,
|
|
2771
|
-
...currentBuild,
|
|
2772
|
-
};
|
|
2773
|
-
|
|
2774
|
-
const nextPackageJson = {
|
|
2775
|
-
...packageJson,
|
|
2776
|
-
rsx: {
|
|
2777
|
-
...currentRsx,
|
|
2778
|
-
build: nextBuild,
|
|
2779
|
-
},
|
|
2780
|
-
};
|
|
2781
|
-
|
|
2782
|
-
if (dryRun) {
|
|
2783
|
-
logInfo(`[dry-run] patch ${packageJsonPath} (rsx.build)`);
|
|
2784
|
-
return true;
|
|
2785
|
-
}
|
|
3567
|
+
ensureRsxConfigFile(projectRoot, effectiveContext, dryRun);
|
|
2786
3568
|
|
|
2787
|
-
|
|
2788
|
-
packageJsonPath,
|
|
2789
|
-
`${JSON.stringify(nextPackageJson, null, 2)}\n`,
|
|
2790
|
-
'utf8',
|
|
2791
|
-
);
|
|
2792
|
-
logOk(`Patched ${packageJsonPath} (rsx.build)`);
|
|
2793
|
-
return true;
|
|
3569
|
+
logOk('RS-X init completed.');
|
|
2794
3570
|
}
|
|
2795
3571
|
|
|
2796
3572
|
function ensureAngularProvidersInEntry(entryFile, dryRun) {
|
|
@@ -2799,10 +3575,6 @@ function ensureAngularProvidersInEntry(entryFile, dryRun) {
|
|
|
2799
3575
|
}
|
|
2800
3576
|
|
|
2801
3577
|
const original = fs.readFileSync(entryFile, 'utf8');
|
|
2802
|
-
if (original.includes('providexRsx')) {
|
|
2803
|
-
logInfo(`Angular entry already includes providexRsx: ${entryFile}`);
|
|
2804
|
-
return true;
|
|
2805
|
-
}
|
|
2806
3578
|
|
|
2807
3579
|
if (!original.includes('bootstrapApplication(')) {
|
|
2808
3580
|
logWarn(
|
|
@@ -2814,6 +3586,109 @@ function ensureAngularProvidersInEntry(entryFile, dryRun) {
|
|
|
2814
3586
|
return false;
|
|
2815
3587
|
}
|
|
2816
3588
|
|
|
3589
|
+
const bootstrapCallMatch = original.match(
|
|
3590
|
+
/bootstrapApplication\(\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*|\{[\s\S]*?\}))?\s*\)/mu,
|
|
3591
|
+
);
|
|
3592
|
+
const componentIdentifier = bootstrapCallMatch?.[1] ?? null;
|
|
3593
|
+
const configArgument = bootstrapCallMatch?.[2] ?? null;
|
|
3594
|
+
const configImportIdentifier =
|
|
3595
|
+
configArgument && /^[A-Za-z_$][\w$]*$/u.test(configArgument)
|
|
3596
|
+
? configArgument
|
|
3597
|
+
: original.includes('appConfig')
|
|
3598
|
+
? 'appConfig'
|
|
3599
|
+
: null;
|
|
3600
|
+
|
|
3601
|
+
const staticImportPattern = new RegExp(
|
|
3602
|
+
String.raw`^\s*import\s+\{\s*([^}]*)\b${componentIdentifier ?? ''}\b([^}]*)\}\s+from\s+['"]([^'"]+)['"];\s*$`,
|
|
3603
|
+
'mu',
|
|
3604
|
+
);
|
|
3605
|
+
const componentImportMatch = componentIdentifier
|
|
3606
|
+
? original.match(staticImportPattern)
|
|
3607
|
+
: null;
|
|
3608
|
+
const dynamicComponentImportMatch = componentIdentifier
|
|
3609
|
+
? original.match(
|
|
3610
|
+
new RegExp(
|
|
3611
|
+
String.raw`const\s+\{\s*${componentIdentifier}\s*\}\s*=\s*await\s+import\('([^']+)'\);`,
|
|
3612
|
+
'mu',
|
|
3613
|
+
),
|
|
3614
|
+
)
|
|
3615
|
+
: null;
|
|
3616
|
+
const componentImportPath =
|
|
3617
|
+
componentImportMatch?.[3] ?? dynamicComponentImportMatch?.[1] ?? null;
|
|
3618
|
+
|
|
3619
|
+
const configImportMatch = configImportIdentifier
|
|
3620
|
+
? original.match(
|
|
3621
|
+
new RegExp(
|
|
3622
|
+
String.raw`^\s*import\s+\{\s*([^}]*)\b${configImportIdentifier}\b([^}]*)\}\s+from\s+['"]([^'"]+)['"];\s*$`,
|
|
3623
|
+
'mu',
|
|
3624
|
+
),
|
|
3625
|
+
)
|
|
3626
|
+
: null;
|
|
3627
|
+
const configImportPath = configImportMatch?.[3] ?? null;
|
|
3628
|
+
|
|
3629
|
+
const canPreloadBeforeComponentImport =
|
|
3630
|
+
componentIdentifier !== null && componentImportPath !== null;
|
|
3631
|
+
|
|
3632
|
+
if (canPreloadBeforeComponentImport) {
|
|
3633
|
+
const importLines = original
|
|
3634
|
+
.split('\n')
|
|
3635
|
+
.filter((line) => /^\s*import\s+/u.test(line))
|
|
3636
|
+
.filter((line) => !line.match(staticImportPattern))
|
|
3637
|
+
.filter((line) =>
|
|
3638
|
+
configImportMatch
|
|
3639
|
+
? !line.match(
|
|
3640
|
+
new RegExp(
|
|
3641
|
+
String.raw`^\s*import\s+\{\s*([^}]*)\b${configImportIdentifier}\b([^}]*)\}\s+from\s+['"][^'"]+['"];\s*$`,
|
|
3642
|
+
'u',
|
|
3643
|
+
),
|
|
3644
|
+
)
|
|
3645
|
+
: true,
|
|
3646
|
+
)
|
|
3647
|
+
.filter(
|
|
3648
|
+
(line) =>
|
|
3649
|
+
!line.includes("import { providexRsx } from '@rs-x/angular';"),
|
|
3650
|
+
)
|
|
3651
|
+
.filter(
|
|
3652
|
+
(line) =>
|
|
3653
|
+
!line.includes("import { InjectionContainer } from '@rs-x/core';"),
|
|
3654
|
+
)
|
|
3655
|
+
.filter(
|
|
3656
|
+
(line) =>
|
|
3657
|
+
!line.includes(
|
|
3658
|
+
"import { RsXExpressionParserModule } from '@rs-x/expression-parser';",
|
|
3659
|
+
),
|
|
3660
|
+
);
|
|
3661
|
+
|
|
3662
|
+
const bootstrapConfigExpression =
|
|
3663
|
+
configImportPath && configImportIdentifier
|
|
3664
|
+
? `const [{ ${configImportIdentifier} }, { ${componentIdentifier} }] = await Promise.all([\n import('${configImportPath}'),\n import('${componentImportPath}'),\n ]);\n\n await bootstrapApplication(${componentIdentifier}, {\n ...${configImportIdentifier},\n providers: [...(${configImportIdentifier}.providers ?? []), ...providexRsx()],\n });`
|
|
3665
|
+
: `const { ${componentIdentifier} } = await import('${componentImportPath}');\n\n await bootstrapApplication(${componentIdentifier}, {\n providers: [...providexRsx()],\n });`;
|
|
3666
|
+
|
|
3667
|
+
const rewritten = `${importLines.join('\n')}
|
|
3668
|
+
import { providexRsx } from '@rs-x/angular';
|
|
3669
|
+
import { InjectionContainer } from '@rs-x/core';
|
|
3670
|
+
import { RsXExpressionParserModule } from '@rs-x/expression-parser';
|
|
3671
|
+
|
|
3672
|
+
const bootstrap = async (): Promise<void> => {
|
|
3673
|
+
await InjectionContainer.load(RsXExpressionParserModule);
|
|
3674
|
+
${bootstrapConfigExpression}
|
|
3675
|
+
};
|
|
3676
|
+
|
|
3677
|
+
void bootstrap().catch((error) => {
|
|
3678
|
+
console.error(error);
|
|
3679
|
+
});
|
|
3680
|
+
`;
|
|
3681
|
+
|
|
3682
|
+
if (dryRun) {
|
|
3683
|
+
logInfo(`[dry-run] patch ${entryFile} (providexRsx + preload)`);
|
|
3684
|
+
return true;
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
fs.writeFileSync(entryFile, rewritten, 'utf8');
|
|
3688
|
+
logOk(`Patched ${entryFile} to preload RS-X and include providexRsx.`);
|
|
3689
|
+
return true;
|
|
3690
|
+
}
|
|
3691
|
+
|
|
2817
3692
|
const sourceWithImport = injectImport(
|
|
2818
3693
|
original,
|
|
2819
3694
|
"import { providexRsx } from '@rs-x/angular';",
|
|
@@ -3106,9 +3981,13 @@ ${patchBlock}
|
|
|
3106
3981
|
|
|
3107
3982
|
function runSetupReact(flags) {
|
|
3108
3983
|
const dryRun = Boolean(flags['dry-run']);
|
|
3109
|
-
const pm = detectPackageManager(flags.pm);
|
|
3110
|
-
const tag = resolveInstallTag(flags);
|
|
3111
3984
|
const projectRoot = process.cwd();
|
|
3985
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
3986
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
3987
|
+
const reactTsConfigPath = resolveProjectTsConfig(projectRoot);
|
|
3988
|
+
const reactTsConfigRelative = path
|
|
3989
|
+
.relative(projectRoot, reactTsConfigPath)
|
|
3990
|
+
.replace(/\\/gu, '/');
|
|
3112
3991
|
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
3113
3992
|
if (!fs.existsSync(packageJsonPath)) {
|
|
3114
3993
|
logError(`package.json not found in ${projectRoot}`);
|
|
@@ -3154,10 +4033,17 @@ function runSetupReact(flags) {
|
|
|
3154
4033
|
} else {
|
|
3155
4034
|
logInfo('Skipping RS-X React bindings install (--skip-install).');
|
|
3156
4035
|
}
|
|
4036
|
+
ensureRsxConfigFile(projectRoot, 'react', dryRun);
|
|
3157
4037
|
upsertScriptInPackageJson(
|
|
3158
4038
|
projectRoot,
|
|
3159
4039
|
'build:rsx',
|
|
3160
|
-
|
|
4040
|
+
`rsx build --project ${reactTsConfigRelative} --no-emit --prod`,
|
|
4041
|
+
dryRun,
|
|
4042
|
+
);
|
|
4043
|
+
upsertScriptInPackageJson(
|
|
4044
|
+
projectRoot,
|
|
4045
|
+
'typecheck:rsx',
|
|
4046
|
+
`rsx typecheck --project ${reactTsConfigRelative}`,
|
|
3161
4047
|
dryRun,
|
|
3162
4048
|
);
|
|
3163
4049
|
upsertScriptInPackageJson(
|
|
@@ -3173,14 +4059,21 @@ function runSetupReact(flags) {
|
|
|
3173
4059
|
dryRun,
|
|
3174
4060
|
);
|
|
3175
4061
|
wireRsxVitePlugin(projectRoot, dryRun);
|
|
4062
|
+
if (resolveCliVerifyFlag(projectRoot, flags, 'setup')) {
|
|
4063
|
+
verifySetupOutput(projectRoot, 'react');
|
|
4064
|
+
}
|
|
3176
4065
|
logOk('RS-X React setup completed.');
|
|
3177
4066
|
}
|
|
3178
4067
|
|
|
3179
4068
|
function runSetupNext(flags) {
|
|
3180
4069
|
const dryRun = Boolean(flags['dry-run']);
|
|
3181
|
-
const pm = detectPackageManager(flags.pm);
|
|
3182
|
-
const tag = resolveInstallTag(flags);
|
|
3183
4070
|
const projectRoot = process.cwd();
|
|
4071
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
4072
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
4073
|
+
const nextTsConfigPath = resolveProjectTsConfig(projectRoot);
|
|
4074
|
+
const nextTsConfigRelative = path
|
|
4075
|
+
.relative(projectRoot, nextTsConfigPath)
|
|
4076
|
+
.replace(/\\/gu, '/');
|
|
3184
4077
|
runInit({
|
|
3185
4078
|
...flags,
|
|
3186
4079
|
'skip-vscode': true,
|
|
@@ -3201,15 +4094,43 @@ function runSetupNext(flags) {
|
|
|
3201
4094
|
} else {
|
|
3202
4095
|
logInfo('Skipping RS-X React bindings install (--skip-install).');
|
|
3203
4096
|
}
|
|
4097
|
+
ensureRsxConfigFile(projectRoot, 'next', dryRun);
|
|
4098
|
+
upsertScriptInPackageJson(
|
|
4099
|
+
projectRoot,
|
|
4100
|
+
'build:rsx',
|
|
4101
|
+
`rsx build --project ${nextTsConfigRelative} --no-emit --prod`,
|
|
4102
|
+
dryRun,
|
|
4103
|
+
);
|
|
4104
|
+
upsertScriptInPackageJson(
|
|
4105
|
+
projectRoot,
|
|
4106
|
+
'typecheck:rsx',
|
|
4107
|
+
`rsx typecheck --project ${nextTsConfigRelative}`,
|
|
4108
|
+
dryRun,
|
|
4109
|
+
);
|
|
4110
|
+
upsertScriptInPackageJson(
|
|
4111
|
+
projectRoot,
|
|
4112
|
+
'dev',
|
|
4113
|
+
'npm run build:rsx && next dev',
|
|
4114
|
+
dryRun,
|
|
4115
|
+
);
|
|
4116
|
+
upsertScriptInPackageJson(
|
|
4117
|
+
projectRoot,
|
|
4118
|
+
'build',
|
|
4119
|
+
'npm run build:rsx && next build',
|
|
4120
|
+
dryRun,
|
|
4121
|
+
);
|
|
3204
4122
|
wireRsxNextWebpack(projectRoot, dryRun);
|
|
4123
|
+
if (resolveCliVerifyFlag(projectRoot, flags, 'setup')) {
|
|
4124
|
+
verifySetupOutput(projectRoot, 'next');
|
|
4125
|
+
}
|
|
3205
4126
|
logOk('RS-X Next.js setup completed.');
|
|
3206
4127
|
}
|
|
3207
4128
|
|
|
3208
4129
|
function runSetupVue(flags) {
|
|
3209
4130
|
const dryRun = Boolean(flags['dry-run']);
|
|
3210
|
-
const pm = detectPackageManager(flags.pm);
|
|
3211
|
-
const tag = resolveInstallTag(flags);
|
|
3212
4131
|
const projectRoot = process.cwd();
|
|
4132
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
4133
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
3213
4134
|
runInit({
|
|
3214
4135
|
...flags,
|
|
3215
4136
|
'skip-vscode': true,
|
|
@@ -3238,6 +4159,7 @@ function runSetupVue(flags) {
|
|
|
3238
4159
|
} else {
|
|
3239
4160
|
logInfo('Skipping RS-X Vue bindings install (--skip-install).');
|
|
3240
4161
|
}
|
|
4162
|
+
ensureRsxConfigFile(projectRoot, 'vuejs', dryRun);
|
|
3241
4163
|
upsertScriptInPackageJson(
|
|
3242
4164
|
projectRoot,
|
|
3243
4165
|
'build:rsx',
|
|
@@ -3267,14 +4189,17 @@ function runSetupVue(flags) {
|
|
|
3267
4189
|
ensureTsConfigIncludePattern(vueTsConfigPath, 'src/**/*.d.ts', dryRun);
|
|
3268
4190
|
ensureVueEnvTypes(projectRoot, dryRun);
|
|
3269
4191
|
wireRsxVitePlugin(projectRoot, dryRun);
|
|
4192
|
+
if (resolveCliVerifyFlag(projectRoot, flags, 'setup')) {
|
|
4193
|
+
verifySetupOutput(projectRoot, 'vuejs');
|
|
4194
|
+
}
|
|
3270
4195
|
logOk('RS-X Vue setup completed.');
|
|
3271
4196
|
}
|
|
3272
4197
|
|
|
3273
4198
|
function runSetupAngular(flags) {
|
|
3274
4199
|
const dryRun = Boolean(flags['dry-run']);
|
|
3275
|
-
const pm = detectPackageManager(flags.pm);
|
|
3276
|
-
const tag = resolveInstallTag(flags);
|
|
3277
4200
|
const projectRoot = process.cwd();
|
|
4201
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
4202
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
3278
4203
|
const angularTsConfigPath = resolveAngularProjectTsConfig(projectRoot);
|
|
3279
4204
|
const angularTsConfigRelative = path
|
|
3280
4205
|
.relative(projectRoot, angularTsConfigPath)
|
|
@@ -3318,7 +4243,7 @@ function runSetupAngular(flags) {
|
|
|
3318
4243
|
);
|
|
3319
4244
|
}
|
|
3320
4245
|
|
|
3321
|
-
|
|
4246
|
+
ensureRsxConfigFile(projectRoot, 'angular', dryRun);
|
|
3322
4247
|
|
|
3323
4248
|
upsertScriptInPackageJson(
|
|
3324
4249
|
projectRoot,
|
|
@@ -3386,13 +4311,17 @@ function runSetupAngular(flags) {
|
|
|
3386
4311
|
dryRun,
|
|
3387
4312
|
});
|
|
3388
4313
|
|
|
4314
|
+
if (resolveCliVerifyFlag(projectRoot, flags, 'setup')) {
|
|
4315
|
+
verifySetupOutput(projectRoot, 'angular');
|
|
4316
|
+
}
|
|
4317
|
+
|
|
3389
4318
|
logOk('RS-X Angular setup completed.');
|
|
3390
4319
|
}
|
|
3391
4320
|
|
|
3392
4321
|
function runSetupAuto(flags) {
|
|
3393
4322
|
const projectRoot = process.cwd();
|
|
3394
4323
|
const context = detectProjectContext(projectRoot);
|
|
3395
|
-
const tag =
|
|
4324
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
3396
4325
|
|
|
3397
4326
|
if (context === 'react') {
|
|
3398
4327
|
logInfo('Auto-detected framework: react');
|
|
@@ -3419,7 +4348,7 @@ function runSetupAuto(flags) {
|
|
|
3419
4348
|
}
|
|
3420
4349
|
|
|
3421
4350
|
logInfo('No framework-specific setup detected; running generic setup.');
|
|
3422
|
-
const pm =
|
|
4351
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
3423
4352
|
installRuntimePackages(
|
|
3424
4353
|
pm,
|
|
3425
4354
|
Boolean(flags['dry-run']),
|
|
@@ -3450,8 +4379,13 @@ function runBuild(flags) {
|
|
|
3450
4379
|
const dryRun = Boolean(flags['dry-run']);
|
|
3451
4380
|
const noEmit = Boolean(flags['no-emit']);
|
|
3452
4381
|
const prodMode = parseBooleanFlag(flags.prod, false);
|
|
4382
|
+
const invocationConfig = resolveRsxBuildConfig(invocationRoot);
|
|
3453
4383
|
const projectArg =
|
|
3454
|
-
typeof flags.project === 'string'
|
|
4384
|
+
typeof flags.project === 'string'
|
|
4385
|
+
? flags.project
|
|
4386
|
+
: typeof invocationConfig.tsconfig === 'string'
|
|
4387
|
+
? invocationConfig.tsconfig
|
|
4388
|
+
: 'tsconfig.json';
|
|
3455
4389
|
const configPath = path.resolve(invocationRoot, projectArg);
|
|
3456
4390
|
const projectRoot = path.dirname(configPath);
|
|
3457
4391
|
const context = detectProjectContext(projectRoot);
|
|
@@ -3550,7 +4484,9 @@ function runBuild(flags) {
|
|
|
3550
4484
|
const outDirOverride =
|
|
3551
4485
|
typeof flags['out-dir'] === 'string'
|
|
3552
4486
|
? path.resolve(projectRoot, flags['out-dir'])
|
|
3553
|
-
:
|
|
4487
|
+
: typeof rsxBuildConfig.outDir === 'string'
|
|
4488
|
+
? path.resolve(projectRoot, rsxBuildConfig.outDir)
|
|
4489
|
+
: null;
|
|
3554
4490
|
const outDir =
|
|
3555
4491
|
outDirOverride ??
|
|
3556
4492
|
parsedConfig.options.outDir ??
|
|
@@ -4072,20 +5008,286 @@ function ensureAngularPolyfillsContainsFile({
|
|
|
4072
5008
|
logOk(`Updated angular.json to inject RS-X AOT runtime registration.`);
|
|
4073
5009
|
}
|
|
4074
5010
|
|
|
4075
|
-
function
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
return {};
|
|
5011
|
+
function readJsonFileIfPresent(filePath) {
|
|
5012
|
+
if (!fs.existsSync(filePath)) {
|
|
5013
|
+
return null;
|
|
4079
5014
|
}
|
|
4080
5015
|
|
|
4081
5016
|
try {
|
|
4082
|
-
|
|
4083
|
-
const rsxConfig = packageJson.rsx ?? {};
|
|
4084
|
-
const buildConfig = rsxConfig.build ?? {};
|
|
4085
|
-
return typeof buildConfig === 'object' && buildConfig ? buildConfig : {};
|
|
5017
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
4086
5018
|
} catch {
|
|
4087
|
-
return
|
|
5019
|
+
return null;
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
|
|
5023
|
+
function validateRsxConfigShape(config, filePath) {
|
|
5024
|
+
if (typeof config !== 'object' || !config || Array.isArray(config)) {
|
|
5025
|
+
logError(
|
|
5026
|
+
`Invalid RS-X config in ${filePath}: expected a JSON object at the top level.`,
|
|
5027
|
+
);
|
|
5028
|
+
process.exit(1);
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
const build = config.build;
|
|
5032
|
+
if (build !== undefined) {
|
|
5033
|
+
if (typeof build !== 'object' || !build || Array.isArray(build)) {
|
|
5034
|
+
logError(
|
|
5035
|
+
`Invalid RS-X config in ${filePath}: "build" must be an object.`,
|
|
5036
|
+
);
|
|
5037
|
+
process.exit(1);
|
|
5038
|
+
}
|
|
5039
|
+
|
|
5040
|
+
const stringKeys = ['preparseFile', 'compiledFile', 'registrationFile'];
|
|
5041
|
+
const extraStringKeys = ['tsconfig', 'outDir'];
|
|
5042
|
+
for (const key of extraStringKeys) {
|
|
5043
|
+
if (build[key] !== undefined && typeof build[key] !== 'string') {
|
|
5044
|
+
logError(
|
|
5045
|
+
`Invalid RS-X config in ${filePath}: "build.${key}" must be a string.`,
|
|
5046
|
+
);
|
|
5047
|
+
process.exit(1);
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
for (const key of stringKeys) {
|
|
5051
|
+
if (build[key] !== undefined && typeof build[key] !== 'string') {
|
|
5052
|
+
logError(
|
|
5053
|
+
`Invalid RS-X config in ${filePath}: "build.${key}" must be a string.`,
|
|
5054
|
+
);
|
|
5055
|
+
process.exit(1);
|
|
5056
|
+
}
|
|
5057
|
+
}
|
|
5058
|
+
|
|
5059
|
+
const booleanKeys = ['preparse', 'compiled', 'compiledResolvedEvaluator'];
|
|
5060
|
+
for (const key of booleanKeys) {
|
|
5061
|
+
if (build[key] !== undefined && typeof build[key] !== 'boolean') {
|
|
5062
|
+
logError(
|
|
5063
|
+
`Invalid RS-X config in ${filePath}: "build.${key}" must be a boolean.`,
|
|
5064
|
+
);
|
|
5065
|
+
process.exit(1);
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
|
|
5070
|
+
const cli = config.cli;
|
|
5071
|
+
if (cli !== undefined) {
|
|
5072
|
+
if (typeof cli !== 'object' || !cli || Array.isArray(cli)) {
|
|
5073
|
+
logError(`Invalid RS-X config in ${filePath}: "cli" must be an object.`);
|
|
5074
|
+
process.exit(1);
|
|
5075
|
+
}
|
|
5076
|
+
|
|
5077
|
+
const cliStringKeys = ['packageManager', 'installTag'];
|
|
5078
|
+
for (const key of cliStringKeys) {
|
|
5079
|
+
if (cli[key] !== undefined && typeof cli[key] !== 'string') {
|
|
5080
|
+
logError(
|
|
5081
|
+
`Invalid RS-X config in ${filePath}: "cli.${key}" must be a string.`,
|
|
5082
|
+
);
|
|
5083
|
+
process.exit(1);
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
|
|
5087
|
+
if (
|
|
5088
|
+
cli.packageManager !== undefined &&
|
|
5089
|
+
!['pnpm', 'npm', 'yarn', 'bun'].includes(cli.packageManager)
|
|
5090
|
+
) {
|
|
5091
|
+
logError(
|
|
5092
|
+
`Invalid RS-X config in ${filePath}: "cli.packageManager" must be one of pnpm, npm, yarn, or bun.`,
|
|
5093
|
+
);
|
|
5094
|
+
process.exit(1);
|
|
5095
|
+
}
|
|
5096
|
+
|
|
5097
|
+
if (
|
|
5098
|
+
cli.installTag !== undefined &&
|
|
5099
|
+
!['latest', 'next'].includes(cli.installTag)
|
|
5100
|
+
) {
|
|
5101
|
+
logError(
|
|
5102
|
+
`Invalid RS-X config in ${filePath}: "cli.installTag" must be "latest" or "next".`,
|
|
5103
|
+
);
|
|
5104
|
+
process.exit(1);
|
|
5105
|
+
}
|
|
5106
|
+
|
|
5107
|
+
const cliBooleanSections = ['setup', 'project'];
|
|
5108
|
+
for (const key of cliBooleanSections) {
|
|
5109
|
+
if (cli[key] !== undefined) {
|
|
5110
|
+
if (
|
|
5111
|
+
typeof cli[key] !== 'object' ||
|
|
5112
|
+
!cli[key] ||
|
|
5113
|
+
Array.isArray(cli[key])
|
|
5114
|
+
) {
|
|
5115
|
+
logError(
|
|
5116
|
+
`Invalid RS-X config in ${filePath}: "cli.${key}" must be an object.`,
|
|
5117
|
+
);
|
|
5118
|
+
process.exit(1);
|
|
5119
|
+
}
|
|
5120
|
+
if (
|
|
5121
|
+
cli[key].verify !== undefined &&
|
|
5122
|
+
typeof cli[key].verify !== 'boolean'
|
|
5123
|
+
) {
|
|
5124
|
+
logError(
|
|
5125
|
+
`Invalid RS-X config in ${filePath}: "cli.${key}.verify" must be a boolean.`,
|
|
5126
|
+
);
|
|
5127
|
+
process.exit(1);
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
|
|
5132
|
+
const add = cli.add;
|
|
5133
|
+
if (add !== undefined) {
|
|
5134
|
+
if (typeof add !== 'object' || !add || Array.isArray(add)) {
|
|
5135
|
+
logError(
|
|
5136
|
+
`Invalid RS-X config in ${filePath}: "cli.add" must be an object.`,
|
|
5137
|
+
);
|
|
5138
|
+
process.exit(1);
|
|
5139
|
+
}
|
|
5140
|
+
|
|
5141
|
+
if (
|
|
5142
|
+
add.defaultDirectory !== undefined &&
|
|
5143
|
+
typeof add.defaultDirectory !== 'string'
|
|
5144
|
+
) {
|
|
5145
|
+
logError(
|
|
5146
|
+
`Invalid RS-X config in ${filePath}: "cli.add.defaultDirectory" must be a string.`,
|
|
5147
|
+
);
|
|
5148
|
+
process.exit(1);
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
if (add.searchRoots !== undefined) {
|
|
5152
|
+
if (
|
|
5153
|
+
!Array.isArray(add.searchRoots) ||
|
|
5154
|
+
add.searchRoots.some((entry) => typeof entry !== 'string')
|
|
5155
|
+
) {
|
|
5156
|
+
logError(
|
|
5157
|
+
`Invalid RS-X config in ${filePath}: "cli.add.searchRoots" must be an array of strings.`,
|
|
5158
|
+
);
|
|
5159
|
+
process.exit(1);
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
function mergeRsxConfig(baseConfig, overrideConfig) {
|
|
5167
|
+
const base = typeof baseConfig === 'object' && baseConfig ? baseConfig : {};
|
|
5168
|
+
const override =
|
|
5169
|
+
typeof overrideConfig === 'object' && overrideConfig ? overrideConfig : {};
|
|
5170
|
+
|
|
5171
|
+
return {
|
|
5172
|
+
...base,
|
|
5173
|
+
...override,
|
|
5174
|
+
build: {
|
|
5175
|
+
...(typeof base.build === 'object' && base.build ? base.build : {}),
|
|
5176
|
+
...(typeof override.build === 'object' && override.build
|
|
5177
|
+
? override.build
|
|
5178
|
+
: {}),
|
|
5179
|
+
},
|
|
5180
|
+
cli: {
|
|
5181
|
+
...(typeof base.cli === 'object' && base.cli ? base.cli : {}),
|
|
5182
|
+
...(typeof override.cli === 'object' && override.cli ? override.cli : {}),
|
|
5183
|
+
add: {
|
|
5184
|
+
...(typeof base.cli?.add === 'object' && base.cli?.add
|
|
5185
|
+
? base.cli.add
|
|
5186
|
+
: {}),
|
|
5187
|
+
...(typeof override.cli?.add === 'object' && override.cli?.add
|
|
5188
|
+
? override.cli.add
|
|
5189
|
+
: {}),
|
|
5190
|
+
},
|
|
5191
|
+
},
|
|
5192
|
+
};
|
|
5193
|
+
}
|
|
5194
|
+
|
|
5195
|
+
function resolveRsxProjectConfig(projectRoot) {
|
|
5196
|
+
const fileConfigPath = path.join(projectRoot, 'rsx.config.json');
|
|
5197
|
+
const fileRsxConfig = readJsonFileIfPresent(fileConfigPath) ?? {};
|
|
5198
|
+
validateRsxConfigShape(fileRsxConfig, fileConfigPath);
|
|
5199
|
+
return mergeRsxConfig({}, fileRsxConfig);
|
|
5200
|
+
}
|
|
5201
|
+
|
|
5202
|
+
function defaultCliAddConfigForTemplate(template) {
|
|
5203
|
+
if (template === 'next' || template === 'nextjs') {
|
|
5204
|
+
return {
|
|
5205
|
+
defaultDirectory: 'app/expressions',
|
|
5206
|
+
searchRoots: ['app', 'src', 'expressions'],
|
|
5207
|
+
};
|
|
5208
|
+
}
|
|
5209
|
+
|
|
5210
|
+
return {
|
|
5211
|
+
defaultDirectory: 'src/expressions',
|
|
5212
|
+
searchRoots: ['src', 'app', 'expressions'],
|
|
5213
|
+
};
|
|
5214
|
+
}
|
|
5215
|
+
|
|
5216
|
+
function defaultCliConfigForTemplate(template) {
|
|
5217
|
+
return {
|
|
5218
|
+
packageManager: 'npm',
|
|
5219
|
+
installTag: 'next',
|
|
5220
|
+
setup: {
|
|
5221
|
+
verify: false,
|
|
5222
|
+
},
|
|
5223
|
+
project: {
|
|
5224
|
+
verify: false,
|
|
5225
|
+
},
|
|
5226
|
+
add: defaultCliAddConfigForTemplate(template),
|
|
5227
|
+
};
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5230
|
+
function defaultRsxBuildConfigForTemplate(template) {
|
|
5231
|
+
if (template === 'next' || template === 'nextjs') {
|
|
5232
|
+
return {
|
|
5233
|
+
preparse: true,
|
|
5234
|
+
preparseFile: 'app/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
5235
|
+
compiled: true,
|
|
5236
|
+
compiledFile: 'app/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
5237
|
+
registrationFile: 'app/rsx-generated/rsx-aot-registration.generated.ts',
|
|
5238
|
+
compiledResolvedEvaluator: false,
|
|
5239
|
+
};
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5242
|
+
return {
|
|
5243
|
+
preparse: true,
|
|
5244
|
+
preparseFile: 'src/rsx-generated/rsx-aot-preparsed.generated.ts',
|
|
5245
|
+
compiled: true,
|
|
5246
|
+
compiledFile: 'src/rsx-generated/rsx-aot-compiled.generated.ts',
|
|
5247
|
+
registrationFile: 'src/rsx-generated/rsx-aot-registration.generated.ts',
|
|
5248
|
+
compiledResolvedEvaluator: false,
|
|
5249
|
+
};
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
function ensureRsxConfigFile(projectRoot, template, dryRun) {
|
|
5253
|
+
const configPath = path.join(projectRoot, 'rsx.config.json');
|
|
5254
|
+
const defaultConfig = {
|
|
5255
|
+
build: defaultRsxBuildConfigForTemplate(template),
|
|
5256
|
+
cli: defaultCliConfigForTemplate(template),
|
|
5257
|
+
};
|
|
5258
|
+
|
|
5259
|
+
const existingConfig = readJsonFileIfPresent(configPath);
|
|
5260
|
+
const nextConfig = mergeRsxConfig(defaultConfig, existingConfig ?? {});
|
|
5261
|
+
|
|
5262
|
+
if (
|
|
5263
|
+
existingConfig &&
|
|
5264
|
+
JSON.stringify(existingConfig) === JSON.stringify(nextConfig)
|
|
5265
|
+
) {
|
|
5266
|
+
return false;
|
|
5267
|
+
}
|
|
5268
|
+
|
|
5269
|
+
if (dryRun) {
|
|
5270
|
+
logInfo(`[dry-run] ${existingConfig ? 'patch' : 'create'} ${configPath}`);
|
|
5271
|
+
return true;
|
|
4088
5272
|
}
|
|
5273
|
+
|
|
5274
|
+
fs.writeFileSync(
|
|
5275
|
+
configPath,
|
|
5276
|
+
`${JSON.stringify(nextConfig, null, 2)}\n`,
|
|
5277
|
+
'utf8',
|
|
5278
|
+
);
|
|
5279
|
+
logOk(`${existingConfig ? 'Patched' : 'Created'} ${configPath}`);
|
|
5280
|
+
return true;
|
|
5281
|
+
}
|
|
5282
|
+
|
|
5283
|
+
function resolveRsxBuildConfig(projectRoot) {
|
|
5284
|
+
const buildConfig = resolveRsxProjectConfig(projectRoot).build ?? {};
|
|
5285
|
+
return typeof buildConfig === 'object' && buildConfig ? buildConfig : {};
|
|
5286
|
+
}
|
|
5287
|
+
|
|
5288
|
+
function resolveRsxCliConfig(projectRoot) {
|
|
5289
|
+
const cliConfig = resolveRsxProjectConfig(projectRoot).cli ?? {};
|
|
5290
|
+
return typeof cliConfig === 'object' && cliConfig ? cliConfig : {};
|
|
4089
5291
|
}
|
|
4090
5292
|
|
|
4091
5293
|
function parseBooleanFlag(value, defaultValue) {
|
|
@@ -4146,7 +5348,7 @@ function printGeneralHelp() {
|
|
|
4146
5348
|
console.log(
|
|
4147
5349
|
' typecheck Type-check project + RS-X semantic checks',
|
|
4148
5350
|
);
|
|
4149
|
-
console.log(' version | -v
|
|
5351
|
+
console.log(' version | v | -v Print CLI version');
|
|
4150
5352
|
console.log('');
|
|
4151
5353
|
console.log('Help Aliases:');
|
|
4152
5354
|
console.log(' rsx -h');
|
|
@@ -4180,14 +5382,28 @@ function printAddHelp() {
|
|
|
4180
5382
|
console.log(
|
|
4181
5383
|
' - Prompts for expression export name (must be valid TS identifier)',
|
|
4182
5384
|
);
|
|
5385
|
+
console.log(" - Prompts for the initial expression string (default: 'a')");
|
|
5386
|
+
console.log(
|
|
5387
|
+
' - Seeds the generated model with top-level identifiers found in that expression when possible',
|
|
5388
|
+
);
|
|
4183
5389
|
console.log(
|
|
4184
5390
|
' - Prompts whether file name should be kebab-case (default: yes)',
|
|
4185
5391
|
);
|
|
4186
5392
|
console.log(' - Prompts for output directory (relative or absolute)');
|
|
4187
|
-
console.log(' - Prompts whether to reuse an existing model file');
|
|
4188
|
-
console.log(' - Creates <name>.ts and optionally creates <name>.model.ts');
|
|
4189
5393
|
console.log(
|
|
4190
|
-
' -
|
|
5394
|
+
' - Prompts for add mode: new one-file, new separate-model, or update existing',
|
|
5395
|
+
);
|
|
5396
|
+
console.log(
|
|
5397
|
+
' - Defaults to keeping the model in the same file as the expression',
|
|
5398
|
+
);
|
|
5399
|
+
console.log(
|
|
5400
|
+
' - If you choose an existing file, shows a list of files that already contain RS-X expressions',
|
|
5401
|
+
);
|
|
5402
|
+
console.log(
|
|
5403
|
+
' - Can still create or reuse a separate model file when you opt out of same-file mode',
|
|
5404
|
+
);
|
|
5405
|
+
console.log(
|
|
5406
|
+
' - Respects rsx.config.json (`cli.add`) for add defaults and file discovery',
|
|
4191
5407
|
);
|
|
4192
5408
|
}
|
|
4193
5409
|
|
|
@@ -4226,7 +5442,7 @@ function printInstallHelp(target) {
|
|
|
4226
5442
|
function printSetupHelp() {
|
|
4227
5443
|
console.log('Usage:');
|
|
4228
5444
|
console.log(
|
|
4229
|
-
' rsx setup [--pm <pnpm|npm|yarn|bun>] [--next] [--force] [--local] [--dry-run]',
|
|
5445
|
+
' rsx setup [--pm <pnpm|npm|yarn|bun>] [--next] [--verify] [--force] [--local] [--dry-run]',
|
|
4230
5446
|
);
|
|
4231
5447
|
console.log('');
|
|
4232
5448
|
console.log('What it does:');
|
|
@@ -4235,12 +5451,17 @@ function printSetupHelp() {
|
|
|
4235
5451
|
);
|
|
4236
5452
|
console.log(' - Installs runtime packages');
|
|
4237
5453
|
console.log(' - Installs compiler tooling packages');
|
|
4238
|
-
console.log(' -
|
|
5454
|
+
console.log(' - Writes rsx.build config plus build/typecheck scripts');
|
|
5455
|
+
console.log(' - Creates rsx.config.json with CLI defaults you can override');
|
|
4239
5456
|
console.log(' - Applies framework-specific transform/build integration');
|
|
5457
|
+
console.log(' - Does not install the VS Code extension automatically');
|
|
4240
5458
|
console.log('');
|
|
4241
5459
|
console.log('Options:');
|
|
4242
5460
|
console.log(' --pm Explicit package manager');
|
|
4243
5461
|
console.log(' --next Install prerelease versions (dist-tag next)');
|
|
5462
|
+
console.log(
|
|
5463
|
+
' --verify Validate the resulting setup output before returning',
|
|
5464
|
+
);
|
|
4244
5465
|
console.log(' --force Reinstall extension if already installed');
|
|
4245
5466
|
console.log(' --local Build/install local VSIX from repo workspace');
|
|
4246
5467
|
console.log(' --dry-run Print commands without executing them');
|
|
@@ -4259,14 +5480,17 @@ function printInitHelp() {
|
|
|
4259
5480
|
console.log(
|
|
4260
5481
|
' - Detects project context and wires RS-X bootstrap in entry file',
|
|
4261
5482
|
);
|
|
4262
|
-
console.log(' -
|
|
5483
|
+
console.log(' - Creates rsx.config.json with CLI defaults you can override');
|
|
5484
|
+
console.log(' - Does not install the VS Code extension automatically');
|
|
4263
5485
|
console.log('');
|
|
4264
5486
|
console.log('Options:');
|
|
4265
5487
|
console.log(' --pm Explicit package manager');
|
|
4266
5488
|
console.log(' --entry Explicit application entry file');
|
|
4267
5489
|
console.log(' --next Install prerelease versions (dist-tag next)');
|
|
4268
5490
|
console.log(' --skip-install Skip npm/pnpm/yarn/bun package installation');
|
|
4269
|
-
console.log(
|
|
5491
|
+
console.log(
|
|
5492
|
+
' --skip-vscode Accepted for compatibility; VS Code is not auto-installed',
|
|
5493
|
+
);
|
|
4270
5494
|
console.log(' --force Reinstall extension if already installed');
|
|
4271
5495
|
console.log(' --local Build/install local VSIX from repo workspace');
|
|
4272
5496
|
console.log(' --dry-run Print commands without executing them');
|
|
@@ -4275,7 +5499,7 @@ function printInitHelp() {
|
|
|
4275
5499
|
function printProjectHelp() {
|
|
4276
5500
|
console.log('Usage:');
|
|
4277
5501
|
console.log(
|
|
4278
|
-
' rsx project [angular|vuejs|react|nextjs|nodejs] [--name <project-name>] [--pm <pnpm|npm|yarn|bun>] [--next] [--template <angular|vuejs|react|nextjs|nodejs>] [--tarballs-dir <path>] [--skip-install] [--skip-vscode] [--dry-run]',
|
|
5502
|
+
' rsx project [angular|vuejs|react|nextjs|nodejs] [--name <project-name>] [--pm <pnpm|npm|yarn|bun>] [--next] [--template <angular|vuejs|react|nextjs|nodejs>] [--tarballs-dir <path>] [--skip-install] [--skip-vscode] [--verify] [--dry-run]',
|
|
4279
5503
|
);
|
|
4280
5504
|
console.log('');
|
|
4281
5505
|
console.log('What it does:');
|
|
@@ -4286,6 +5510,9 @@ function printProjectHelp() {
|
|
|
4286
5510
|
);
|
|
4287
5511
|
console.log(' - Scaffolds framework app and wires RS-X bootstrap/setup');
|
|
4288
5512
|
console.log(' - Writes package.json with RS-X dependencies');
|
|
5513
|
+
console.log(
|
|
5514
|
+
' - Creates rsx.config.json with starter CLI defaults you can override',
|
|
5515
|
+
);
|
|
4289
5516
|
console.log(
|
|
4290
5517
|
' - Adds tsconfig + TypeScript plugin config for editor support',
|
|
4291
5518
|
);
|
|
@@ -4295,6 +5522,7 @@ function printProjectHelp() {
|
|
|
4295
5522
|
console.log(' - For React/Next templates: also installs @rs-x/react');
|
|
4296
5523
|
console.log(' - For Vue template: also installs @rs-x/vue');
|
|
4297
5524
|
console.log(' - Installs dependencies (unless --skip-install)');
|
|
5525
|
+
console.log(' - Verifies the generated starter before reporting success');
|
|
4298
5526
|
console.log('');
|
|
4299
5527
|
console.log('Options:');
|
|
4300
5528
|
console.log(' --name Project folder/package name');
|
|
@@ -4308,7 +5536,12 @@ function printProjectHelp() {
|
|
|
4308
5536
|
);
|
|
4309
5537
|
console.log(' (or set RSX_TARBALLS_DIR env var)');
|
|
4310
5538
|
console.log(' --skip-install Skip dependency installation');
|
|
4311
|
-
console.log(
|
|
5539
|
+
console.log(
|
|
5540
|
+
' --skip-vscode Accepted for compatibility; VS Code is not auto-installed',
|
|
5541
|
+
);
|
|
5542
|
+
console.log(
|
|
5543
|
+
' --verify Re-run starter structure checks explicitly after generation',
|
|
5544
|
+
);
|
|
4312
5545
|
console.log(' --dry-run Print actions without writing files');
|
|
4313
5546
|
}
|
|
4314
5547
|
|
|
@@ -4526,13 +5759,14 @@ function main() {
|
|
|
4526
5759
|
}
|
|
4527
5760
|
|
|
4528
5761
|
if (command === 'install' && target === 'compiler') {
|
|
4529
|
-
const
|
|
4530
|
-
const
|
|
5762
|
+
const projectRoot = process.cwd();
|
|
5763
|
+
const pm = resolveCliPackageManager(projectRoot, flags.pm);
|
|
5764
|
+
const tag = resolveConfiguredInstallTag(projectRoot, flags);
|
|
4531
5765
|
installCompilerPackages(
|
|
4532
5766
|
pm,
|
|
4533
5767
|
Boolean(flags['dry-run']),
|
|
4534
5768
|
tag,
|
|
4535
|
-
|
|
5769
|
+
projectRoot,
|
|
4536
5770
|
flags,
|
|
4537
5771
|
);
|
|
4538
5772
|
return;
|