-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add @jahands/cli-tools package
- Loading branch information
Showing
31 changed files
with
796 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@repo/cli-example': patch | ||
'@jahands/cli-tools': patch | ||
--- | ||
|
||
feat: Add @jahands/cli-tools package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/** @type {import("eslint").Linter.Config} */ | ||
module.exports = { | ||
root: true, | ||
extends: ['@repo/eslint-config/workers.cjs'], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# cli-example | ||
|
||
An example CLI to showcase the @jahands/cli-tools package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "@repo/cli-example", | ||
"version": "0.1.0", | ||
"private": true, | ||
"sideEffects": false, | ||
"scripts": { | ||
"check:lint": "run-eslint-workers", | ||
"check:types": "run-tsc", | ||
"cli": "bun ./src/bin/cli.ts", | ||
"test": "run-vitest" | ||
}, | ||
"dependencies": { | ||
"@commander-js/extra-typings": "12.1.0", | ||
"@jahands/cli-tools": "workspace:*", | ||
"commander": "12.1.0", | ||
"zod": "3.23.8", | ||
"zx": "8.1.9" | ||
}, | ||
"devDependencies": { | ||
"@cloudflare/vitest-pool-workers": "0.5.14", | ||
"@cloudflare/workers-types": "4.20241004.0", | ||
"@repo/eslint-config": "workspace:*", | ||
"@repo/tools": "workspace:*", | ||
"@repo/typescript-config": "workspace:*", | ||
"vitest": "2.1.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import 'zx/globals' | ||
|
||
import { program } from '@commander-js/extra-typings' | ||
|
||
import { exampleCmd } from '../cmd/example' | ||
|
||
program | ||
.name('factorio-icons') | ||
.description('A CLI for scripting cropping Factorio icons') | ||
|
||
// Commands | ||
.addCommand(exampleCmd) | ||
|
||
// Don't hang for unresolved promises | ||
.hook('postAction', () => process.exit(0)) | ||
.parseAsync() | ||
.catch((e) => { | ||
if (e instanceof ProcessOutput) { | ||
process.exit(1) | ||
} else { | ||
throw e | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Command } from '@commander-js/extra-typings' | ||
import { cliError, prefixOutput, validateArg } from '@jahands/cli-tools' | ||
import { z } from 'zod' | ||
|
||
export const exampleCmd = new Command('example').description('An example command with subcommands') | ||
|
||
exampleCmd | ||
.command('throw-cli-error') | ||
.description('throws a cli error') | ||
.action(async () => { | ||
throw cliError('an error!') | ||
}) | ||
|
||
exampleCmd | ||
.command('validate-args') | ||
.requiredOption( | ||
'-e, --env <staging|production>', | ||
'environment to use', | ||
validateArg(z.enum(['staging', 'production'])) | ||
) | ||
.action(async ({ env }) => { | ||
echo(`env is: ${env}`) | ||
}) | ||
|
||
exampleCmd.command('prefix-output').action(async () => { | ||
const proc = $`jctl` | ||
await prefixOutput(proc, 'OUTPUT:') | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"compilerOptions": { | ||
"lib": ["ESNext"], | ||
"target": "ESNext", | ||
"module": "ESNext", | ||
"moduleDetection": "force", | ||
"jsx": "react-jsx", | ||
"allowJs": true, | ||
|
||
/* Bundler mode */ | ||
"moduleResolution": "bundler", | ||
"allowImportingTsExtensions": true, | ||
"verbatimModuleSyntax": true, | ||
"noEmit": true, | ||
|
||
/* Linting */ | ||
"skipLibCheck": true, | ||
"strict": true, | ||
"noFallthroughCasesInSwitch": true, | ||
"forceConsistentCasingInFileNames": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config' | ||
|
||
export default defineWorkersConfig({ | ||
test: { | ||
poolOptions: { | ||
workers: { | ||
main: `${__dirname}/src/index.ts`, | ||
isolatedStorage: true, | ||
singleWorker: true, | ||
miniflare: { | ||
compatibilityDate: '2024-09-02', | ||
compatibilityFlags: ['nodejs_compat'], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
name = "hono-app" | ||
main = "src/index.ts" | ||
compatibility_date = "2024-09-02" | ||
compatibility_flags = ["nodejs_compat"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/** @type {import("eslint").Linter.Config} */ | ||
module.exports = { | ||
root: true, | ||
extends: ['@repo/eslint-config/workers.cjs'], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# @jahands/cli-tools | ||
|
||
A collection of CLI tools to help with my commander CLIs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
"name": "@jahands/cli-tools", | ||
"version": "0.1.0", | ||
"private": false, | ||
"description": "cli tools for commander", | ||
"homepage": "https://github.com/jahands/workers-packages/tree/main/packages/cli-tools", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/jahands/workers-packages.git", | ||
"directory": "packages/cli-tools" | ||
}, | ||
"license": "MIT", | ||
"author": { | ||
"name": "Jacob Hands", | ||
"url": "https://github.com/jahands" | ||
}, | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.mjs" | ||
} | ||
} | ||
}, | ||
"main": "./dist/index.mjs", | ||
"module": "./dist/index.mjs", | ||
"scripts": { | ||
"build": "bun ./scripts/build.ts", | ||
"check:lint": "run-eslint-workers", | ||
"check:types": "run-tsc", | ||
"test": "run-vitest" | ||
}, | ||
"devDependencies": { | ||
"@repo/tools": "workspace:*", | ||
"@repo/typescript-config": "workspace:*", | ||
"esbuild": "0.24.0", | ||
"vitest": "2.1.1" | ||
}, | ||
"peerDependencies": { | ||
"@commander-js/extra-typings": "^12.1.0", | ||
"commander": "^12.1.0", | ||
"typescript": "^5.5.4", | ||
"zod": "^3.23.8", | ||
"zx": "^8.1.9" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import 'zx/globals' | ||
|
||
import { inspect } from 'util' | ||
import ts from 'typescript' | ||
|
||
import { entryPoints } from './entrypoints' | ||
|
||
function buildDeclarationFiles(fileNames: string[], options: ts.CompilerOptions): void { | ||
options = { | ||
...options, | ||
declaration: true, | ||
emitDeclarationOnly: true, | ||
outDir: './dist/', | ||
} | ||
const program = ts.createProgram(fileNames, options) | ||
program.emit() | ||
} | ||
|
||
const tsconfig = ts.readConfigFile('./tsconfig.json', ts.sys.readFile) | ||
if (tsconfig.error) { | ||
throw new Error(`failed to read tsconfig: ${inspect(tsconfig)}`) | ||
} | ||
|
||
buildDeclarationFiles(entryPoints, tsconfig.config) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import 'zx/globals' | ||
|
||
import * as esbuild from 'esbuild' | ||
|
||
import { entryPoints } from './entrypoints' | ||
|
||
await fs.rm('./dist/', { force: true, recursive: true }) | ||
|
||
await Promise.all([ | ||
$`bun ./scripts/build-types.ts`, | ||
esbuild.build({ | ||
entryPoints, | ||
outdir: './dist/', | ||
logLevel: 'info', | ||
outExtension: { | ||
'.js': '.mjs', | ||
}, | ||
target: 'es2022', | ||
format: 'esm', | ||
bundle: true, | ||
treeShaking: true, | ||
external: [ | ||
// peerDependencies | ||
'@commander-js/extra-typings', | ||
'commander', | ||
'typescript', | ||
'zod', | ||
'zx', | ||
], | ||
}), | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export const entryPoints = [ | ||
// entrypoints | ||
'./src/index.ts', | ||
] as const satisfies string[] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Command, program } from '@commander-js/extra-typings' | ||
import { beforeAll, beforeEach, describe, expect, it } from 'vitest' | ||
import { z } from 'zod' | ||
|
||
import { parseArg } from './args' | ||
|
||
const exitErrors: Error[] = [] | ||
beforeAll(() => { | ||
// Don't call process.exit(1) - instead, track the errors here | ||
// for tests that throw program.error() | ||
program.exitOverride((e) => { | ||
exitErrors.push(e) | ||
throw e | ||
}) | ||
}) | ||
|
||
beforeEach(() => { | ||
exitErrors.splice(0, exitErrors.length) | ||
}) | ||
|
||
describe('parseArg()', () => { | ||
it(`returns the output of the zod schema's parse()`, () => { | ||
const cmd = new Command() | ||
const schema = z.coerce.number() | ||
expect(parseArg('5', schema, cmd)).toBe(5) | ||
}) | ||
|
||
it(`throws cmd.error() with nicely formatted message`, () => { | ||
let exited = false | ||
const cli = new Command() | ||
.exitOverride((e) => { | ||
exited = true | ||
throw e | ||
}) | ||
.action(() => { | ||
const schema = z.coerce.number({ description: 'a number' }) | ||
parseArg('a', schema, cli) | ||
}) | ||
expect(() => cli.parse([])).toThrowErrorMatchingInlineSnapshot( | ||
`[CommanderError: [91merror[39m[90m:[39m Expected number, received nan]` | ||
) | ||
expect(exited).toBe(true) | ||
}) | ||
|
||
it(`throws program.error() if no command is passed in`, () => { | ||
expect(exitErrors.length).toBe(0) | ||
const schema = z.string().regex(/^foo$/, 'should have been foo!') | ||
expect(() => parseArg('bar', schema)).toThrowErrorMatchingInlineSnapshot( | ||
`[CommanderError: [91merror[39m[90m:[39m should have been foo!]` | ||
) | ||
expect(exitErrors.length).toBe(1) | ||
expect(exitErrors).toMatchInlineSnapshot(` | ||
[ | ||
[CommanderError: [91merror[39m[90m:[39m should have been foo!], | ||
] | ||
`) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { program } from '@commander-js/extra-typings' | ||
import { ZodError } from 'zod' | ||
|
||
import type { Command } from '@commander-js/extra-typings' | ||
import type { ZodTypeAny } from 'zod' | ||
|
||
/** | ||
* Parses an argument using a zod validator. If it fails, | ||
* it throws a well-formatted commander error using the Zod messages | ||
* @param validator Zod schema to validate with | ||
* @param cmd Optional commander Command to use when throwing an error. Defaults to `program` | ||
* @returns The zod type specified | ||
*/ | ||
export function validateArg<T extends ZodTypeAny>(validator: T, cmd?: Command) { | ||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
return (s: string) => parseArg(s, validator, cmd) | ||
} | ||
|
||
/** | ||
* Parses an argument using a zod validator. If it fails, | ||
* it throws a well-formatted commander error using the Zod messages | ||
* @param s Arg/option passed into CLI | ||
* @param validator Zod schema to validate with | ||
* @param cmd Optional commander Command to use when throwing an error. Defaults to `program` | ||
* @returns The zod type specified | ||
*/ | ||
export function parseArg<T extends ZodTypeAny>( | ||
s: string, | ||
validator: T, | ||
cmd?: Command | ||
): ReturnType<T['parse']> { | ||
try { | ||
return validator.parse(s) | ||
} catch (err) { | ||
if (err instanceof ZodError && err.issues.length > 0) { | ||
const messages = err.issues.map((e) => e.message) | ||
let messagesFmt = messages[0] | ||
for (const msg of messages.slice(1)) { | ||
messagesFmt += `\n ${msg}` | ||
} | ||
throw (cmd ?? program).error(`${chalk.redBright('error')}${chalk.grey(':')} ${messagesFmt}`) | ||
} else { | ||
throw err | ||
} | ||
} | ||
} |
Oops, something went wrong.