@rcrsr/rill-cli 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/dist/check/config.d.ts +20 -0
  3. package/dist/check/config.d.ts.map +1 -0
  4. package/dist/check/config.js +151 -0
  5. package/dist/check/config.js.map +1 -0
  6. package/dist/check/fixer.d.ts +39 -0
  7. package/dist/check/fixer.d.ts.map +1 -0
  8. package/dist/check/fixer.js +119 -0
  9. package/dist/check/fixer.js.map +1 -0
  10. package/dist/check/index.d.ts +10 -0
  11. package/dist/check/index.d.ts.map +1 -0
  12. package/dist/check/index.js +21 -0
  13. package/dist/check/index.js.map +1 -0
  14. package/dist/check/rules/anti-patterns.d.ts +65 -0
  15. package/dist/check/rules/anti-patterns.d.ts.map +1 -0
  16. package/dist/check/rules/anti-patterns.js +481 -0
  17. package/dist/check/rules/anti-patterns.js.map +1 -0
  18. package/dist/check/rules/closures.d.ts +66 -0
  19. package/dist/check/rules/closures.d.ts.map +1 -0
  20. package/dist/check/rules/closures.js +370 -0
  21. package/dist/check/rules/closures.js.map +1 -0
  22. package/dist/check/rules/collections.d.ts +90 -0
  23. package/dist/check/rules/collections.d.ts.map +1 -0
  24. package/dist/check/rules/collections.js +373 -0
  25. package/dist/check/rules/collections.js.map +1 -0
  26. package/dist/check/rules/conditionals.d.ts +41 -0
  27. package/dist/check/rules/conditionals.d.ts.map +1 -0
  28. package/dist/check/rules/conditionals.js +134 -0
  29. package/dist/check/rules/conditionals.js.map +1 -0
  30. package/dist/check/rules/flow.d.ts +46 -0
  31. package/dist/check/rules/flow.d.ts.map +1 -0
  32. package/dist/check/rules/flow.js +206 -0
  33. package/dist/check/rules/flow.js.map +1 -0
  34. package/dist/check/rules/formatting.d.ts +143 -0
  35. package/dist/check/rules/formatting.d.ts.map +1 -0
  36. package/dist/check/rules/formatting.js +656 -0
  37. package/dist/check/rules/formatting.js.map +1 -0
  38. package/dist/check/rules/helpers.d.ts +26 -0
  39. package/dist/check/rules/helpers.d.ts.map +1 -0
  40. package/dist/check/rules/helpers.js +66 -0
  41. package/dist/check/rules/helpers.js.map +1 -0
  42. package/dist/check/rules/index.d.ts +21 -0
  43. package/dist/check/rules/index.d.ts.map +1 -0
  44. package/dist/check/rules/index.js +78 -0
  45. package/dist/check/rules/index.js.map +1 -0
  46. package/dist/check/rules/loops.d.ts +77 -0
  47. package/dist/check/rules/loops.d.ts.map +1 -0
  48. package/dist/check/rules/loops.js +310 -0
  49. package/dist/check/rules/loops.js.map +1 -0
  50. package/dist/check/rules/naming.d.ts +21 -0
  51. package/dist/check/rules/naming.d.ts.map +1 -0
  52. package/dist/check/rules/naming.js +174 -0
  53. package/dist/check/rules/naming.js.map +1 -0
  54. package/dist/check/rules/strings.d.ts +28 -0
  55. package/dist/check/rules/strings.d.ts.map +1 -0
  56. package/dist/check/rules/strings.js +79 -0
  57. package/dist/check/rules/strings.js.map +1 -0
  58. package/dist/check/rules/types.d.ts +41 -0
  59. package/dist/check/rules/types.d.ts.map +1 -0
  60. package/dist/check/rules/types.js +167 -0
  61. package/dist/check/rules/types.js.map +1 -0
  62. package/dist/check/types.d.ts +112 -0
  63. package/dist/check/types.d.ts.map +1 -0
  64. package/dist/check/types.js +6 -0
  65. package/dist/check/types.js.map +1 -0
  66. package/dist/check/validator.d.ts +18 -0
  67. package/dist/check/validator.d.ts.map +1 -0
  68. package/dist/check/validator.js +110 -0
  69. package/dist/check/validator.js.map +1 -0
  70. package/dist/check/visitor.d.ts +33 -0
  71. package/dist/check/visitor.d.ts.map +1 -0
  72. package/dist/check/visitor.js +259 -0
  73. package/dist/check/visitor.js.map +1 -0
  74. package/dist/cli-check.d.ts +43 -0
  75. package/dist/cli-check.d.ts.map +1 -0
  76. package/dist/cli-check.js +366 -0
  77. package/dist/cli-check.js.map +1 -0
  78. package/dist/cli-error-enrichment.d.ts +73 -0
  79. package/dist/cli-error-enrichment.d.ts.map +1 -0
  80. package/dist/cli-error-enrichment.js +205 -0
  81. package/dist/cli-error-enrichment.js.map +1 -0
  82. package/dist/cli-error-formatter.d.ts +45 -0
  83. package/dist/cli-error-formatter.d.ts.map +1 -0
  84. package/dist/cli-error-formatter.js +218 -0
  85. package/dist/cli-error-formatter.js.map +1 -0
  86. package/dist/cli-eval.d.ts +15 -0
  87. package/dist/cli-eval.d.ts.map +1 -0
  88. package/dist/cli-eval.js +116 -0
  89. package/dist/cli-eval.js.map +1 -0
  90. package/dist/cli-exec.d.ts +58 -0
  91. package/dist/cli-exec.d.ts.map +1 -0
  92. package/dist/cli-exec.js +326 -0
  93. package/dist/cli-exec.js.map +1 -0
  94. package/dist/cli-explain.d.ts +24 -0
  95. package/dist/cli-explain.d.ts.map +1 -0
  96. package/dist/cli-explain.js +68 -0
  97. package/dist/cli-explain.js.map +1 -0
  98. package/dist/cli-lsp-diagnostic.d.ts +35 -0
  99. package/dist/cli-lsp-diagnostic.d.ts.map +1 -0
  100. package/dist/cli-lsp-diagnostic.js +98 -0
  101. package/dist/cli-lsp-diagnostic.js.map +1 -0
  102. package/dist/cli-module-loader.d.ts +19 -0
  103. package/dist/cli-module-loader.d.ts.map +1 -0
  104. package/dist/cli-module-loader.js +83 -0
  105. package/dist/cli-module-loader.js.map +1 -0
  106. package/dist/cli-shared.d.ts +62 -0
  107. package/dist/cli-shared.d.ts.map +1 -0
  108. package/dist/cli-shared.js +158 -0
  109. package/dist/cli-shared.js.map +1 -0
  110. package/dist/cli.d.ts +13 -0
  111. package/dist/cli.d.ts.map +1 -0
  112. package/dist/cli.js +62 -0
  113. package/dist/cli.js.map +1 -0
  114. package/dist/test-internal-import.d.ts +2 -0
  115. package/dist/test-internal-import.d.ts.map +1 -0
  116. package/dist/test-internal-import.js +7 -0
  117. package/dist/test-internal-import.js.map +1 -0
  118. package/package.json +24 -0
  119. package/src/check/config.ts +202 -0
  120. package/src/check/fixer.ts +174 -0
  121. package/src/check/index.ts +39 -0
  122. package/src/check/rules/anti-patterns.ts +585 -0
  123. package/src/check/rules/closures.ts +445 -0
  124. package/src/check/rules/collections.ts +437 -0
  125. package/src/check/rules/conditionals.ts +155 -0
  126. package/src/check/rules/flow.ts +262 -0
  127. package/src/check/rules/formatting.ts +811 -0
  128. package/src/check/rules/helpers.ts +89 -0
  129. package/src/check/rules/index.ts +140 -0
  130. package/src/check/rules/loops.ts +372 -0
  131. package/src/check/rules/naming.ts +242 -0
  132. package/src/check/rules/strings.ts +104 -0
  133. package/src/check/rules/types.ts +214 -0
  134. package/src/check/types.ts +163 -0
  135. package/src/check/validator.ts +136 -0
  136. package/src/check/visitor.ts +338 -0
  137. package/src/cli-check.ts +456 -0
  138. package/src/cli-error-enrichment.ts +274 -0
  139. package/src/cli-error-formatter.ts +313 -0
  140. package/src/cli-eval.ts +145 -0
  141. package/src/cli-exec.ts +408 -0
  142. package/src/cli-explain.ts +76 -0
  143. package/src/cli-lsp-diagnostic.ts +132 -0
  144. package/src/cli-module-loader.ts +101 -0
  145. package/src/cli-shared.ts +187 -0
  146. package/tests/check/cli-check.test.ts +189 -0
  147. package/tests/check/config.test.ts +350 -0
  148. package/tests/check/fixer.test.ts +373 -0
  149. package/tests/check/format-diagnostics.test.ts +327 -0
  150. package/tests/check/rules/anti-patterns.test.ts +467 -0
  151. package/tests/check/rules/closures.test.ts +192 -0
  152. package/tests/check/rules/collections.test.ts +380 -0
  153. package/tests/check/rules/conditionals.test.ts +185 -0
  154. package/tests/check/rules/flow.test.ts +250 -0
  155. package/tests/check/rules/formatting.test.ts +755 -0
  156. package/tests/check/rules/loops.test.ts +334 -0
  157. package/tests/check/rules/naming.test.ts +336 -0
  158. package/tests/check/rules/strings.test.ts +129 -0
  159. package/tests/check/rules/types.test.ts +257 -0
  160. package/tests/check/validator.test.ts +444 -0
  161. package/tests/check/visitor.test.ts +171 -0
  162. package/tests/cli/check.test.ts +801 -0
  163. package/tests/cli/error-enrichment.test.ts +510 -0
  164. package/tests/cli/error-formatter.test.ts +631 -0
  165. package/tests/cli/eval.test.ts +85 -0
  166. package/tests/cli/exec.test.ts +537 -0
  167. package/tests/cli-explain.test.ts +249 -0
  168. package/tests/cli-lsp-diagnostic.test.ts +202 -0
  169. package/tests/cli-shared.test.ts +439 -0
  170. package/tsconfig.json +9 -0
  171. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * CLI Module Loader
3
+ *
4
+ * Implements module loading for the Rill CLI with circular dependency detection.
5
+ * See docs/integration-modules.md for module convention specification.
6
+ */
7
+ import * as fs from 'fs/promises';
8
+ import * as path from 'path';
9
+ import * as yaml from 'yaml';
10
+ import { parse } from '@rcrsr/rill';
11
+ import { execute, createRuntimeContext } from '@rcrsr/rill';
12
+ /**
13
+ * Load a module and its dependencies recursively.
14
+ *
15
+ * @param specifier - Module path (relative or absolute)
16
+ * @param fromPath - Path of the importing file
17
+ * @param cache - Module cache keyed by canonical path
18
+ * @param chain - Set of paths in current import chain for circular detection
19
+ * @returns Dict of exported values
20
+ * @throws Error if module not found or circular dependency detected
21
+ */
22
+ export async function loadModule(specifier, fromPath, cache, chain = new Set()) {
23
+ // Resolve to absolute canonical path
24
+ const absolutePath = path.resolve(path.dirname(fromPath), specifier);
25
+ // Check for circular dependency
26
+ if (chain.has(absolutePath)) {
27
+ const cycle = [...chain, absolutePath].join(' -> ');
28
+ throw new Error(`Circular dependency detected: ${cycle}`);
29
+ }
30
+ // Return cached module if already loaded
31
+ if (cache.has(absolutePath)) {
32
+ return cache.get(absolutePath);
33
+ }
34
+ // Check if module file exists
35
+ try {
36
+ await fs.access(absolutePath);
37
+ }
38
+ catch {
39
+ throw new Error(`Module not found: ${specifier}`);
40
+ }
41
+ // Add to chain to detect cycles in dependencies
42
+ chain.add(absolutePath);
43
+ try {
44
+ // Load and parse module source
45
+ const source = await fs.readFile(absolutePath, 'utf-8');
46
+ const ast = parse(source);
47
+ // Extract frontmatter (yaml.parse returns null for empty content)
48
+ const frontmatter = ast.frontmatter
49
+ ? (yaml.parse(ast.frontmatter.content) ?? {})
50
+ : {};
51
+ // Resolve dependencies first
52
+ const imports = {};
53
+ if (frontmatter['use'] && Array.isArray(frontmatter['use'])) {
54
+ for (const entry of frontmatter['use']) {
55
+ if (typeof entry === 'object' && entry !== null) {
56
+ const [name, depPath] = Object.entries(entry)[0];
57
+ imports[name] = await loadModule(depPath, absolutePath, cache, chain);
58
+ }
59
+ }
60
+ }
61
+ // Execute module with dependencies
62
+ const ctx = createRuntimeContext({ variables: imports });
63
+ const result = await execute(ast, ctx);
64
+ // Extract exports
65
+ const exports = {};
66
+ const exportList = frontmatter['export'];
67
+ if (Array.isArray(exportList)) {
68
+ for (const name of exportList) {
69
+ if (typeof name === 'string' && result.variables[name] !== undefined) {
70
+ exports[name] = result.variables[name];
71
+ }
72
+ }
73
+ }
74
+ // Cache and return
75
+ cache.set(absolutePath, exports);
76
+ return exports;
77
+ }
78
+ finally {
79
+ // Remove from chain after processing
80
+ chain.delete(absolutePath);
81
+ }
82
+ }
83
+ //# sourceMappingURL=cli-module-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-module-loader.js","sourceRoot":"","sources":["../src/cli-module-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * CLI Shared Utilities
3
+ * Common formatting functions for CLI tools
4
+ */
5
+ import { VERSION } from '@rcrsr/rill';
6
+ import type { RillValue } from '@rcrsr/rill';
7
+ import { type ScopeInfo } from './cli-error-enrichment.js';
8
+ import { type FormatOptions } from './cli-error-formatter.js';
9
+ /**
10
+ * Convert execution result to human-readable string
11
+ *
12
+ * @param value - The value to format
13
+ * @returns Formatted string representation
14
+ */
15
+ export declare function formatOutput(value: RillValue): string;
16
+ /**
17
+ * Format error for stderr output
18
+ *
19
+ * When source is available, uses enrichment pipeline to add source snippets and suggestions.
20
+ * Otherwise, falls back to simple formatting for backward compatibility.
21
+ *
22
+ * @param err - The error to format
23
+ * @param source - Optional source code for enrichment
24
+ * @param options - Optional format options (defaults to human format)
25
+ * @param scope - Optional scope information for suggestions
26
+ * @returns Formatted error message
27
+ */
28
+ export declare function formatError(err: Error, source?: string, options?: Partial<FormatOptions>, scope?: ScopeInfo): string;
29
+ /**
30
+ * Determine exit code from script result
31
+ *
32
+ * Implements exit code semantics per language spec:
33
+ * - true / non-empty string: exit 0
34
+ * - false / empty string: exit 1
35
+ * - [0, "message"]: exit 0 with message
36
+ * - [1, "message"]: exit 1 with message
37
+ *
38
+ * @param value - The script return value
39
+ * @returns Exit code and optional message
40
+ */
41
+ export declare function determineExitCode(value: RillValue): {
42
+ code: number;
43
+ message?: string;
44
+ };
45
+ /**
46
+ * Detect help or version flags in CLI argument array.
47
+ * Checks for --help, -h, --version, -v in any position.
48
+ *
49
+ * @param argv - Command-line arguments (process.argv.slice(2))
50
+ * @returns Object with mode if flag found, null otherwise
51
+ */
52
+ export declare function detectHelpVersionFlag(argv: string[]): {
53
+ mode: 'help' | 'version';
54
+ } | null;
55
+ /**
56
+ * Package version string (re-exported from version-data.ts)
57
+ *
58
+ * This replaces the previous async readVersion() function with a synchronous constant.
59
+ * The version is now generated at build time by packages/core/scripts/generate-version.ts.
60
+ */
61
+ export { VERSION };
62
+ //# sourceMappingURL=cli-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-shared.d.ts","sourceRoot":"","sources":["../src/cli-shared.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAc,OAAO,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAElC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAQrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,KAAK,EACV,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,EAChC,KAAK,CAAC,EAAE,SAAS,GAChB,MAAM,CA6DR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAgCA;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EAAE,GACb;IAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG,IAAI,CASrC;AAED;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * CLI Shared Utilities
3
+ * Common formatting functions for CLI tools
4
+ */
5
+ import { isCallable, VERSION } from '@rcrsr/rill';
6
+ import { ParseError, RuntimeError } from '@rcrsr/rill';
7
+ import { LexerError } from '@rcrsr/rill';
8
+ import { enrichError } from './cli-error-enrichment.js';
9
+ import { formatError as formatEnrichedError, } from './cli-error-formatter.js';
10
+ /**
11
+ * Convert execution result to human-readable string
12
+ *
13
+ * @param value - The value to format
14
+ * @returns Formatted string representation
15
+ */
16
+ export function formatOutput(value) {
17
+ if (value === null)
18
+ return 'null';
19
+ if (typeof value === 'string')
20
+ return value;
21
+ if (typeof value === 'number' || typeof value === 'boolean') {
22
+ return String(value);
23
+ }
24
+ if (isCallable(value))
25
+ return '[closure]';
26
+ return JSON.stringify(value, null, 2);
27
+ }
28
+ /**
29
+ * Format error for stderr output
30
+ *
31
+ * When source is available, uses enrichment pipeline to add source snippets and suggestions.
32
+ * Otherwise, falls back to simple formatting for backward compatibility.
33
+ *
34
+ * @param err - The error to format
35
+ * @param source - Optional source code for enrichment
36
+ * @param options - Optional format options (defaults to human format)
37
+ * @param scope - Optional scope information for suggestions
38
+ * @returns Formatted error message
39
+ */
40
+ export function formatError(err, source, options, scope) {
41
+ // IC-12: Use enrichment pipeline when source is available and error is RillError
42
+ if (source !== undefined &&
43
+ (err instanceof LexerError ||
44
+ err instanceof ParseError ||
45
+ err instanceof RuntimeError)) {
46
+ try {
47
+ const enriched = enrichError(err, source, scope);
48
+ const formatOpts = {
49
+ format: options?.format ?? 'human',
50
+ verbose: options?.verbose ?? false,
51
+ includeCallStack: options?.includeCallStack ?? false,
52
+ maxCallStackDepth: options?.maxCallStackDepth ?? 10,
53
+ };
54
+ return formatEnrichedError(enriched, formatOpts);
55
+ }
56
+ catch {
57
+ // If enrichment fails, fall back to simple formatting
58
+ }
59
+ }
60
+ // IC-12: Fallback to existing behavior for backward compatibility
61
+ if (err instanceof LexerError) {
62
+ const location = err.location;
63
+ return `Lexer error at line ${location.line}: ${err.message.replace(/ at \d+:\d+$/, '')}`;
64
+ }
65
+ if (err instanceof ParseError) {
66
+ const location = err.location;
67
+ if (location) {
68
+ return `Parse error at line ${location.line}: ${err.message.replace(/ at \d+:\d+$/, '')}`;
69
+ }
70
+ return `Parse error: ${err.message}`;
71
+ }
72
+ if (err instanceof RuntimeError) {
73
+ const location = err.location;
74
+ const baseMessage = err.message.replace(/ at \d+:\d+$/, '');
75
+ if (location) {
76
+ return `Runtime error at line ${location.line}: ${baseMessage}`;
77
+ }
78
+ return `Runtime error: ${baseMessage}`;
79
+ }
80
+ // Handle file not found errors (ENOENT)
81
+ if (err instanceof Error &&
82
+ 'code' in err &&
83
+ err.code === 'ENOENT' &&
84
+ 'path' in err) {
85
+ return `File not found: ${err.path}`;
86
+ }
87
+ // Handle module errors
88
+ if (err.message.includes('Cannot find module')) {
89
+ return `Module error: ${err.message}`;
90
+ }
91
+ return err.message;
92
+ }
93
+ /**
94
+ * Determine exit code from script result
95
+ *
96
+ * Implements exit code semantics per language spec:
97
+ * - true / non-empty string: exit 0
98
+ * - false / empty string: exit 1
99
+ * - [0, "message"]: exit 0 with message
100
+ * - [1, "message"]: exit 1 with message
101
+ *
102
+ * @param value - The script return value
103
+ * @returns Exit code and optional message
104
+ */
105
+ export function determineExitCode(value) {
106
+ // Handle tuple format: [code, message]
107
+ if (Array.isArray(value)) {
108
+ if (value.length >= 2) {
109
+ const code = value[0];
110
+ const message = value[1];
111
+ // Validate code is 0 or 1
112
+ if (typeof code === 'number' && (code === 0 || code === 1)) {
113
+ // Return with message if provided as string
114
+ if (typeof message === 'string' && message !== '') {
115
+ return { code, message };
116
+ }
117
+ return { code };
118
+ }
119
+ }
120
+ // Non-conforming array: treat as truthy (exit 0)
121
+ return { code: 0 };
122
+ }
123
+ // Boolean values
124
+ if (typeof value === 'boolean') {
125
+ return { code: value ? 0 : 1 };
126
+ }
127
+ // String values
128
+ if (typeof value === 'string') {
129
+ return { code: value === '' ? 1 : 0 };
130
+ }
131
+ // All other values (number, dict, closure, etc.) are truthy: exit 0
132
+ return { code: 0 };
133
+ }
134
+ /**
135
+ * Detect help or version flags in CLI argument array.
136
+ * Checks for --help, -h, --version, -v in any position.
137
+ *
138
+ * @param argv - Command-line arguments (process.argv.slice(2))
139
+ * @returns Object with mode if flag found, null otherwise
140
+ */
141
+ export function detectHelpVersionFlag(argv) {
142
+ // Help takes precedence over version
143
+ if (argv.includes('--help') || argv.includes('-h')) {
144
+ return { mode: 'help' };
145
+ }
146
+ if (argv.includes('--version') || argv.includes('-v')) {
147
+ return { mode: 'version' };
148
+ }
149
+ return null;
150
+ }
151
+ /**
152
+ * Package version string (re-exported from version-data.ts)
153
+ *
154
+ * This replaces the previous async readVersion() function with a synchronous constant.
155
+ * The version is now generated at build time by packages/core/scripts/generate-version.ts.
156
+ */
157
+ export { VERSION };
158
+ //# sourceMappingURL=cli-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-shared.js","sourceRoot":"","sources":["../src/cli-shared.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Rill CLI - Run rill scripts from the command line
4
+ *
5
+ * @deprecated Use rill-exec or rill-eval commands instead. This file will be removed in v1.0.
6
+ *
7
+ * Usage:
8
+ * npx tsx src/cli.ts <script.rill>
9
+ * npx tsx src/cli.ts -e "code"
10
+ * echo "code" | npx tsx src/cli.ts -
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
package/dist/cli.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Rill CLI - Run rill scripts from the command line
4
+ *
5
+ * @deprecated Use rill-exec or rill-eval commands instead. This file will be removed in v1.0.
6
+ *
7
+ * Usage:
8
+ * npx tsx src/cli.ts <script.rill>
9
+ * npx tsx src/cli.ts -e "code"
10
+ * echo "code" | npx tsx src/cli.ts -
11
+ */
12
+ import * as fs from 'fs';
13
+ import { createRuntimeContext, execute, parse } from '@rcrsr/rill';
14
+ import { formatOutput } from './cli-shared.js';
15
+ async function run(source) {
16
+ const ctx = createRuntimeContext({
17
+ callbacks: {
18
+ onLog: (value) => console.log(formatOutput(value)),
19
+ },
20
+ });
21
+ try {
22
+ const ast = parse(source);
23
+ const result = await execute(ast, ctx);
24
+ console.log(formatOutput(result.value));
25
+ }
26
+ catch (err) {
27
+ console.error(err instanceof Error ? err.message : String(err));
28
+ process.exit(1);
29
+ }
30
+ }
31
+ async function main() {
32
+ const args = process.argv.slice(2);
33
+ if (args.length === 0) {
34
+ console.error('Usage: rill <script.rill> | rill -e "code" | rill -');
35
+ process.exit(1);
36
+ }
37
+ let source;
38
+ const arg0 = args[0];
39
+ if (arg0 === '-e') {
40
+ // Inline code: rill -e "code"
41
+ if (!args[1]) {
42
+ console.error('Missing code after -e');
43
+ process.exit(1);
44
+ }
45
+ source = args[1];
46
+ }
47
+ else if (arg0 === '-') {
48
+ // Stdin: echo "code" | rill -
49
+ source = fs.readFileSync(0, 'utf-8');
50
+ }
51
+ else {
52
+ // File: rill script.rill
53
+ if (!fs.existsSync(arg0)) {
54
+ console.error(`File not found: ${arg0}`);
55
+ process.exit(1);
56
+ }
57
+ source = fs.readFileSync(arg0, 'utf-8');
58
+ }
59
+ await run(source);
60
+ }
61
+ main();
62
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,KAAK,UAAU,GAAG,CAAC,MAAc;IAC/B,MAAM,GAAG,GAAG,oBAAoB,CAAC;QAC/B,SAAS,EAAE;YACT,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;SACnD;KACF,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,MAAc,CAAC;IACnB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;IAEtB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,8BAA8B;QAC9B,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;SAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACxB,8BAA8B;QAC9B,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,yBAAyB;QACzB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC;AACpB,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function testInternalImport(): void;
2
+ //# sourceMappingURL=test-internal-import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-internal-import.d.ts","sourceRoot":"","sources":["../src/test-internal-import.ts"],"names":[],"mappings":"AAKA,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -0,0 +1,7 @@
1
+ // VERIFICATION TEST: This file should cause pnpm install to fail
2
+ // Testing EC-1: CLI imports internal core path → pnpm install fails
3
+ import { Parser } from '@rill-lang/core/src/parser/parser.js';
4
+ export function testInternalImport() {
5
+ console.log('This should not compile or build successfully');
6
+ }
7
+ //# sourceMappingURL=test-internal-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-internal-import.js","sourceRoot":"","sources":["../src/test-internal-import.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,oEAAoE;AAEpE,OAAO,EAAE,MAAM,EAAE,MAAM,sCAAsC,CAAC;AAE9D,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;AAC/D,CAAC"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@rcrsr/rill-cli",
3
+ "version": "0.6.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "rill-exec": "./dist/cli-exec.js",
7
+ "rill-eval": "./dist/cli-eval.js",
8
+ "rill-check": "./dist/cli-check.js"
9
+ },
10
+ "dependencies": {
11
+ "yaml": "^2.8.2",
12
+ "@rcrsr/rill": "^0.6.0"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc --build",
19
+ "test": "vitest run",
20
+ "typecheck": "tsc --noEmit",
21
+ "lint": "eslint --config ../../eslint.config.js src/",
22
+ "check": "pnpm run build && pnpm run test && pnpm run lint"
23
+ }
24
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Configuration Loader for rill-check
3
+ * Loads and validates .rill-check.json configuration files.
4
+ */
5
+
6
+ import { readFileSync, existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { CheckConfig, RuleState, Severity } from './types.js';
9
+ import { VALIDATION_RULES } from './rules/index.js';
10
+
11
+ // ============================================================
12
+ // CONSTANTS
13
+ // ============================================================
14
+
15
+ /** Configuration file name */
16
+ const CONFIG_FILE_NAME = '.rill-check.json';
17
+
18
+ // ============================================================
19
+ // DEFAULT CONFIGURATION
20
+ // ============================================================
21
+
22
+ /**
23
+ * Create default configuration with all rules enabled.
24
+ * Returns configuration where all known rules are set to 'on'.
25
+ */
26
+ export function createDefaultConfig(): CheckConfig {
27
+ const rules: Record<string, RuleState> = {};
28
+ const severity: Record<string, Severity> = {};
29
+
30
+ for (const rule of VALIDATION_RULES) {
31
+ rules[rule.code] = 'on';
32
+ severity[rule.code] = rule.severity;
33
+ }
34
+
35
+ return { rules, severity };
36
+ }
37
+
38
+ // ============================================================
39
+ // VALIDATION
40
+ // ============================================================
41
+
42
+ /**
43
+ * Validate that a value is a valid RuleState.
44
+ */
45
+ function isRuleState(value: unknown): value is RuleState {
46
+ return value === 'on' || value === 'off' || value === 'warn';
47
+ }
48
+
49
+ /**
50
+ * Validate that a value is a valid Severity.
51
+ */
52
+ function isSeverity(value: unknown): value is Severity {
53
+ return value === 'error' || value === 'warning' || value === 'info';
54
+ }
55
+
56
+ /**
57
+ * Validate configuration structure and values.
58
+ * Throws Error if configuration is invalid.
59
+ */
60
+ function validateConfig(data: unknown): asserts data is {
61
+ rules?: Record<string, unknown>;
62
+ severity?: Record<string, unknown>;
63
+ } {
64
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
65
+ throw new Error('[RILL-C003] Invalid configuration: must be an object');
66
+ }
67
+
68
+ const config = data as Record<string, unknown>;
69
+
70
+ // Validate rules field if present
71
+ if ('rules' in config) {
72
+ if (
73
+ typeof config['rules'] !== 'object' ||
74
+ config['rules'] === null ||
75
+ Array.isArray(config['rules'])
76
+ ) {
77
+ throw new Error(
78
+ '[RILL-C003] Invalid configuration: rules must be an object'
79
+ );
80
+ }
81
+
82
+ const rules = config['rules'] as Record<string, unknown>;
83
+ for (const [code, state] of Object.entries(rules)) {
84
+ if (!isRuleState(state)) {
85
+ throw new Error(
86
+ `[RILL-C003] Invalid configuration: rule ${code} has invalid state "${state}" (must be 'on', 'off', or 'warn')`
87
+ );
88
+ }
89
+ }
90
+ }
91
+
92
+ // Validate severity field if present
93
+ if ('severity' in config) {
94
+ if (
95
+ typeof config['severity'] !== 'object' ||
96
+ config['severity'] === null ||
97
+ Array.isArray(config['severity'])
98
+ ) {
99
+ throw new Error(
100
+ '[RILL-C003] Invalid configuration: severity must be an object'
101
+ );
102
+ }
103
+
104
+ const severity = config['severity'] as Record<string, unknown>;
105
+ for (const [code, sev] of Object.entries(severity)) {
106
+ if (!isSeverity(sev)) {
107
+ throw new Error(
108
+ `[RILL-C003] Invalid configuration: rule ${code} has invalid severity "${sev}" (must be 'error', 'warning', or 'info')`
109
+ );
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Validate that all rule codes in config are known rules.
117
+ * Throws Error if unknown rule code found.
118
+ */
119
+ function validateRuleCodes(config: CheckConfig): void {
120
+ const knownRules = new Set(VALIDATION_RULES.map((r) => r.code));
121
+
122
+ // Check rules field
123
+ for (const code of Object.keys(config.rules)) {
124
+ if (!knownRules.has(code)) {
125
+ throw new Error(
126
+ `[RILL-C003] Invalid configuration: unknown rule ${code}`
127
+ );
128
+ }
129
+ }
130
+
131
+ // Check severity field
132
+ for (const code of Object.keys(config.severity)) {
133
+ if (!knownRules.has(code)) {
134
+ throw new Error(
135
+ `[RILL-C003] Invalid configuration: unknown rule ${code}`
136
+ );
137
+ }
138
+ }
139
+ }
140
+
141
+ // ============================================================
142
+ // CONFIGURATION LOADING
143
+ // ============================================================
144
+
145
+ /**
146
+ * Load configuration from .rill-check.json in the specified directory.
147
+ *
148
+ * @param cwd - Directory to search for configuration file
149
+ * @returns CheckConfig object, or null if file not found
150
+ * @throws Error with "Invalid configuration: {reason}" if JSON is invalid [EC-3]
151
+ * @throws Error with "Invalid configuration: unknown rule {code}" if unknown rule [EC-4]
152
+ */
153
+ export function loadConfig(cwd: string): CheckConfig | null {
154
+ const configPath = join(cwd, CONFIG_FILE_NAME);
155
+
156
+ // Return null if file not found (not an error)
157
+ if (!existsSync(configPath)) {
158
+ return null;
159
+ }
160
+
161
+ let fileContent: string;
162
+ try {
163
+ fileContent = readFileSync(configPath, 'utf-8');
164
+ } catch (err) {
165
+ throw new Error(
166
+ `[RILL-C003] Invalid configuration: failed to read file (${err instanceof Error ? err.message : String(err)})`
167
+ );
168
+ }
169
+
170
+ // Parse JSON
171
+ let parsedData: unknown;
172
+ try {
173
+ parsedData = JSON.parse(fileContent);
174
+ } catch (err) {
175
+ throw new Error(
176
+ `[RILL-C003] Invalid configuration: invalid JSON (${err instanceof Error ? err.message : String(err)})`
177
+ );
178
+ }
179
+
180
+ // Validate structure
181
+ validateConfig(parsedData);
182
+
183
+ // Get defaults
184
+ const defaults = createDefaultConfig();
185
+
186
+ // Merge with defaults (parsedData is validated, so we can safely cast)
187
+ const rules = {
188
+ ...defaults.rules,
189
+ ...(parsedData.rules as Record<string, RuleState> | undefined),
190
+ };
191
+ const severity = {
192
+ ...defaults.severity,
193
+ ...(parsedData.severity as Record<string, Severity> | undefined),
194
+ };
195
+
196
+ const config: CheckConfig = { rules, severity };
197
+
198
+ // Validate rule codes
199
+ validateRuleCodes(config);
200
+
201
+ return config;
202
+ }