@r8s/cli 0.1.0 → 0.2.1
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/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +1082 -7
- package/dist/catalog.js.map +1 -1
- package/dist/cli.js +418 -431
- package/dist/cli.js.map +1 -1
- package/dist/renderer.d.ts +13 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +45 -1
- package/dist/renderer.js.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -3,35 +3,40 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const path_1 = require("path");
|
|
6
|
+
const core_1 = require("@r8s/core");
|
|
7
|
+
const FLAG_TABLE = {
|
|
8
|
+
'--help': (o) => ((o.help = true), false),
|
|
9
|
+
'-h': (o) => ((o.help = true), false),
|
|
10
|
+
'--out': (o, v) => ((o.out = v), true),
|
|
11
|
+
'-o': (o, v) => ((o.out = v), true),
|
|
12
|
+
'--entry': (o, v) => ((o.entry = v), true),
|
|
13
|
+
'-e': (o, v) => ((o.entry = v), true),
|
|
14
|
+
'--template': (o, v) => ((o.template = v), true),
|
|
15
|
+
'-t': (o, v) => ((o.template = v), true),
|
|
16
|
+
'--strategy': (o, v) => ((o.strategy = v), true),
|
|
17
|
+
'--operators': (o, v) => ((o.operators = v), true),
|
|
18
|
+
'--include-operators': (o) => ((o.includeOperators = true), false),
|
|
19
|
+
'--operators-only': (o) => ((o.operatorsOnly = true), false),
|
|
20
|
+
'--skip-secret-guardrails': (o) => ((o.skipSecretGuardrails = true), false),
|
|
21
|
+
};
|
|
6
22
|
function parseArgs(args) {
|
|
7
23
|
const options = {};
|
|
24
|
+
const positionals = [];
|
|
8
25
|
for (let i = 0; i < args.length; i++) {
|
|
9
26
|
const arg = args[i];
|
|
10
|
-
|
|
11
|
-
|
|
27
|
+
const handler = FLAG_TABLE[arg];
|
|
28
|
+
if (handler) {
|
|
29
|
+
const needsValue = handler(options, args[i + 1]);
|
|
30
|
+
if (needsValue)
|
|
31
|
+
i++;
|
|
12
32
|
}
|
|
13
|
-
else if (arg
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
else if (arg === '--template' || arg === '-t') {
|
|
17
|
-
options.template = args[++i];
|
|
18
|
-
}
|
|
19
|
-
else if (arg === '--operators') {
|
|
20
|
-
options.operators = args[++i];
|
|
21
|
-
}
|
|
22
|
-
else if (arg === '--strategy' || arg === '-s') {
|
|
23
|
-
options.strategy = args[++i];
|
|
24
|
-
}
|
|
25
|
-
else if (arg === '--include-operators') {
|
|
26
|
-
options.includeOperators = true;
|
|
27
|
-
}
|
|
28
|
-
else if (arg === '--operators-only') {
|
|
29
|
-
options.operatorsOnly = true;
|
|
30
|
-
}
|
|
31
|
-
else if (arg === '--help' || arg === '-h') {
|
|
32
|
-
options.help = true;
|
|
33
|
+
else if (!arg.startsWith('-')) {
|
|
34
|
+
positionals.push(arg);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
37
|
+
// Preserve command + its args in place for the command dispatch
|
|
38
|
+
args.length = 0;
|
|
39
|
+
args.push(...positionals);
|
|
35
40
|
return options;
|
|
36
41
|
}
|
|
37
42
|
function showHelp() {
|
|
@@ -58,6 +63,8 @@ Options:
|
|
|
58
63
|
--out, -o <path> Output file path (default: stdout)
|
|
59
64
|
--include-operators Include operator manifests in rendered output
|
|
60
65
|
--operators-only Render only operator manifests (with render command)
|
|
66
|
+
--skip-secret-guardrails Bypass the plaintext-credentials guardrail (local dev only).
|
|
67
|
+
Stdout output is masked; never commit or apply skipped output.
|
|
61
68
|
--template, -t <name> Template for init (basic, fullstack) [default: basic]
|
|
62
69
|
--operators <list> Comma-separated list of operators to include
|
|
63
70
|
--strategy, -s <name> Deployment strategy:
|
|
@@ -556,423 +563,370 @@ See \`flux/README.md\` for detailed setup instructions.
|
|
|
556
563
|
`;
|
|
557
564
|
}
|
|
558
565
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
566
|
+
/** Render a component from the catalog with dummy values for its required props. */
|
|
567
|
+
function writeDummyTsx(comp, tag) {
|
|
568
|
+
const dummyValues = {
|
|
569
|
+
name: '"example"',
|
|
570
|
+
image: '"example/app:v1"',
|
|
571
|
+
host: '"example.com"',
|
|
572
|
+
serviceName: '"example"',
|
|
573
|
+
children: 'null',
|
|
574
|
+
selector: '{ app: "example" }',
|
|
575
|
+
};
|
|
576
|
+
const requiredProps = comp.props.filter((p) => p.required);
|
|
577
|
+
const propsStr = requiredProps
|
|
578
|
+
.map((p) => `${p.name}={${dummyValues[p.name] ?? '"dummy"'}}`)
|
|
579
|
+
.join(' ');
|
|
580
|
+
const tsx = `import { ${comp.name} } from '${comp.package}'\nexport default <${comp.name} ${propsStr} />\n`;
|
|
581
|
+
const tmpFile = (0, path_1.resolve)(`.r8s-${tag}-${Date.now()}.tsx`);
|
|
582
|
+
(0, fs_1.writeFileSync)(tmpFile, tsx, 'utf-8');
|
|
583
|
+
return tmpFile;
|
|
584
|
+
}
|
|
585
|
+
function removeQuietly(file) {
|
|
586
|
+
try {
|
|
587
|
+
require('fs').unlinkSync(file);
|
|
565
588
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const projectName = args[1] || 'r8s-app';
|
|
569
|
-
const template = options.template || 'basic';
|
|
570
|
-
const strategy = options.strategy || 'github-actions';
|
|
571
|
-
const operators = options.operators
|
|
572
|
-
?.split(',')
|
|
573
|
-
.map((op) => op.trim())
|
|
574
|
-
.filter(Boolean);
|
|
575
|
-
if (strategy !== 'github-actions' && strategy !== 'flux-controller') {
|
|
576
|
-
console.error(`Invalid strategy: ${strategy}. Valid: github-actions, flux-controller`);
|
|
577
|
-
process.exit(1);
|
|
578
|
-
}
|
|
579
|
-
try {
|
|
580
|
-
await initProject(projectName, template, strategy, operators);
|
|
581
|
-
}
|
|
582
|
-
catch (error) {
|
|
583
|
-
console.error('Error:', error instanceof Error ? error.message : error);
|
|
584
|
-
process.exit(1);
|
|
585
|
-
}
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
if (command === 'operators') {
|
|
589
|
-
try {
|
|
590
|
-
const entryFile = await findEntryFile(options.entry);
|
|
591
|
-
console.error(`Rendering operators from: ${entryFile}`);
|
|
592
|
-
const { renderToOperatorsYaml } = await import('./renderer.js');
|
|
593
|
-
const yamlOutput = await renderToOperatorsYaml(entryFile);
|
|
594
|
-
if (options.out) {
|
|
595
|
-
const { writeFileSync, mkdirSync } = await import('fs');
|
|
596
|
-
const { dirname } = await import('path');
|
|
597
|
-
mkdirSync(dirname((0, path_1.resolve)(options.out)), { recursive: true });
|
|
598
|
-
writeFileSync((0, path_1.resolve)(options.out), yamlOutput, 'utf-8');
|
|
599
|
-
console.error(`Output written to: ${(0, path_1.resolve)(options.out)}`);
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
console.log(yamlOutput);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
catch (error) {
|
|
606
|
-
console.error('Error:', error instanceof Error ? error.message : error);
|
|
607
|
-
process.exit(1);
|
|
608
|
-
}
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
if (command === 'list') {
|
|
612
|
-
const { allComponents, operators } = await import('./catalog.js');
|
|
613
|
-
const comps = allComponents();
|
|
614
|
-
console.log('\nComponents:\n');
|
|
615
|
-
const byCat = new Map();
|
|
616
|
-
for (const c of comps) {
|
|
617
|
-
const arr = byCat.get(c.category) ?? [];
|
|
618
|
-
arr.push(c);
|
|
619
|
-
byCat.set(c.category, arr);
|
|
620
|
-
}
|
|
621
|
-
for (const [cat, items] of byCat) {
|
|
622
|
-
console.log(` ${cat}:`);
|
|
623
|
-
for (const c of items) {
|
|
624
|
-
console.log(` ${c.name.padEnd(12)} ${c.package.padEnd(20)} ${c.description}`);
|
|
625
|
-
}
|
|
626
|
-
console.log();
|
|
627
|
-
}
|
|
628
|
-
console.log('Operators:\n');
|
|
629
|
-
for (const op of operators) {
|
|
630
|
-
console.log(` ${op.name.padEnd(24)} ${op.description}`);
|
|
631
|
-
}
|
|
632
|
-
console.log('\nUse "r8s info <name>" for props and examples.');
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
if (command === 'info') {
|
|
636
|
-
const name = args[1];
|
|
637
|
-
if (!name) {
|
|
638
|
-
console.error('Usage: r8s info <component-name>');
|
|
639
|
-
console.error('Example: r8s info App');
|
|
640
|
-
process.exit(1);
|
|
641
|
-
}
|
|
642
|
-
const { findComponent } = await import('./catalog.js');
|
|
643
|
-
const comp = findComponent(name);
|
|
644
|
-
if (!comp) {
|
|
645
|
-
console.error(`Component not found: ${name}`);
|
|
646
|
-
console.error('Use "r8s list" to see available components.');
|
|
647
|
-
process.exit(1);
|
|
648
|
-
}
|
|
649
|
-
console.log(`\n${comp.name} (${comp.package})`);
|
|
650
|
-
console.log(`${comp.category}`);
|
|
651
|
-
console.log(`\n${comp.description}\n`);
|
|
652
|
-
console.log('Props:');
|
|
653
|
-
for (const p of comp.props) {
|
|
654
|
-
const req = p.required ? 'required' : 'optional';
|
|
655
|
-
const def = p.default ? ` [default: ${p.default}]` : '';
|
|
656
|
-
console.log(` ${p.name.padEnd(16)} ${p.type.padEnd(36)} ${req}${def}`);
|
|
657
|
-
console.log(` ${' '.repeat(18)}${p.description}`);
|
|
658
|
-
}
|
|
659
|
-
console.log('\nExample:');
|
|
660
|
-
console.log(` ${comp.example}`);
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
if (command === 'context') {
|
|
664
|
-
const { allComponents, operators } = await import('./catalog.js');
|
|
665
|
-
const comps = allComponents();
|
|
666
|
-
console.log('# r8s context for LLMs\n');
|
|
667
|
-
console.log('## Workflow');
|
|
668
|
-
console.log('1. Write TSX that default-exports a JSX element');
|
|
669
|
-
console.log('2. Run: r8s render --entry <file.tsx> --out <file.yaml>');
|
|
670
|
-
console.log('3. Commit the YAML. GitOps (FluxCD/ArgoCD) applies it.');
|
|
671
|
-
console.log('4. Never hand-edit YAML — change TSX and re-render.\n');
|
|
672
|
-
console.log('## Rules');
|
|
673
|
-
console.log('- Lowercase elements (<deployment>, <service>) are raw K8s resources.');
|
|
674
|
-
console.log('- PascalCase elements (<App>, <Database>) are recipe components.');
|
|
675
|
-
console.log('- Components are TypeScript functions — testable with render() + vitest.');
|
|
676
|
-
console.log('- Entry file must default-export a JSX element or function.\n');
|
|
677
|
-
console.log('## Components\n');
|
|
678
|
-
for (const c of comps) {
|
|
679
|
-
const required = c.props.filter((p) => p.required).map((p) => `${p.name}: ${p.type}`);
|
|
680
|
-
console.log(`${c.name} (${c.package}) — ${c.description}`);
|
|
681
|
-
console.log(` Required: ${required.join(', ') || 'none'}`);
|
|
682
|
-
console.log(` Example: ${c.example.replace(/\n/g, ' ')}`);
|
|
683
|
-
console.log();
|
|
684
|
-
}
|
|
685
|
-
console.log('## Operators (auto-declared by recipes)');
|
|
686
|
-
for (const op of operators) {
|
|
687
|
-
console.log(` ${op.name} — ${op.description}`);
|
|
688
|
-
}
|
|
689
|
-
console.log('\n## Commands');
|
|
690
|
-
console.log(' r8s init [name] Scaffold a project');
|
|
691
|
-
console.log(' r8s render --entry f.tsx Render to stdout');
|
|
692
|
-
console.log(' r8s render --out k8s.yaml Render to file');
|
|
693
|
-
console.log(' r8s list List all components');
|
|
694
|
-
console.log(' r8s info <name> Show props for a component');
|
|
695
|
-
console.log(' r8s preview <name> Render a component with dummy props');
|
|
696
|
-
console.log(' r8s explain <name> Show resources + operators a component creates');
|
|
697
|
-
console.log(' r8s validate <file.tsx> Type-check + reference-check');
|
|
698
|
-
console.log(' r8s search <term> Search npm for community recipes');
|
|
699
|
-
console.log(' r8s add <package> Install a community recipe from npm');
|
|
700
|
-
console.log(' r8s context This output');
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
if (command === 'search') {
|
|
704
|
-
const term = args.slice(1).join(' ');
|
|
705
|
-
if (!term) {
|
|
706
|
-
console.error('Usage: r8s search <term>');
|
|
707
|
-
console.error('Example: r8s search database');
|
|
708
|
-
process.exit(1);
|
|
709
|
-
}
|
|
710
|
-
console.log(`Searching npm for r8s recipes matching "${term}"...\n`);
|
|
711
|
-
try {
|
|
712
|
-
const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(`keywords:r8s ${term}`)}&size=25`;
|
|
713
|
-
const res = await fetch(url);
|
|
714
|
-
const data = await res.json();
|
|
715
|
-
if (!data.objects || data.objects.length === 0) {
|
|
716
|
-
console.log('No packages found.');
|
|
717
|
-
console.log('\nTo publish a recipe, add "r8s" to the keywords in package.json.');
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
console.log('Package Version Description');
|
|
721
|
-
console.log('─'.repeat(80));
|
|
722
|
-
for (const obj of data.objects) {
|
|
723
|
-
const pkg = obj.package;
|
|
724
|
-
const name = pkg.name.padEnd(30);
|
|
725
|
-
const version = pkg.version.padEnd(10);
|
|
726
|
-
const desc = (pkg.description ?? '').substring(0, 38);
|
|
727
|
-
console.log(`${name} ${version} ${desc}`);
|
|
728
|
-
}
|
|
729
|
-
console.log(`\n${data.total} package(s) found.`);
|
|
730
|
-
console.log('Install with: r8s add <package-name>');
|
|
731
|
-
}
|
|
732
|
-
catch (error) {
|
|
733
|
-
console.error('Search failed:', error instanceof Error ? error.message : error);
|
|
734
|
-
process.exit(1);
|
|
735
|
-
}
|
|
736
|
-
return;
|
|
589
|
+
catch {
|
|
590
|
+
// best-effort cleanup of a temp preview file
|
|
737
591
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
745
|
-
console.log(`Installing ${packageName}...`);
|
|
746
|
-
const { execSync } = await import('child_process');
|
|
747
|
-
try {
|
|
748
|
-
execSync(`npm install ${packageName}`, { stdio: 'inherit' });
|
|
749
|
-
console.log(`\n✅ ${packageName} installed.`);
|
|
750
|
-
console.log(`Import components in your TSX:`);
|
|
751
|
-
console.log(` import { MyComponent } from '${packageName}'`);
|
|
752
|
-
}
|
|
753
|
-
catch (error) {
|
|
754
|
-
console.error('Install failed:', error instanceof Error ? error.message : error);
|
|
755
|
-
process.exit(1);
|
|
756
|
-
}
|
|
757
|
-
return;
|
|
592
|
+
}
|
|
593
|
+
/** Components must exist in the catalog — resolve names must too. */
|
|
594
|
+
function requireComponent(name, usage) {
|
|
595
|
+
if (!name) {
|
|
596
|
+
console.error(usage);
|
|
597
|
+
process.exit(1);
|
|
758
598
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
if (!name) {
|
|
762
|
-
console.error('Usage: r8s preview <component-name>');
|
|
763
|
-
console.error('Example: r8s preview App');
|
|
764
|
-
process.exit(1);
|
|
765
|
-
}
|
|
766
|
-
const { findComponent } = await import('./catalog.js');
|
|
599
|
+
// dynamic import kept local so `r8s list` stays cold-start fast
|
|
600
|
+
return import('./catalog.js').then(({ findComponent }) => {
|
|
767
601
|
const comp = findComponent(name);
|
|
768
602
|
if (!comp) {
|
|
769
603
|
console.error(`Component not found: ${name}`);
|
|
770
604
|
console.error('Use "r8s list" to see available components.');
|
|
771
605
|
process.exit(1);
|
|
772
606
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const tmpFile = (0, path_1.resolve)(`.r8s-preview-${Date.now()}.tsx`);
|
|
788
|
-
(0, fs_1.writeFileSync)(tmpFile, tsx, 'utf-8');
|
|
789
|
-
try {
|
|
790
|
-
const { renderToYaml } = await import('./renderer.js');
|
|
791
|
-
const yaml = await renderToYaml(tmpFile);
|
|
792
|
-
console.log(`# Preview of ${comp.name} with dummy required props\n`);
|
|
793
|
-
console.log(yaml);
|
|
794
|
-
}
|
|
795
|
-
catch (error) {
|
|
796
|
-
console.error('Preview failed:', error instanceof Error ? error.message : error);
|
|
797
|
-
console.error('\nThis component may require a Platform context or specific props.');
|
|
798
|
-
process.exit(1);
|
|
799
|
-
}
|
|
800
|
-
finally {
|
|
801
|
-
try {
|
|
802
|
-
require('fs').unlinkSync(tmpFile);
|
|
803
|
-
}
|
|
804
|
-
catch { }
|
|
805
|
-
}
|
|
806
|
-
return;
|
|
607
|
+
return comp;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
async function cmdInit({ args, options }) {
|
|
611
|
+
const projectName = args[1] || 'r8s-app';
|
|
612
|
+
const template = options.template || 'basic';
|
|
613
|
+
const strategy = options.strategy || 'github-actions';
|
|
614
|
+
const operators = options.operators
|
|
615
|
+
?.split(',')
|
|
616
|
+
.map((op) => op.trim())
|
|
617
|
+
.filter(Boolean);
|
|
618
|
+
if (strategy !== 'github-actions' && strategy !== 'flux-controller') {
|
|
619
|
+
console.error(`Invalid strategy: ${strategy}. Valid: github-actions, flux-controller`);
|
|
620
|
+
process.exit(1);
|
|
807
621
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
622
|
+
try {
|
|
623
|
+
await initProject(projectName, template, strategy, operators);
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
627
|
+
process.exit(1);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/** Render-to-file or stdout: the shared output path for render/operators. */
|
|
631
|
+
async function writeOutput(yaml, out) {
|
|
632
|
+
if (out) {
|
|
633
|
+
const { writeFileSync, mkdirSync } = await import('fs');
|
|
634
|
+
const { dirname } = await import('path');
|
|
635
|
+
mkdirSync(dirname((0, path_1.resolve)(out)), { recursive: true });
|
|
636
|
+
writeFileSync((0, path_1.resolve)(out), yaml, 'utf-8');
|
|
637
|
+
console.error(`Output written to: ${(0, path_1.resolve)(out)}`);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
console.log(yaml);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function failWith(prefix, error, sanitize = false) {
|
|
644
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
645
|
+
console.error(prefix, sanitize ? (0, core_1.sanitizeErrorMessage)(msg) : msg);
|
|
646
|
+
process.exit(1);
|
|
647
|
+
}
|
|
648
|
+
async function cmdOperators({ options }) {
|
|
649
|
+
try {
|
|
650
|
+
const entryFile = await findEntryFile(options.entry);
|
|
651
|
+
console.error(`Rendering operators from: ${entryFile}`);
|
|
652
|
+
const { renderToOperatorsYaml } = await import('./renderer.js');
|
|
653
|
+
await writeOutput(await renderToOperatorsYaml(entryFile), options.out);
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
failWith('Error:', error);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
async function cmdList() {
|
|
660
|
+
const { allComponents, operators } = await import('./catalog.js');
|
|
661
|
+
const comps = allComponents();
|
|
662
|
+
console.log('\nComponents:\n');
|
|
663
|
+
const byCat = new Map();
|
|
664
|
+
for (const c of comps) {
|
|
665
|
+
const arr = byCat.get(c.category) ?? [];
|
|
666
|
+
arr.push(c);
|
|
667
|
+
byCat.set(c.category, arr);
|
|
668
|
+
}
|
|
669
|
+
for (const [cat, items] of byCat) {
|
|
670
|
+
console.log(` ${cat}:`);
|
|
671
|
+
for (const c of items) {
|
|
672
|
+
console.log(` ${c.name.padEnd(12)} ${c.package.padEnd(20)} ${c.description}`);
|
|
859
673
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
674
|
+
console.log();
|
|
675
|
+
}
|
|
676
|
+
console.log('Operators:\n');
|
|
677
|
+
for (const op of operators) {
|
|
678
|
+
console.log(` ${op.name.padEnd(24)} ${op.description}`);
|
|
679
|
+
}
|
|
680
|
+
console.log('\nUse "r8s info <name>" for props and examples.');
|
|
681
|
+
}
|
|
682
|
+
async function cmdInfo({ args }) {
|
|
683
|
+
const comp = await requireComponent(args[1], 'Usage: r8s info <component-name>');
|
|
684
|
+
console.log(`\n${comp.name} (${comp.package})`);
|
|
685
|
+
console.log(`${comp.category}`);
|
|
686
|
+
console.log(`\n${comp.description}\n`);
|
|
687
|
+
console.log('Props:');
|
|
688
|
+
for (const p of comp.props) {
|
|
689
|
+
const req = p.required ? 'required' : 'optional';
|
|
690
|
+
const def = p.default ? ` [default: ${p.default}]` : '';
|
|
691
|
+
console.log(` ${p.name.padEnd(16)} ${p.type.padEnd(36)} ${req}${def}`);
|
|
692
|
+
console.log(` ${' '.repeat(18)}${p.description}`);
|
|
693
|
+
}
|
|
694
|
+
console.log('\nExample:');
|
|
695
|
+
console.log(` ${comp.example}`);
|
|
696
|
+
}
|
|
697
|
+
async function cmdContext() {
|
|
698
|
+
const { allComponents, operators } = await import('./catalog.js');
|
|
699
|
+
const comps = allComponents();
|
|
700
|
+
console.log('# r8s context for LLMs\n');
|
|
701
|
+
console.log('## Workflow');
|
|
702
|
+
console.log('1. Write TSX that default-exports a JSX element');
|
|
703
|
+
console.log('2. Run: r8s render --entry <file.tsx> --out <file.yaml>');
|
|
704
|
+
console.log('3. Commit the YAML. GitOps (FluxCD/ArgoCD) applies it.');
|
|
705
|
+
console.log('4. Never hand-edit YAML — change TSX and re-render.\n');
|
|
706
|
+
console.log('## Rules');
|
|
707
|
+
console.log('- Lowercase elements (<deployment>, <service>) are raw K8s resources.');
|
|
708
|
+
console.log('- PascalCase elements (<App>, <Database>) are recipe components.');
|
|
709
|
+
console.log('- Components are TypeScript functions — testable with render() + vitest.');
|
|
710
|
+
console.log('- Entry file must default-export a JSX element or function.\n');
|
|
711
|
+
console.log('## Components\n');
|
|
712
|
+
for (const c of comps) {
|
|
713
|
+
const required = c.props.filter((p) => p.required).map((p) => `${p.name}: ${p.type}`);
|
|
714
|
+
console.log(`${c.name} (${c.package}) — ${c.description}`);
|
|
715
|
+
console.log(` Required: ${required.join(', ') || 'none'}`);
|
|
716
|
+
console.log(` Example: ${c.example.replace(/\n/g, ' ')}`);
|
|
717
|
+
console.log();
|
|
718
|
+
}
|
|
719
|
+
console.log('## Operators (auto-declared by recipes)');
|
|
720
|
+
for (const op of operators) {
|
|
721
|
+
console.log(` ${op.name} — ${op.description}`);
|
|
722
|
+
}
|
|
723
|
+
console.log('\n## Commands');
|
|
724
|
+
console.log(' r8s init [name] Scaffold a project');
|
|
725
|
+
console.log(' r8s render --entry f.tsx Render to stdout');
|
|
726
|
+
console.log(' r8s render --out k8s.yaml Render to file');
|
|
727
|
+
console.log(' r8s list List all components');
|
|
728
|
+
console.log(' r8s info <name> Show props for a component');
|
|
729
|
+
console.log(' r8s preview <name> Render a component with dummy props');
|
|
730
|
+
console.log(' r8s explain <name> Show resources + operators a component creates');
|
|
731
|
+
console.log(' r8s validate <file.tsx> Type-check + reference-check');
|
|
732
|
+
console.log(' r8s search <term> Search npm for community recipes');
|
|
733
|
+
console.log(' r8s add <package> Install a community recipe from npm');
|
|
734
|
+
console.log(' r8s context This output');
|
|
735
|
+
}
|
|
736
|
+
async function cmdSearch({ args }) {
|
|
737
|
+
const term = args.slice(1).join(' ');
|
|
738
|
+
if (!term) {
|
|
739
|
+
console.error('Usage: r8s search <term>');
|
|
740
|
+
console.error('Example: r8s search database');
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
console.log(`Searching npm for r8s recipes matching "${term}"...\n`);
|
|
744
|
+
try {
|
|
745
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(`keywords:r8s ${term}`)}&size=25`;
|
|
746
|
+
const res = await fetch(url);
|
|
747
|
+
const data = await res.json();
|
|
748
|
+
if (!data.objects || data.objects.length === 0) {
|
|
749
|
+
console.log('No packages found.');
|
|
750
|
+
console.log('\nTo publish a recipe, add "r8s" to the keywords in package.json.');
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
console.log('Package Version Description');
|
|
754
|
+
console.log('─'.repeat(80));
|
|
755
|
+
for (const obj of data.objects) {
|
|
756
|
+
const pkg = obj.package;
|
|
757
|
+
console.log(`${pkg.name.padEnd(30)} ${String(pkg.version).padEnd(10)} ${(pkg.description ?? '').substring(0, 38)}`);
|
|
758
|
+
}
|
|
759
|
+
console.log(`\n${data.total} package(s) found.`);
|
|
760
|
+
console.log('Install with: r8s add <package-name>');
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
failWith('Search failed:', error);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
async function cmdAdd({ args }) {
|
|
767
|
+
const packageName = args[1];
|
|
768
|
+
if (!packageName) {
|
|
769
|
+
console.error('Usage: r8s add <package-name>');
|
|
770
|
+
console.error('Example: r8s add @acme/r8s-redis');
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
console.log(`Installing ${packageName}...`);
|
|
774
|
+
const { execSync } = await import('child_process');
|
|
775
|
+
try {
|
|
776
|
+
execSync(`npm install ${packageName}`, { stdio: 'inherit' });
|
|
777
|
+
console.log(`\n✅ ${packageName} installed.`);
|
|
778
|
+
console.log(`Import components in your TSX:`);
|
|
779
|
+
console.log(` import { MyComponent } from '${packageName}'`);
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
failWith('Install failed:', error);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async function cmdPreview({ args }) {
|
|
786
|
+
const comp = await requireComponent(args[1], 'Usage: r8s preview <component-name>');
|
|
787
|
+
const tmpFile = writeDummyTsx(comp, 'preview');
|
|
788
|
+
try {
|
|
789
|
+
const { renderToYaml } = await import('./renderer.js');
|
|
790
|
+
console.log(`# Preview of ${comp.name} with dummy required props\n`);
|
|
791
|
+
console.log(await renderToYaml(tmpFile));
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
console.error('Preview failed:', error instanceof Error ? error.message : error);
|
|
795
|
+
console.error('\nThis component may require a Platform context or specific props.');
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
finally {
|
|
799
|
+
removeQuietly(tmpFile);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async function cmdExplain({ args }) {
|
|
803
|
+
const comp = await requireComponent(args[1], 'Usage: r8s explain <component-name>');
|
|
804
|
+
const { operators } = await import('./catalog.js');
|
|
805
|
+
console.log(`\n${comp.name} (${comp.package})`);
|
|
806
|
+
console.log(`${comp.description}\n`);
|
|
807
|
+
const tmpFile = writeDummyTsx(comp, 'explain');
|
|
808
|
+
try {
|
|
809
|
+
const { bundleAndRender } = await import('./renderer.js');
|
|
810
|
+
const result = await bundleAndRender(tmpFile);
|
|
811
|
+
console.log('Resources created:');
|
|
812
|
+
for (const r of result.resources) {
|
|
813
|
+
console.log(` ${r.kind.padEnd(24)} ${r.metadata?.namespace ?? ''}/${r.metadata?.name ?? ''}`);
|
|
814
|
+
}
|
|
815
|
+
if (result.operators.length > 0) {
|
|
816
|
+
console.log('\nOperators required:');
|
|
817
|
+
for (const op of result.operators) {
|
|
818
|
+
const meta = operators.find((o) => o.name === op.name);
|
|
819
|
+
console.log(` ${op.name.padEnd(24)} ${meta?.description ?? ''}`);
|
|
863
820
|
}
|
|
864
|
-
catch { }
|
|
865
821
|
}
|
|
866
|
-
|
|
822
|
+
console.log(`\n${result.resources.length} resource(s), ${result.operators.length} operator(s).`);
|
|
867
823
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
stdio: 'pipe',
|
|
891
|
-
cwd: process.cwd(),
|
|
892
|
-
});
|
|
893
|
-
console.log('✅ TypeScript: no errors');
|
|
894
|
-
}
|
|
895
|
-
catch (error) {
|
|
896
|
-
const stdout = error.stdout?.toString() ?? '';
|
|
897
|
-
const stderr = error.stderr?.toString() ?? '';
|
|
898
|
-
console.error('❌ TypeScript errors:');
|
|
899
|
-
console.error(stdout || stderr || error.message);
|
|
900
|
-
process.exit(1);
|
|
901
|
-
}
|
|
902
|
-
// 2. Render and check references
|
|
903
|
-
try {
|
|
904
|
-
const { bundleAndRender } = await import('./renderer.js');
|
|
905
|
-
const result = await bundleAndRender(resolved);
|
|
906
|
-
const resources = result.resources;
|
|
907
|
-
const names = new Set(resources.map((r) => `${r.kind}/${r.metadata?.namespace ?? ''}/${r.metadata?.name ?? ''}`));
|
|
908
|
-
const issues = [];
|
|
909
|
-
// Check HTTPRoute backendRefs
|
|
910
|
-
for (const route of resources.filter((r) => r.kind === 'HTTPRoute' || r.kind === 'Ingress')) {
|
|
911
|
-
const refs = route.kind === 'HTTPRoute'
|
|
912
|
-
? (route.spec?.rules?.flatMap((r) => r.backendRefs ?? []) ?? [])
|
|
913
|
-
: (route.spec?.rules?.flatMap((r) => r.http?.paths?.map((p) => p.backend?.service) ?? []) ?? []);
|
|
914
|
-
for (const ref of refs) {
|
|
915
|
-
const svcName = ref.name;
|
|
916
|
-
const svc = resources.find((r) => r.kind === 'Service' && r.metadata.name === svcName);
|
|
917
|
-
if (!svc) {
|
|
918
|
-
issues.push(`⚠️ ${route.kind} ${route.metadata.name} → Service "${svcName}" not found (operator-managed?)`);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
// Check Deployment volume refs
|
|
923
|
-
for (const d of resources.filter((r) => r.kind === 'Deployment' || r.kind === 'StatefulSet')) {
|
|
924
|
-
const vols = d.spec?.template?.spec?.volumes ?? [];
|
|
925
|
-
for (const vol of vols) {
|
|
926
|
-
if (vol.secret) {
|
|
927
|
-
const sec = resources.find((r) => r.kind === 'Secret' && r.metadata.name === vol.secret.secretName);
|
|
928
|
-
if (!sec)
|
|
929
|
-
issues.push(`⚠️ ${d.kind} ${d.metadata.name} → Secret "${vol.secret.secretName}" not found`);
|
|
930
|
-
}
|
|
931
|
-
if (vol.configMap) {
|
|
932
|
-
const cm = resources.find((r) => r.kind === 'ConfigMap' && r.metadata.name === vol.configMap.name);
|
|
933
|
-
if (!cm)
|
|
934
|
-
issues.push(`⚠️ ${d.kind} ${d.metadata.name} → ConfigMap "${vol.configMap.name}" not found`);
|
|
935
|
-
}
|
|
936
|
-
if (vol.persistentVolumeClaim) {
|
|
937
|
-
const pvc = resources.find((r) => r.kind === 'PersistentVolumeClaim' &&
|
|
938
|
-
r.metadata.name === vol.persistentVolumeClaim.claimName);
|
|
939
|
-
if (!pvc)
|
|
940
|
-
issues.push(`⚠️ ${d.kind} ${d.metadata.name} → PVC "${vol.persistentVolumeClaim.claimName}" not found`);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
// Check empty DNSEndpoint targets
|
|
945
|
-
for (const dns of resources.filter((r) => r.kind === 'DNSEndpoint')) {
|
|
946
|
-
for (const ep of dns.spec?.endpoints ?? []) {
|
|
947
|
-
if (!ep.targets || ep.targets.length === 0) {
|
|
948
|
-
issues.push(`⚠️ DNSEndpoint ${dns.metadata.name} has empty targets`);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
console.log(`✅ Render: ${resources.length} resources, ${result.operators.length} operators`);
|
|
953
|
-
if (issues.length > 0) {
|
|
954
|
-
console.log(`\n${issues.length} reference issue(s) found:`);
|
|
955
|
-
for (const issue of issues) {
|
|
956
|
-
console.log(` ${issue}`);
|
|
957
|
-
}
|
|
958
|
-
console.log('\nSome references may be operator-managed (e.g. Keycloak Service).');
|
|
959
|
-
process.exit(1);
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
console.log('✅ References: all resolved');
|
|
824
|
+
catch (error) {
|
|
825
|
+
console.error('Explain failed:', error instanceof Error ? error.message : error);
|
|
826
|
+
console.error('\nThis component may require a Platform context.');
|
|
827
|
+
process.exit(1);
|
|
828
|
+
}
|
|
829
|
+
finally {
|
|
830
|
+
removeQuietly(tmpFile);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
/** Reference checks after render: routes→Services, volumes→Secrets/ConfigMaps/PVCs, DNS targets. */
|
|
834
|
+
function missingKinds(resources) {
|
|
835
|
+
return (kind, name) => !resources.some((r) => r.kind === kind && r.metadata?.name === name);
|
|
836
|
+
}
|
|
837
|
+
function routeIssues(resources, missing) {
|
|
838
|
+
const issues = [];
|
|
839
|
+
for (const route of resources.filter((r) => r.kind === 'HTTPRoute' || r.kind === 'Ingress')) {
|
|
840
|
+
const refs = route.kind === 'HTTPRoute'
|
|
841
|
+
? (route.spec?.rules?.flatMap((r) => r.backendRefs ?? []) ?? [])
|
|
842
|
+
: (route.spec?.rules?.flatMap((r) => r.http?.paths?.map((p) => p.backend?.service) ?? []) ?? []);
|
|
843
|
+
for (const ref of refs) {
|
|
844
|
+
if (missing('Service', ref.name)) {
|
|
845
|
+
issues.push(`⚠️ ${route.kind} ${route.metadata.name} → Service "${ref.name}" not found (operator-managed?)`);
|
|
963
846
|
}
|
|
964
847
|
}
|
|
965
|
-
|
|
966
|
-
|
|
848
|
+
}
|
|
849
|
+
return issues;
|
|
850
|
+
}
|
|
851
|
+
function volumeIssues(resources, missing) {
|
|
852
|
+
const targetsOf = (vol) => [
|
|
853
|
+
[vol.secret?.secretName, 'Secret'],
|
|
854
|
+
[vol.configMap?.name, 'ConfigMap'],
|
|
855
|
+
[vol.persistentVolumeClaim?.claimName, 'PersistentVolumeClaim'],
|
|
856
|
+
];
|
|
857
|
+
const issuesFor = (d, vol) => targetsOf(vol)
|
|
858
|
+
.filter(([target, kind]) => target !== undefined && missing(kind, target))
|
|
859
|
+
.map(([target, kind]) => `⚠️ ${d.kind} ${d.metadata.name} → ${kind} "${target}" not found`);
|
|
860
|
+
return resources
|
|
861
|
+
.filter((r) => r.kind === 'Deployment' || r.kind === 'StatefulSet')
|
|
862
|
+
.flatMap((d) => (d.spec?.template?.spec?.volumes ?? []).flatMap((vol) => issuesFor(d, vol)));
|
|
863
|
+
}
|
|
864
|
+
function dnsIssues(resources) {
|
|
865
|
+
return resources
|
|
866
|
+
.filter((r) => r.kind === 'DNSEndpoint')
|
|
867
|
+
.flatMap((dns) => dns.spec?.endpoints ?? [])
|
|
868
|
+
.filter((ep) => !ep.targets || ep.targets.length === 0)
|
|
869
|
+
.map((ep) => `⚠️ DNSEndpoint ${ep.dnsName ?? ''} has empty targets`);
|
|
870
|
+
}
|
|
871
|
+
function referenceIssues(resources) {
|
|
872
|
+
const missing = missingKinds(resources);
|
|
873
|
+
return [
|
|
874
|
+
...routeIssues(resources, missing),
|
|
875
|
+
...volumeIssues(resources, missing),
|
|
876
|
+
...dnsIssues(resources),
|
|
877
|
+
];
|
|
878
|
+
}
|
|
879
|
+
function typecheckEntry(resolved) {
|
|
880
|
+
try {
|
|
881
|
+
const { execSync } = require('child_process');
|
|
882
|
+
const tsconfigPath = (0, path_1.resolve)('tsconfig.json');
|
|
883
|
+
const tscCmd = (0, fs_1.existsSync)(tsconfigPath)
|
|
884
|
+
? `npx tsc --noEmit -p ${tsconfigPath}`
|
|
885
|
+
: `npx tsc --noEmit --jsx react-jsx --jsxImportSource @r8s/core --moduleResolution bundler --target es2022 --module esnext ${resolved}`;
|
|
886
|
+
execSync(tscCmd, { stdio: 'pipe', cwd: process.cwd() });
|
|
887
|
+
console.log('✅ TypeScript: no errors');
|
|
888
|
+
}
|
|
889
|
+
catch (error) {
|
|
890
|
+
const stdout = error.stdout?.toString() ?? '';
|
|
891
|
+
const stderr = error.stderr?.toString() ?? '';
|
|
892
|
+
console.error('❌ TypeScript errors:');
|
|
893
|
+
console.error(stdout || stderr || error.message);
|
|
894
|
+
process.exit(1);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
async function cmdValidate({ args }) {
|
|
898
|
+
const entryFile = args[1];
|
|
899
|
+
if (!entryFile) {
|
|
900
|
+
console.error('Usage: r8s validate <file.tsx>');
|
|
901
|
+
console.error('Example: r8s validate infra/app.tsx');
|
|
902
|
+
process.exit(1);
|
|
903
|
+
}
|
|
904
|
+
const resolved = (0, path_1.resolve)(entryFile);
|
|
905
|
+
if (!(0, fs_1.existsSync)(resolved)) {
|
|
906
|
+
console.error(`File not found: ${resolved}`);
|
|
907
|
+
process.exit(1);
|
|
908
|
+
}
|
|
909
|
+
console.log(`Validating: ${resolved}\n`);
|
|
910
|
+
typecheckEntry(resolved);
|
|
911
|
+
try {
|
|
912
|
+
const { bundleAndRender } = await import('./renderer.js');
|
|
913
|
+
const result = await bundleAndRender(resolved);
|
|
914
|
+
const issues = referenceIssues(result.resources);
|
|
915
|
+
console.log(`✅ Render: ${result.resources.length} resources, ${result.operators.length} operators`);
|
|
916
|
+
if (issues.length > 0) {
|
|
917
|
+
console.log(`\n${issues.length} reference issue(s) found:`);
|
|
918
|
+
for (const issue of issues)
|
|
919
|
+
console.log(` ${issue}`);
|
|
920
|
+
console.log('\nSome references may be operator-managed (e.g. Keycloak Service).');
|
|
967
921
|
process.exit(1);
|
|
968
922
|
}
|
|
969
|
-
|
|
923
|
+
console.log('✅ References: all resolved');
|
|
970
924
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
showHelp();
|
|
974
|
-
process.exit(1);
|
|
925
|
+
catch (error) {
|
|
926
|
+
failWith('❌ Render failed:', error, true);
|
|
975
927
|
}
|
|
928
|
+
}
|
|
929
|
+
async function cmdRender({ options }) {
|
|
976
930
|
try {
|
|
977
931
|
const entryFile = await findEntryFile(options.entry);
|
|
978
932
|
console.error(`Rendering: ${entryFile}`);
|
|
@@ -980,21 +934,54 @@ async function main() {
|
|
|
980
934
|
const yamlOutput = await renderToYaml(entryFile, {
|
|
981
935
|
includeOperators: options.includeOperators,
|
|
982
936
|
operatorsOnly: options.operatorsOnly,
|
|
937
|
+
skipSecretGuardrails: options.skipSecretGuardrails,
|
|
938
|
+
// Skipping the guardrail is explicit consent, but stdout is a log
|
|
939
|
+
// channel (CI output) — credentials are masked there. --out files
|
|
940
|
+
// stay faithful so local dev workflows still apply real values.
|
|
941
|
+
redactSecrets: options.skipSecretGuardrails && !options.out,
|
|
983
942
|
});
|
|
984
|
-
|
|
985
|
-
const { writeFileSync, mkdirSync } = await import('fs');
|
|
986
|
-
const { dirname } = await import('path');
|
|
987
|
-
mkdirSync(dirname((0, path_1.resolve)(options.out)), { recursive: true });
|
|
988
|
-
writeFileSync((0, path_1.resolve)(options.out), yamlOutput, 'utf-8');
|
|
989
|
-
console.error(`Output written to: ${(0, path_1.resolve)(options.out)}`);
|
|
990
|
-
}
|
|
991
|
-
else {
|
|
992
|
-
console.log(yamlOutput);
|
|
993
|
-
}
|
|
943
|
+
await writeOutput(yamlOutput, options.out);
|
|
994
944
|
}
|
|
995
945
|
catch (error) {
|
|
996
|
-
|
|
997
|
-
|
|
946
|
+
failWith('Error:', error, true);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async function main() {
|
|
950
|
+
const args = process.argv.slice(2);
|
|
951
|
+
const options = parseArgs(args);
|
|
952
|
+
if (options.help) {
|
|
953
|
+
showHelp();
|
|
954
|
+
process.exit(0);
|
|
955
|
+
}
|
|
956
|
+
const ctx = { args, options };
|
|
957
|
+
switch (args[0] || 'render') {
|
|
958
|
+
case 'init':
|
|
959
|
+
return cmdInit(ctx);
|
|
960
|
+
case 'operators':
|
|
961
|
+
return cmdOperators(ctx);
|
|
962
|
+
case 'list':
|
|
963
|
+
return cmdList();
|
|
964
|
+
case 'info':
|
|
965
|
+
return cmdInfo(ctx);
|
|
966
|
+
case 'context':
|
|
967
|
+
return cmdContext();
|
|
968
|
+
case 'search':
|
|
969
|
+
return cmdSearch(ctx);
|
|
970
|
+
case 'add':
|
|
971
|
+
return cmdAdd(ctx);
|
|
972
|
+
case 'preview':
|
|
973
|
+
return cmdPreview(ctx);
|
|
974
|
+
case 'explain':
|
|
975
|
+
return cmdExplain(ctx);
|
|
976
|
+
case 'validate':
|
|
977
|
+
return cmdValidate(ctx);
|
|
978
|
+
case 'render':
|
|
979
|
+
return cmdRender(ctx);
|
|
980
|
+
default: {
|
|
981
|
+
console.error(`Unknown command: ${args[0]}`);
|
|
982
|
+
showHelp();
|
|
983
|
+
process.exit(1);
|
|
984
|
+
}
|
|
998
985
|
}
|
|
999
986
|
}
|
|
1000
987
|
main();
|