Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

comline

Package Overview
Dependencies
Maintainers
0
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

comline - npm Package Compare versions

Comparing version 0.0.4 to 0.1.0

dist/cli.d.ts

108

dist/cli.js
// src/cli.ts
import * as fs from "node:fs";
import * as path from "node:path";
import {z} from "zod";
import {zodToJsonSchema} from "zod-to-json-schema";
// src/option-parsers.ts
function parseBooleanOption(arg) {
if (arg === `false`)
return false;
if (arg === `0`)
return false;
return true;
}
function parseNumberOption(arg) {
if (arg === ``)
return 1;
if (/^,+$/.test(arg))
return arg.length + 1;
return Number.parseFloat(arg);
}
function parseStringOption(arg) {
return arg;
}
function parseArrayOption(arg) {
return arg.split(` `);
}
// src/retrieve-positional-args.ts

@@ -10,2 +33,3 @@ function retrievePositionalArgs(cliName, positionalArgTree, passed) {

const positionalArgs = endOfOptionsDelimiterIndex === -1 ? undefined : passed.slice(endOfOptionsDelimiterIndex + 1);
const namedPositionalArgs = [];
const validPositionalArgs = [];

@@ -26,3 +50,6 @@ let treePointer = positionalArgTree;

}
return [];
return {
path: [],
route: ``
};
}

@@ -46,2 +73,3 @@ for (const positionalArg of positionalArgs) {

treePointer = treePointer[1][positionalArg];
namedPositionalArgs.push(positionalArg);
validPositionalArgs.push(positionalArg);

@@ -52,2 +80,3 @@ } else if (Object.keys(treePointer[1]).length > 0) {

treePointer = treePointer[1][variablePath];
namedPositionalArgs.push(variablePath);
validPositionalArgs.push(positionalArg);

@@ -83,25 +112,10 @@ continue;

}
return validPositionalArgs;
return {
path: validPositionalArgs,
route: namedPositionalArgs.join(`/`)
};
}
// src/option-parsers.ts
function parseBooleanOption(arg) {
if (arg === `false`)
return false;
if (arg === `0`)
return false;
return true;
}
function parseNumberOption(arg) {
if (arg === ``)
return 1;
if (/^,+$/.test(arg))
return arg.length + 1;
return Number.parseFloat(arg);
}
function parseStringOption(arg) {
return arg;
}
// src/encapsulate.ts
var encapsulateConsole = function() {
function encapsulateConsole() {
const createMockFn = () => {

@@ -128,4 +142,4 @@ const calls = [];

return { mockConsoleCalls, restoreConsole };
};
var withCapturedOutput = function(fn, options = {
}
function withCapturedOutput(fn, options = {
console: true,

@@ -171,3 +185,3 @@ stdout: true,

};
};
}
function encapsulate(fn, options) {

@@ -231,5 +245,11 @@ const {

}
var myTree = required({
hello: optional({
world: null,
$name: optional({ good: required({ morning: null }) })
})
});
// src/cli.ts
var retrieveArgValue = function(argument, flag2) {
function retrieveArgValue(argument, flag2) {
const isSwitch = argument.startsWith(`--`);

@@ -246,8 +266,7 @@ const [key, value] = argument.split(`=`);

return retrievedValue;
};
}
function cli({
cliName,
positionalArgTree,
options,
optionsSchema,
routes,
routeOptions,
discoverConfigPath = () => path.join(process.cwd(), `${cliName}.config.json`)

@@ -262,5 +281,11 @@ }, logger = {

let optionsFromConfig;
const positionalArgs = positionalArgTree ? retrievePositionalArgs(cliName, positionalArgTree, passed) : [];
const positionalArgs = routes ? retrievePositionalArgs(cliName, routes, passed) : { path: [], route: `` };
const route = routeOptions[positionalArgs.route];
const options = route?.options ?? {};
const optionsSchema = route?.optionsSchema ?? z.object({});
if (route === undefined) {
throw new Error(`Could not find options for route "${positionalArgs.route}". Valid routes are: \n\t- ${Object.keys(routeOptions).join(`\n\t- `)}`);
}
if (discoverConfigPath) {
const configFilePath = discoverConfigPath(positionalArgs);
const configFilePath = discoverConfigPath(positionalArgs.path);
if (configFilePath) {

@@ -304,7 +329,17 @@ if (fs.existsSync(configFilePath)) {

return {
positionalArgs,
suppliedOptions,
writeJsonSchema: (filepath) => {
const jsonSchema = zodToJsonSchema(optionsSchema);
fs.writeFileSync(filepath, JSON.stringify(jsonSchema, null, `\t`));
inputs: {
case: positionalArgs.route,
path: positionalArgs.path,
opts: suppliedOptions
},
writeJsonSchema: (outdir) => {
for (const [unsafeRoute, optionsGroup] of Object.entries(routeOptions)) {
if (optionsGroup === null) {
continue;
}
const safeRoute = unsafeRoute.replaceAll(`/`, `.`);
const jsonSchema = zodToJsonSchema(optionsGroup.optionsSchema);
const filepath = path.resolve(outdir, `${cliName}.${safeRoute || `main`}.schema.json`);
fs.writeFileSync(filepath, JSON.stringify(jsonSchema, null, `\t`));
}
}

@@ -319,2 +354,3 @@ };

parseBooleanOption,
parseArrayOption,
optional,

@@ -321,0 +357,0 @@ encapsulate,

{
"name": "comline",
"version": "0.0.4",
"version": "0.1.0",
"license": "MIT",

@@ -20,18 +20,22 @@ "author": {

"dependencies": {
"zod": "3.22.4",
"zod-to-json-schema": "3.22.5"
"zod": "3.23.8",
"zod-to-json-schema": "3.23.2"
},
"devDependencies": {
"@types/bun": "1.0.12",
"@types/bun": "1.1.8",
"@types/node": "22.5.4",
"@types/tmp": "0.2.6",
"concurrently": "8.2.2",
"tmp": "0.2.3",
"tsup": "8.0.2",
"vitest": "1.4.0"
"tsup": "8.2.4",
"vitest": "2.0.5"
},
"scripts": {
"build": "bun build --outdir dist --target node --external zod --external zod-to-json-schema -- src/cli.ts",
"build:js": "bun build --outdir dist --target node --external zod --external zod-to-json-schema -- src/cli.ts",
"build:dts": "tsup",
"build": "concurrently \"bun:build:*\"",
"lint:biome": "biome check -- .",
"lint:eslint": "eslint .",
"lint:eslint": "eslint --flag unstable_ts_config -- .",
"lint:types": "tsc --noEmit",
"lint:types:watch": "tsc --watch --noEmit",
"lint": "bun run lint:biome && bun run lint:eslint && bun run lint:types",

@@ -38,0 +42,0 @@ "test": "vitest",

import * as fs from "node:fs"
import * as path from "node:path"
import type { ZodSchema } from "zod"
import { z } from "zod"
import { zodToJsonSchema } from "zod-to-json-schema"
import type { Flag } from "./flag"
import type { Tree, TreePath } from "./tree"
import { parseStringOption } from "./option-parsers"
import { retrievePositionalArgs } from "./retrieve-positional-args"
import { parseStringOption } from "./option-parsers"
import type { Flat, ToPath, Tree, TreeMap, TreePath } from "./tree"
export * from "./encapsulate"
export * from "./flag"
export * from "./option-parsers"
export * from "./encapsulate"
export * from "./tree"
export * from "./flag"

@@ -24,2 +26,4 @@ export type CliOptionValue =

const FILENAME_CHAR_ALLOWLIST = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-`
export type CliOption<T extends CliOptionValue> = (T extends string

@@ -42,13 +46,29 @@ ? {

export type CommandLineInterface<
PositionalArgTree extends Tree,
Options extends Record<string, CliOptionValue>,
> = {
export type CliParseOutput<CLI extends CommandLineInterface<any>> = Flat<
Readonly<{
[K in keyof CLI[`routeOptions`]]: K extends string
? Readonly<{
case: K
path: ToPath<K, `/`>
opts: CLI[`routeOptions`][K] extends { optionsSchema: any }
? z.infer<CLI[`routeOptions`][K][`optionsSchema`]>
: null
}>
: never
}>[keyof CLI[`routeOptions`]]
>
export type OptionsGroup<Options extends Record<string, CliOptionValue> | null> =
Options extends Record<string, CliOptionValue>
? {
options: { [K in keyof Options]: CliOption<Options[K]> }
optionsSchema: ZodSchema<Options>
}
: null
export type CommandLineInterface<Routes extends Tree> = {
cliName: string
discoverConfigPath?: (
positionalArgs: TreePath<PositionalArgTree>,
) => string | undefined
positionalArgTree?: PositionalArgTree
options: { [K in keyof Options]: CliOption<Options[K]> }
optionsSchema: ZodSchema<Options>
routeOptions: TreeMap<Routes, OptionsGroup<any>>
routes?: Routes
discoverConfigPath?: (positionalArgs: TreePath<Routes>) => string | undefined
}

@@ -75,14 +95,15 @@

export type CliRoutes<CLI extends CommandLineInterface<any>> = CLI[`routes`]
export function cli<
PositionalArgs extends Tree,
Options extends Record<string, CliOptionValue>,
CLI extends CommandLineInterface<Routes>,
Routes extends Tree = Exclude<CLI[`routes`], undefined>,
>(
{
cliName,
positionalArgTree,
options,
optionsSchema,
routes,
routeOptions,
discoverConfigPath = () =>
path.join(process.cwd(), `${cliName}.config.json`),
}: CommandLineInterface<PositionalArgs, Options>,
}: CLI,
logger = {

@@ -94,14 +115,27 @@ error: (...args: any[]) => {

): (args: string[]) => {
positionalArgs: TreePath<PositionalArgs>
suppliedOptions: Options
writeJsonSchema: (path: string) => void
inputs: CliParseOutput<CLI>
writeJsonSchema: (outdir: string) => void
} {
return (passed = process.argv) => {
type Options = CLI[`routeOptions`][keyof CLI[`routeOptions`]]
let failedValidation = false
let optionsFromConfig: Options | undefined
const positionalArgs = positionalArgTree
? retrievePositionalArgs(cliName, positionalArgTree, passed)
: ([] as any)
const positionalArgs = routes
? retrievePositionalArgs(cliName, routes, passed)
: { path: [] as TreePath<Routes>, route: `` }
const route: OptionsGroup<any> = routeOptions[positionalArgs.route]
const options = route?.options ?? {}
const optionsSchema = route?.optionsSchema ?? z.object({})
if (route === undefined) {
throw new Error(
`Could not find options for route "${positionalArgs.route}". Valid routes are: \n\t- ${Object.keys(routeOptions).join(`\n\t- `)}`,
)
}
if (discoverConfigPath) {
const configFilePath = discoverConfigPath(positionalArgs)
const configFilePath = discoverConfigPath(positionalArgs.path)
if (configFilePath) {

@@ -167,7 +201,22 @@ if (fs.existsSync(configFilePath)) {

return {
positionalArgs,
suppliedOptions,
writeJsonSchema: (filepath) => {
const jsonSchema = zodToJsonSchema(optionsSchema)
fs.writeFileSync(filepath, JSON.stringify(jsonSchema, null, `\t`))
inputs: {
case: positionalArgs.route,
path: positionalArgs.path,
opts: suppliedOptions,
} as unknown as CliParseOutput<CLI>,
writeJsonSchema: (outdir) => {
for (const [unsafeRoute, optionsGroup] of Object.entries(
routeOptions as Record<string, OptionsGroup<any> | null>,
)) {
if (optionsGroup === null) {
continue
}
const safeRoute = unsafeRoute.replaceAll(`/`, `.`)
const jsonSchema = zodToJsonSchema(optionsGroup.optionsSchema)
const filepath = path.resolve(
outdir,
`${cliName}.${safeRoute || `main`}.schema.json`,
)
fs.writeFileSync(filepath, JSON.stringify(jsonSchema, null, `\t`))
}
},

@@ -174,0 +223,0 @@ }

@@ -16,1 +16,5 @@ export function parseBooleanOption(arg: string): boolean {

}
export function parseArrayOption(arg: string): string[] {
return arg.split(` `)
}

@@ -1,2 +0,2 @@

import type { Tree, TreePath } from "./tree"
import type { Join, Tree, TreePath, TreePathName } from "./tree"

@@ -7,3 +7,6 @@ export function retrievePositionalArgs<PositionalArgTree extends Tree>(

passed: string[],
): TreePath<PositionalArgTree> {
): {
path: TreePath<PositionalArgTree>
route: Join<TreePathName<PositionalArgTree>>
} {
const endOfOptionsDelimiterIndex = passed.indexOf(`--`)

@@ -15,2 +18,3 @@ const positionalArgs =

const namedPositionalArgs: string[] = []
const validPositionalArgs: string[] = []

@@ -32,3 +36,6 @@ let treePointer: object = positionalArgTree

}
return [] as TreePath<PositionalArgTree>
return {
path: [] as TreePath<PositionalArgTree>,
route: `` as Join<TreePathName<PositionalArgTree>>,
}
}

@@ -53,2 +60,3 @@ for (const positionalArg of positionalArgs) {

treePointer = treePointer[1][positionalArg]
namedPositionalArgs.push(positionalArg)
validPositionalArgs.push(positionalArg)

@@ -61,2 +69,3 @@ } else if (Object.keys(treePointer[1]).length > 0) {

treePointer = treePointer[1][variablePath]
namedPositionalArgs.push(variablePath)
validPositionalArgs.push(positionalArg)

@@ -94,3 +103,8 @@ continue

}
return validPositionalArgs as TreePath<PositionalArgTree>
return {
path: validPositionalArgs as TreePath<PositionalArgTree>,
route: namedPositionalArgs.join(`/`) as Join<
TreePathName<PositionalArgTree>
>,
}
}

@@ -16,9 +16,62 @@ export function required<T>(arg: T): [`required`, T] {

? T[1][K] extends Tree
? [K extends `$${string}` ? string : K, ...TreePath<T[1][K]>]
: [K extends `$${string}` ? string : K]
? [K extends `$${string}` ? string & {} : K, ...TreePath<T[1][K]>]
: [K extends `$${string}` ? string & {} : K]
:
| (T[1][K] extends Tree
? [K extends `$${string}` ? string : K, ...TreePath<T[1][K]>]
: [K extends `$${string}` ? string : K])
? [K extends `$${string}` ? string & {} : K, ...TreePath<T[1][K]>]
: [K extends `$${string}` ? string & {} : K])
| []
}[keyof T[1]]
export type TreePathName<T extends Tree> = {
[K in keyof T[1]]: T[0] extends `required`
? T[1][K] extends Tree
? [K, ...TreePathName<T[1][K]>]
: [K]
: (T[1][K] extends Tree ? [K, ...TreePathName<T[1][K]>] : [K]) | []
}[keyof T[1]]
export type Flat<R extends { [K in PropertyKey]: any }> = {
[K in keyof R]: R[K]
}
export type TreeMap<T extends Tree, P> = {
[K in Join<TreePathName<T>, `/`>]: P
}
export type Join<
Arr extends any[],
Separator extends string = ``,
> = Arr extends []
? ``
: Arr extends [infer First extends string]
? First
: Arr extends [infer First extends string, ...infer Rest extends string[]]
? `${First}${Separator}${Join<Rest, Separator>}`
: string
export type ToPath<
S extends string,
D extends string,
> = S extends `${infer T extends string}${D}${infer U extends string}`
? T extends `$${string}`
? [string & {}, ...ToPath<U, D>]
: [T, ...ToPath<U, D>]
: S extends `$${string}`
? [string & {}]
: [S]
export type MySplit = ToPath<`hello/$world/good/morning`, `/`>
const myTree = required({
hello: optional({
world: null,
$name: optional({ good: required({ morning: null }) }),
}),
})
type MyTreePath = TreePath<typeof myTree>
type MyTreeMap = TreeMap<typeof myTree, null>
type MyTreePathsJoined = Join<MyTreePath, `/`>
// type MyTreePathsJoined$ = Join<MyTreePath$, `/`>
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc