Files
toon/packages/cli/src/index.ts
2025-11-03 11:39:10 +01:00

115 lines
3.1 KiB
TypeScript

import type { Delimiter } from '../../toon/src'
import type { InputSource } from './types'
import * as path from 'node:path'
import process from 'node:process'
import { defineCommand, runMain } from 'citty'
import { consola } from 'consola'
import { name, version } from '../../toon/package.json' with { type: 'json' }
import { DEFAULT_DELIMITER, DELIMITERS } from '../../toon/src'
import { decodeToJson, encodeToToon } from './conversion'
import { detectMode } from './utils'
const main = defineCommand({
meta: {
name,
description: 'TOON CLI — Convert between JSON and TOON formats',
version,
},
args: {
input: {
type: 'positional',
description: 'Input file path (omit or use "-" to read from stdin)',
required: false,
},
output: {
type: 'string',
description: 'Output file path',
alias: 'o',
},
encode: {
type: 'boolean',
description: 'Encode JSON to TOON (auto-detected by default)',
alias: 'e',
},
decode: {
type: 'boolean',
description: 'Decode TOON to JSON (auto-detected by default)',
alias: 'd',
},
delimiter: {
type: 'string',
description: 'Delimiter for arrays: comma (,), tab (\\t), or pipe (|)',
default: ',',
},
indent: {
type: 'string',
description: 'Indentation size',
default: '2',
},
lengthMarker: {
type: 'boolean',
description: 'Use length marker (#) for arrays',
default: false,
},
strict: {
type: 'boolean',
description: 'Enable strict mode for decoding',
default: true,
},
stats: {
type: 'boolean',
description: 'Show token statistics',
default: false,
},
},
async run({ args }) {
const input = args.input
const inputSource: InputSource = !input || input === '-'
? { type: 'stdin' }
: { type: 'file', path: path.resolve(input) }
const outputPath = args.output ? path.resolve(args.output) : undefined
// Parse and validate indent
const indent = Number.parseInt(args.indent || '2', 10)
if (Number.isNaN(indent) || indent < 0) {
throw new Error(`Invalid indent value: ${args.indent}`)
}
// Validate delimiter
const delimiter = args.delimiter || DEFAULT_DELIMITER
if (!(Object.values(DELIMITERS)).includes(delimiter as Delimiter)) {
throw new Error(`Invalid delimiter "${delimiter}". Valid delimiters are: comma (,), tab (\\t), pipe (|)`)
}
const mode = detectMode(inputSource, args.encode, args.decode)
try {
if (mode === 'encode') {
await encodeToToon({
input: inputSource,
output: outputPath,
delimiter: delimiter as Delimiter,
indent,
lengthMarker: args.lengthMarker === true ? '#' : false,
printStats: args.stats === true,
})
}
else {
await decodeToJson({
input: inputSource,
output: outputPath,
indent,
strict: args.strict !== false,
})
}
}
catch (error) {
consola.error(error)
process.exit(1)
}
},
})
runMain(main)