Skip to content

feat: implement CLI and dual-format build #8

@edloidas

Description

@edloidas

Implement CLI binary for command-line usage and configure dual-format (ESM + CJS) build output.


Rationale

The CLI enables:

  • Quick dice rolls from terminal
  • Scripting and automation
  • Testing reproducible rolls with seeds

Dual-format build ensures compatibility with:

  • Modern ESM consumers (Next.js, Vite, etc.)
  • Legacy CJS consumers (older Node.js, some bundlers)

References


Things to Consider

  • Shebang: CLI needs #!/usr/bin/env bun for direct execution
  • Exit codes: 0 for success, 1 for parse errors, 2 for usage errors
  • Verbose output: Show dice breakdown, not just total
  • package.json bin: Must point to built CLI, not source

Implementation

Important

This is not a step-by-step guide — it's a functional checklist ordered logically.

Files to Create

  1. /src/cli/args.ts — Argument parsing:

    interface CLIOptions {
      notation?: string;
      verbose: boolean;
      help: boolean;
      version: boolean;
      seed?: string;
    }
    
    function parseArgs(args: string[]): CLIOptions
  2. /src/cli/index.ts — CLI entry point:

    #!/usr/bin/env bun
    import { parseArgs } from './args';
    import { roll } from '../index';
    
    const options = parseArgs(process.argv.slice(2));
    // Handle --help, --version, then roll
  3. /bin/roll-parser.ts — Bin wrapper (thin shim)

CLI Usage

# Basic usage
roll-parser 2d6+3
# Output: 11

# Verbose output
roll-parser 2d6+3 --verbose
# Output: 2d6[3,5] + 3 = 11

# Reproducible roll
roll-parser 4d6dl1 --seed "character-str"
# Output: 13

# Help
roll-parser --help
# Output: Usage: roll-parser <notation> [options]
#         Options:
#           --verbose, -v  Show detailed roll breakdown
#           --seed <seed>  Use seed for reproducible rolls
#           --help, -h     Show this help message
#           --version      Show version number

# Version
roll-parser --version
# Output: 3.0.0

Build Configuration

Build output structure:

dist/
├── index.mjs    # ESM entry
├── index.js     # CJS entry
├── index.d.ts   # TypeScript declarations
└── cli.js       # CLI bundle (standalone)

package.json configuration:

{
  "type": "module",
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "bin": {
    "roll-parser": "./dist/cli.js"
  }
}

CLI Tests

describe('CLI', () => {
  it('should roll dice from argument', async () => {
    const proc = Bun.spawn(['bun', 'run', 'dist/cli.js', '2d6']);
    const output = await new Response(proc.stdout).text();
    expect(output.trim()).toMatch(/^\d+$/);
  });

  it('should show verbose output with --verbose', async () => {
    const proc = Bun.spawn(['bun', 'run', 'dist/cli.js', '2d6', '--verbose']);
    const output = await new Response(proc.stdout).text();
    expect(output).toMatch(/2d6\[.*\]/);
  });

  it('should produce same result with same seed', async () => {
    const run = () => Bun.spawn(['bun', 'run', 'dist/cli.js', '4d6', '--seed', 'test']);
    const out1 = await new Response(run().stdout).text();
    const out2 = await new Response(run().stdout).text();
    expect(out1).toBe(out2);
  });

  it('should show help with --help', async () => {
    const proc = Bun.spawn(['bun', 'run', 'dist/cli.js', '--help']);
    const output = await new Response(proc.stdout).text();
    expect(output).toContain('Usage:');
  });

  it('should show version with --version', async () => {
    const proc = Bun.spawn(['bun', 'run', 'dist/cli.js', '--version']);
    const output = await new Response(proc.stdout).text();
    expect(output.trim()).toMatch(/^\d+\.\d+\.\d+/);
  });

  it('should exit with code 1 on parse error', async () => {
    const proc = Bun.spawn(['bun', 'run', 'dist/cli.js', 'invalid@dice']);
    await proc.exited;
    expect(proc.exitCode).toBe(1);
  });
});

Acceptance Criteria

  • CLI binary works: roll-parser 2d6
  • --help / -h shows usage information
  • --version shows package version
  • --verbose / -v shows detailed roll breakdown
  • --seed enables reproducible rolls
  • Exit code 0 on success, 1 on parse error, 2 on usage error
  • Build produces ESM (index.mjs) + CJS (index.js) + types (index.d.ts)
  • Package exports configured correctly for both ESM and CJS consumers
  • bin field points to working CLI
  • CI smoke test passes (existing workflow)

Drafted with AI assistance

Metadata

Metadata

Assignees

Labels

DXChanges that improve developer experiencefeatureNew functionality

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions