npx-cli

安装量: 65
排名: #11602

安装

npx skills add https://github.com/jwynia/agent-skills --skill npx-cli

npx CLI Tool Development (Bun-First) Build and publish npx-executable command-line tools using Bun as the primary runtime and toolchain, producing binaries that work for all npm/npx users (Node.js runtime). When to Use This Skill Use when: Creating a new CLI tool from scratch Building an npx-executable binary Setting up argument parsing, sub-commands, or terminal UX for a CLI Publishing a CLI tool to npm Adding a CLI to an existing library package Do NOT use when: Building a library without a CLI (use the npm-package skill) Building an application (not a published package) Working in a monorepo (this skill targets single-package repos) Toolchain Concern Tool Why Runtime / package manager Bun Fast install, run, transpile Bundler Bunup Bun-native, dual entry (lib + cli), .d.ts Argument parsing citty ~3KB, TypeScript-native, auto-help, runMain() Terminal colors picocolors ~7KB, CJS+ESM, auto-detect TypeScript module: "nodenext" , strict: true + extras Maximum correctness Formatting + basic linting Biome v2 Fast, single tool Type-aware linting ESLint + typescript-eslint Deep type safety Testing Vitest Isolation, mocking, coverage Versioning Changesets File-based, explicit Publishing npm publish --provenance Trusted Publishing / OIDC Scaffolding a New CLI Run the scaffold script: bun run < skill-path

/scripts/scaffold.ts ./my-cli \ --name my-cli \ --bin my-cli \ --description "What this CLI does" \ --author "Your Name" \ --license MIT Options: --bin — Binary name for npx (defaults to package name without scope) --cli-only — No library exports, CLI binary only --no-eslint — Skip ESLint, use Biome only Then install dependencies: cd my-cli bun install bun add -d bunup typescript vitest @vitest/coverage-v8 @biomejs/biome @changesets/cli bun add citty picocolors bun add -d eslint typescript-eslint

unless --no-eslint

Project Structure Dual (Library + CLI) — Default my-cli/ ├── src/ │ ├── index.ts # Library exports (programmatic API) │ ├── index.test.ts # Unit tests for library │ ├── cli.ts # CLI entry point (imports from index.ts) │ └── cli.test.ts # CLI integration tests ├── dist/ │ ├── index.js # Library bundle │ ├── index.d.ts # Type declarations │ └── cli.js # CLI binary (with shebang) ├── .changeset/ │ └── config.json ├── package.json ├── tsconfig.json ├── bunup.config.ts ├── biome.json ├── eslint.config.ts ├── vitest.config.ts ├── .gitignore ├── README.md └── LICENSE CLI-Only (No Library Exports) Same structure minus src/index.ts and src/index.test.ts . No exports field in package.json, only bin . Architecture Pattern Separate logic from CLI wiring. The CLI entry ( cli.ts ) is a thin wrapper that: Parses arguments with citty Calls into the library/core modules Formats output for the terminal All business logic lives in importable modules ( index.ts or internal modules). This makes logic unit-testable without spawning processes. cli.ts → imports from → index.ts / core modules ↑ unit tests Key Rules (Non-Negotiable) All rules from the npm-package skill apply here. These additional rules are specific to CLI packages: Binary Configuration Always use

!/usr/bin/env node

in published bin files. Never

!/usr/bin/env bun

. The vast majority of npx users don't have Bun installed. Point bin at compiled JavaScript in dist/ . Never at TypeScript source. npx consumers won't have your build toolchain. Ensure the bin file is executable. The build script includes chmod +x dist/cli.js after compilation. Build with Node.js as the target. Bunup's output must run on Node.js, not require Bun runtime features. Package Configuration Always use "type": "module" in package.json. types must be the first condition in every exports block. Use files: ["dist"] . Whitelist only. For dual packages (library + CLI): The exports field exposes the library API. The bin field exposes the CLI. They are independent — bin is NOT part of exports . Code Quality any is banned. Use unknown and narrow. Use import type for type-only imports. Handle errors gracefully. CLI users should never see raw stack traces. Use citty's runMain() which handles this automatically, plus process.on('SIGINT', ...) for cleanup. Exit with appropriate codes. 0 for success, 1 for errors, 2 for bad arguments, 130 for SIGINT. Reference Documentation Read these before modifying configuration: reference/cli-patterns.md — bin setup, citty patterns, sub-commands, error handling, terminal UX, testing CLI binaries reference/esm-cjs-guide.md — exports map, dual package hazard, common mistakes reference/strict-typescript.md — tsconfig, Biome rules, ESLint type-aware rules, Vitest config reference/publishing-workflow.md — Changesets, files field, Trusted Publishing, CI pipeline Argument Parsing with citty Single Command import { defineCommand , runMain } from 'citty' ; const main = defineCommand ( { meta : { name : 'my-cli' , version : '1.0.0' , description : '...' } , args : { input : { type : 'positional' , description : 'Input file' , required : true } , output : { alias : 'o' , type : 'string' , description : 'Output path' , default : './out' } , verbose : { alias : 'v' , type : 'boolean' , description : 'Verbose output' , default : false } , } , run ( { args } ) { // args is fully typed } , } ) ; void runMain ( main ) ; Sub-Commands import { defineCommand , runMain } from 'citty' ; const init = defineCommand ( { meta : { name : 'init' } , / ... / } ) ; const build = defineCommand ( { meta : { name : 'build' } , / ... / } ) ; const main = defineCommand ( { meta : { name : 'my-cli' , version : '1.0.0' } , subCommands : { init , build } , } ) ; void runMain ( main ) ; See reference/cli-patterns.md for complete examples including error handling, colors, and spinners. Testing Strategy Unit Tests — Test the Logic // src/index.test.ts import { describe , it , expect } from 'vitest' ; import { processInput } from './index.js' ; describe ( 'processInput' , ( ) => { it ( 'handles valid input' , ( ) => { expect ( processInput ( 'test' ) ) . toBe ( 'expected' ) ; } ) ; } ) ; Integration Tests — Test the Binary Build first ( bun run build ), then spawn the compiled binary: // src/cli.test.ts import { describe , it , expect } from 'vitest' ; import { execFile } from 'node:child_process' ; import { promisify } from 'node:util' ; const exec = promisify ( execFile ) ; describe ( 'CLI' , ( ) => { it ( 'prints help' , async ( ) => { const { stdout } = await exec ( 'node' , [ './dist/cli.js' , '--help' ] ) ; expect ( stdout ) . toContain ( 'my-cli' ) ; } ) ; } ) ; Development Workflow

Write code and tests

bun run test:watch

Vitest watch mode

Check everything

bun run lint

Biome + ESLint

bun run typecheck

tsc --noEmit

bun run test

Vitest

Build and try the CLI locally

bun run build node ./dist/cli.js --help node ./dist/cli.js some-input

Prepare release

bunx changeset bunx changeset version

Publish

bun run release

Build + npm publish --provenance

Adding Sub-Commands Later Create a new file per sub-command: src/commands/init.ts , src/commands/build.ts Each exports a defineCommand() result Import and wire into the main command's subCommands Keep logic in testable modules, commands are thin wrappers Converting a CLI-Only Package to Dual (Library + CLI) Create src/index.ts with the public API Update bunup.config.ts to include both entry points Add exports field to package.json alongside the existing bin Add .d.ts generation: dts: { entry: ['src/index.ts'] } Bun-Specific Gotchas bun build does not generate .d.ts files. Use Bunup or tsc --emitDeclarationOnly . bun build does not downlevel syntax. ES2022+ ships as-is. bun publish does not support --provenance . Use npm publish . bun publish uses NPM_CONFIG_TOKEN , not NODE_AUTH_TOKEN . Never use

!/usr/bin/env bun

in published packages. Your users don't have Bun. Bunup banner adds the shebang to ALL output files , including the library entry. If this is a problem, use a post-build script to add the shebang only to dist/cli.js .

返回排行榜