@rcrsr/rill-cli 0.16.0 → 0.17.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.
package/dist/cli-run.js CHANGED
@@ -26,7 +26,7 @@ Options:
26
26
  --format <mode> Output format: human, json, compact (default: human)
27
27
  --verbose Show full error details (default: false)
28
28
  --max-stack-depth <n> Error stack frame limit (default: 10)
29
- --create-bindings Write bindings source to config-defined file and exit
29
+ --create-bindings [dir] Write bindings source to dir and exit (default: ./bindings)
30
30
  --explain <code> Print error code documentation
31
31
  --help Print this help message and exit
32
32
  --version Print version and exit`.trimEnd();
@@ -38,17 +38,42 @@ const BASE_OPTIONS = {
38
38
  format: { type: 'string' },
39
39
  verbose: { type: 'boolean' },
40
40
  'max-stack-depth': { type: 'string' },
41
- 'create-bindings': { type: 'boolean' },
42
41
  help: { type: 'boolean' },
43
42
  version: { type: 'boolean' },
44
43
  explain: { type: 'string' },
45
44
  };
46
45
  // ============================================================
46
+ // CREATE-BINDINGS EXTRACTION
47
+ // ============================================================
48
+ /**
49
+ * Extract --create-bindings [dir] from argv before parseArgs.
50
+ * Handles the optional dir argument that parseArgs cannot natively support.
51
+ * Returns the filtered argv (with --create-bindings removed) and the resolved dir.
52
+ */
53
+ function extractCreateBindings(argv) {
54
+ const idx = argv.indexOf('--create-bindings');
55
+ if (idx === -1) {
56
+ return { filteredArgv: argv, createBindings: undefined };
57
+ }
58
+ const next = argv[idx + 1];
59
+ if (next !== undefined && !next.startsWith('-')) {
60
+ return {
61
+ filteredArgv: [...argv.slice(0, idx), ...argv.slice(idx + 2)],
62
+ createBindings: next,
63
+ };
64
+ }
65
+ return {
66
+ filteredArgv: [...argv.slice(0, idx), ...argv.slice(idx + 1)],
67
+ createBindings: './bindings',
68
+ };
69
+ }
70
+ // ============================================================
47
71
  // PARSE ARGS
48
72
  // ============================================================
49
73
  export function parseCliArgs(argv = process.argv.slice(2)) {
74
+ const { filteredArgv, createBindings } = extractCreateBindings(argv);
50
75
  const { values, positionals } = parseArgs({
51
- args: argv,
76
+ args: filteredArgv,
52
77
  options: BASE_OPTIONS,
53
78
  allowPositionals: true,
54
79
  strict: false,
@@ -77,7 +102,7 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
77
102
  verbose: values['verbose'] === true,
78
103
  maxStackDepth,
79
104
  explain: values['explain'],
80
- createBindings: values['create-bindings'] === true,
105
+ createBindings,
81
106
  };
82
107
  }
83
108
  // ============================================================
@@ -96,8 +121,9 @@ function extractHandlerArgs(argv, params) {
96
121
  type: param.type === 'bool' ? 'boolean' : 'string',
97
122
  };
98
123
  }
124
+ const { filteredArgv } = extractCreateBindings(argv);
99
125
  const { values } = parseArgs({
100
- args: argv,
126
+ args: filteredArgv,
101
127
  options: handlerOptions,
102
128
  allowPositionals: true,
103
129
  strict: false,
@@ -165,14 +191,20 @@ export async function main() {
165
191
  throw err;
166
192
  }
167
193
  // Create bindings and exit early if --create-bindings was set
168
- if (opts.createBindings === true) {
169
- const extBindingsPath = resolve(process.cwd(), project.config.extensions?.bindings ?? 'bindings/ext.rill');
170
- mkdirSync(dirname(extBindingsPath), { recursive: true });
171
- writeFileSync(extBindingsPath, project.extensionBindings + '\n');
194
+ if (opts.createBindings !== undefined) {
195
+ const bindingsDir = resolve(dirname(configPath), opts.createBindings);
196
+ mkdirSync(bindingsDir, { recursive: true });
197
+ writeFileSync(resolve(bindingsDir, 'ext.rill'), project.extensionBindings + '\n');
172
198
  if (project.config.context !== undefined) {
173
- const ctxBindingsPath = resolve(process.cwd(), project.config.context.bindings ?? 'bindings/context.rill');
174
- mkdirSync(dirname(ctxBindingsPath), { recursive: true });
175
- writeFileSync(ctxBindingsPath, project.contextBindings + '\n');
199
+ writeFileSync(resolve(bindingsDir, 'context.rill'), project.contextBindings + '\n');
200
+ }
201
+ for (const dispose of project.disposes) {
202
+ try {
203
+ await dispose();
204
+ }
205
+ catch {
206
+ // Ignore dispose errors during cleanup
207
+ }
176
208
  }
177
209
  process.exit(0);
178
210
  }
@@ -272,7 +304,9 @@ export async function main() {
272
304
  ...opts,
273
305
  scriptPath,
274
306
  };
275
- const runResult = await runScript(runOpts, project.config, project.extTree, project.extensionBindings, [...project.disposes]);
307
+ const runResult = await runScript(runOpts, project.config, project.extTree, [
308
+ ...project.disposes,
309
+ ]);
276
310
  if (runResult.output !== undefined) {
277
311
  process.stdout.write(runResult.output + '\n');
278
312
  }
@@ -2,8 +2,8 @@
2
2
  * Script runner for rill-run.
3
3
  * Builds runtime options, executes rill scripts, and maps results to exit codes.
4
4
  */
5
- import { type SchemeResolver } from '@rcrsr/rill';
6
- import type { NestedExtConfig, RillConfigFile } from '@rcrsr/rill-config';
5
+ import { type RillValue, type SchemeResolver } from '@rcrsr/rill';
6
+ import type { RillConfigFile } from '@rcrsr/rill-config';
7
7
  import type { RunCliOptions } from './types.js';
8
8
  export interface RunResult {
9
9
  readonly exitCode: number;
@@ -11,13 +11,13 @@ export interface RunResult {
11
11
  readonly errorOutput?: string | undefined;
12
12
  }
13
13
  /**
14
- * Build a custom module scheme resolver.
15
- * - ID 'ext' returns generated bindings source
16
- * - ID 'ext.*' drills into extTree subtree and returns bindings for that node
17
- * - All other IDs delegates to moduleResolver with the modules config
14
+ * Build a custom module scheme resolver using folder aliasing.
15
+ * Each config key maps to a directory. Dot-paths resolve to files within:
16
+ * - `module:alias.sub.path``{dir}/sub/path.rill`
17
+ * - `module:alias``{dir}/index.rill`
18
18
  */
19
- export declare function buildModuleResolver(bindingsSource: string, modulesConfig: Record<string, string>, extTree: NestedExtConfig, configDir: string): SchemeResolver;
19
+ export declare function buildModuleResolver(modulesConfig: Record<string, string>, configDir: string): SchemeResolver;
20
20
  /**
21
21
  * Run a rill script file with the given extension tree and config.
22
22
  */
23
- export declare function runScript(opts: RunCliOptions, config: RillConfigFile, extTree: NestedExtConfig, bindingsSrc: string, disposes: Array<() => void | Promise<void>>): Promise<RunResult>;
23
+ export declare function runScript(opts: RunCliOptions, config: RillConfigFile, extTree: Record<string, RillValue>, disposes: Array<() => void | Promise<void>>): Promise<RunResult>;
@@ -6,78 +6,33 @@ import { readFileSync } from 'node:fs';
6
6
  import { dirname, resolve } from 'node:path';
7
7
  import { parse, execute, createRuntimeContext, extResolver, moduleResolver, toNative, isTuple, } from '@rcrsr/rill';
8
8
  import { ParseError, RillError, formatRillError, formatRillErrorJson, } from '@rcrsr/rill';
9
- import { buildExtensionBindings, isLeafFunction } from '@rcrsr/rill-config';
10
- // ============================================================
11
- // TREE CONVERSION
12
- // ============================================================
13
- function convertTreeToRillValues(tree) {
14
- const result = {};
15
- for (const [key, value] of Object.entries(tree)) {
16
- if (typeof value === 'object' &&
17
- value !== null &&
18
- 'fn' in value &&
19
- typeof value.fn === 'function' &&
20
- 'params' in value) {
21
- const rillFn = value;
22
- result[key] = {
23
- __type: 'callable',
24
- kind: 'application',
25
- isProperty: false,
26
- fn: rillFn.fn,
27
- params: rillFn.params,
28
- returnType: rillFn.returnType,
29
- annotations: rillFn.annotations ?? {},
30
- };
31
- }
32
- else {
33
- result[key] = convertTreeToRillValues(value);
34
- }
35
- }
36
- return result;
37
- }
38
9
  // ============================================================
39
10
  // MODULE RESOLVER
40
11
  // ============================================================
41
12
  /**
42
- * Build a custom module scheme resolver.
43
- * - ID 'ext' returns generated bindings source
44
- * - ID 'ext.*' drills into extTree subtree and returns bindings for that node
45
- * - All other IDs delegates to moduleResolver with the modules config
13
+ * Build a custom module scheme resolver using folder aliasing.
14
+ * Each config key maps to a directory. Dot-paths resolve to files within:
15
+ * - `module:alias.sub.path``{dir}/sub/path.rill`
16
+ * - `module:alias``{dir}/index.rill`
46
17
  */
47
- export function buildModuleResolver(bindingsSource, modulesConfig, extTree, configDir) {
48
- const moduleConfig = {};
18
+ export function buildModuleResolver(modulesConfig, configDir) {
19
+ const moduleDirs = {};
49
20
  for (const [id, value] of Object.entries(modulesConfig)) {
50
- if (id !== 'ext') {
51
- moduleConfig[id] = resolve(configDir, value);
52
- }
21
+ moduleDirs[id] = resolve(configDir, value);
53
22
  }
54
23
  const resolver = (resource) => {
55
- if (resource === 'ext') {
56
- return { kind: 'source', text: bindingsSource };
24
+ const dotIndex = resource.indexOf('.');
25
+ const alias = dotIndex === -1 ? resource : resource.slice(0, dotIndex);
26
+ const dirPath = moduleDirs[alias];
27
+ if (dirPath === undefined) {
28
+ return moduleResolver(resource, {});
57
29
  }
58
- if (resource.startsWith('ext.')) {
59
- const suffix = resource.slice('ext.'.length);
60
- const segments = suffix.split('.');
61
- let node = extTree;
62
- let fullyResolved = true;
63
- for (const segment of segments) {
64
- const child = node[segment];
65
- if (child === undefined) {
66
- fullyResolved = false;
67
- break;
68
- }
69
- if (isLeafFunction(child)) {
70
- fullyResolved = false;
71
- break;
72
- }
73
- node = child;
74
- }
75
- if (fullyResolved && node !== extTree) {
76
- const subtreeSource = buildExtensionBindings(node, suffix);
77
- return { kind: 'source', text: subtreeSource };
78
- }
79
- }
80
- return moduleResolver(resource, moduleConfig);
30
+ const subPath = dotIndex === -1 ? '' : resource.slice(dotIndex + 1);
31
+ const relPath = subPath.length > 0
32
+ ? subPath.replaceAll('.', '/') + '.rill'
33
+ : 'index.rill';
34
+ const filePath = resolve(dirPath, relPath);
35
+ return moduleResolver(resource, { [resource]: filePath });
81
36
  };
82
37
  return resolver;
83
38
  }
@@ -120,7 +75,7 @@ function formatOutput(value, format) {
120
75
  /**
121
76
  * Run a rill script file with the given extension tree and config.
122
77
  */
123
- export async function runScript(opts, config, extTree, bindingsSrc, disposes) {
78
+ export async function runScript(opts, config, extTree, disposes) {
124
79
  if (!opts.scriptPath) {
125
80
  return { exitCode: 1, errorOutput: 'no script path provided' };
126
81
  }
@@ -132,10 +87,9 @@ export async function runScript(opts, config, extTree, bindingsSrc, disposes) {
132
87
  const message = err instanceof Error ? err.message : String(err);
133
88
  return { exitCode: 1, errorOutput: message };
134
89
  }
135
- const extConfig = convertTreeToRillValues(extTree);
136
90
  const modulesConfig = config.modules ?? {};
137
91
  const configDir = dirname(resolve(opts.config));
138
- const customModuleResolver = buildModuleResolver(bindingsSrc, modulesConfig, extTree, configDir);
92
+ const customModuleResolver = buildModuleResolver(modulesConfig, configDir);
139
93
  const runtimeOptions = {
140
94
  resolvers: {
141
95
  ext: extResolver,
@@ -143,7 +97,7 @@ export async function runScript(opts, config, extTree, bindingsSrc, disposes) {
143
97
  },
144
98
  configurations: {
145
99
  resolvers: {
146
- ext: extConfig,
100
+ ext: extTree,
147
101
  },
148
102
  },
149
103
  parseSource: parse,
@@ -196,7 +150,7 @@ export async function runScript(opts, config, extTree, bindingsSrc, disposes) {
196
150
  verbose: opts.verbose,
197
151
  maxStackDepth: opts.maxStackDepth,
198
152
  filePath: opts.scriptPath,
199
- sources: { script: source, bindings: bindingsSrc },
153
+ sources: { script: source },
200
154
  });
201
155
  return { exitCode: 1, errorOutput: formatted };
202
156
  }
@@ -12,5 +12,5 @@ export interface RunCliOptions {
12
12
  readonly verbose: boolean;
13
13
  readonly maxStackDepth: number;
14
14
  readonly explain?: string | undefined;
15
- readonly createBindings?: boolean | undefined;
15
+ readonly createBindings?: string | undefined;
16
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rcrsr/rill-cli",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "CLI tools for the rill scripting language",
5
5
  "license": "MIT",
6
6
  "author": "Andre Bremer",
@@ -21,8 +21,8 @@
21
21
  "dependencies": {
22
22
  "dotenv": "^16.0.0",
23
23
  "yaml": "^2.8.2",
24
- "@rcrsr/rill-config": "^0.16.0",
25
- "@rcrsr/rill": "^0.16.0"
24
+ "@rcrsr/rill": "^0.17.0",
25
+ "@rcrsr/rill-config": "^0.17.0"
26
26
  },
27
27
  "files": [
28
28
  "dist"