@ball-lang/cli 1.24.0 → 1.26.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/src/index.ts CHANGED
@@ -12,7 +12,6 @@
12
12
  import { readFileSync, writeFileSync } from 'node:fs';
13
13
  import { fileURLToPath } from 'node:url';
14
14
  import { dirname, join } from 'node:path';
15
- import { BallEngine } from '@ball-lang/engine';
16
15
  import {
17
16
  analyzeCapabilities,
18
17
  checkPolicy,
@@ -20,6 +19,24 @@ import {
20
19
  type Program,
21
20
  } from './capability_analyzer.ts';
22
21
 
22
+ // `@ball-lang/engine` (compiled_engine.ts) and `./cli_core.ts`
23
+ // (compiled_cli.ts) are BOTH auto-generated by compiling a self-hosted Ball
24
+ // program through `@ball-lang/compiler`, and each installs its own runtime
25
+ // preamble as a side effect of module evaluation — including monkey-patching
26
+ // shared native prototypes (`Map.prototype.get`/`entries`/`keys`/`values`).
27
+ // That patching is idempotent against being installed *once*, but NOT against
28
+ // a second, independently-compiled preamble installing on top of it: the
29
+ // second artifact's "capture the native method before shadowing it" step
30
+ // actually captures the FIRST artifact's already-shadowed version, and a
31
+ // later call against a genuine native Map throws ("Method Map.prototype.entries
32
+ // called on incompatible receiver"). Every `ball` invocation is a fresh,
33
+ // single-command process, so the fix is to never let both load in the same
34
+ // one: import each compiled engine LAZILY, only inside the command handler
35
+ // that actually needs it (`cmdRun` / the info-validate-tree-version handlers
36
+ // below), instead of eagerly at module top level.
37
+ type EngineModule = typeof import('@ball-lang/engine');
38
+ type CliCoreModule = typeof import('./cli_core.ts');
39
+
23
40
  const VERSION = readVersion();
24
41
 
25
42
  const USAGE = `ball — the Ball language CLI (v${VERSION})
@@ -29,7 +46,11 @@ USAGE
29
46
 
30
47
  COMMANDS
31
48
  run <program.ball.json> Execute a Ball program and print stdout.
49
+ info <program.ball.json> Inspect ball program structure.
50
+ validate <program.ball.json> Check ball program validity.
51
+ tree <program.ball.json> Print module/import structure.
32
52
  audit <program.ball.json> Static capability analysis (I/O, fs, network, ...).
53
+ version Print version.
33
54
 
34
55
  OPTIONS
35
56
  -h, --help Print this help message.
@@ -187,6 +208,7 @@ async function cmdRun(args: ParsedArgs): Promise<number> {
187
208
  return 1;
188
209
  }
189
210
 
211
+ const { BallEngine }: EngineModule = await import('@ball-lang/engine');
190
212
  const program = loadProgram(programPath);
191
213
  const engine = new BallEngine(program as any, {
192
214
  stdout: (msg: string) => process.stdout.write(msg + '\n'),
@@ -205,6 +227,68 @@ async function cmdRun(args: ParsedArgs): Promise<number> {
205
227
  return 0;
206
228
  }
207
229
 
230
+ // ── info / validate / tree ──────────────────────────────────────────────────
231
+ //
232
+ // All three delegate their report text to `cli_core.ts` (a thin wrapper over
233
+ // `compiled_cli.ts`, compiled from `dart/shared/lib/cli_core.dart` — see
234
+ // CLAUDE.md's "Regenerate compiled TS CLI core" recipe), so the computed
235
+ // output is byte-identical to the native Dart CLI's (proven by
236
+ // `test/cli_core_parity_test.ts`). Only argv parsing / usage-error text stays
237
+ // TS-local, matching `cmdRun`/`cmdAudit`'s existing style.
238
+
239
+ async function cmdInfo(args: ParsedArgs): Promise<number> {
240
+ const programPath = args.positional[0];
241
+ if (!programPath) {
242
+ process.stderr.write('ball: info requires a program path\n\n');
243
+ process.stderr.write(USAGE);
244
+ return 1;
245
+ }
246
+
247
+ const cliCore: CliCoreModule = await import('./cli_core.ts');
248
+ const program = loadProgram(programPath);
249
+ process.stdout.write(cliCore.infoReport(program) + '\n');
250
+ return 0;
251
+ }
252
+
253
+ async function cmdValidate(args: ParsedArgs): Promise<number> {
254
+ const programPath = args.positional[0];
255
+ if (!programPath) {
256
+ process.stderr.write('ball: validate requires a program path\n\n');
257
+ process.stderr.write(USAGE);
258
+ return 1;
259
+ }
260
+
261
+ const cliCore: CliCoreModule = await import('./cli_core.ts');
262
+ const program = loadProgram(programPath);
263
+ const report = cliCore.validateReport(program);
264
+ if (cliCore.validateOk(program)) {
265
+ process.stdout.write(report + '\n');
266
+ return 0;
267
+ }
268
+ process.stderr.write(report + '\n');
269
+ return 1;
270
+ }
271
+
272
+ async function cmdTree(args: ParsedArgs): Promise<number> {
273
+ const programPath = args.positional[0];
274
+ if (!programPath) {
275
+ process.stderr.write('ball: tree requires a program path\n\n');
276
+ process.stderr.write(USAGE);
277
+ return 1;
278
+ }
279
+
280
+ const cliCore: CliCoreModule = await import('./cli_core.ts');
281
+ const program = loadProgram(programPath);
282
+ process.stdout.write(cliCore.treeReport(program) + '\n');
283
+ return 0;
284
+ }
285
+
286
+ async function cmdVersion(): Promise<number> {
287
+ const cliCore: CliCoreModule = await import('./cli_core.ts');
288
+ process.stdout.write(`${cliCore.versionLine(VERSION)}\n`);
289
+ return 0;
290
+ }
291
+
208
292
  function cmdAudit(args: ParsedArgs): number {
209
293
  const programPath = args.positional[0];
210
294
  if (!programPath) {
@@ -266,15 +350,25 @@ async function main(argv: string[]): Promise<number> {
266
350
  }
267
351
 
268
352
  if (args.flags['version'] === true || args.flags['v'] === true) {
269
- process.stdout.write(`${VERSION}\n`);
270
- return 0;
353
+ // Matches the Dart CLI's `version`/`--version`/`-v` cases (all three
354
+ // dispatch to the same `cli_core.versionLine`) — see the `case 'version'`
355
+ // branch below for the bare-command spelling.
356
+ return await cmdVersion();
271
357
  }
272
358
 
273
359
  switch (args.command) {
274
360
  case 'run':
275
361
  return await cmdRun(args);
362
+ case 'info':
363
+ return await cmdInfo(args);
364
+ case 'validate':
365
+ return await cmdValidate(args);
366
+ case 'tree':
367
+ return await cmdTree(args);
276
368
  case 'audit':
277
369
  return cmdAudit(args);
370
+ case 'version':
371
+ return await cmdVersion();
278
372
  case undefined:
279
373
  process.stdout.write(USAGE);
280
374
  return 0;