@mirta/staged-args
v0.4.5
Published
⚡ Staged CLI arguments parser with TypeScript support
Maintainers
Readme
@mirta/staged-args
Утилита поэтапного разбора аргументов командной строки. Предназначена для создания сложных, многоуровневых CLI-инструментов с поддержкой глобальных флагов, безопасной обработки ошибок и гибких подсказок.
@mirta/staged-args — это минималистичный, типобезопасный инструмент на основе parseArgs из node:util для разбора аргументов в несколько этапов. Подходит для фреймворков, генераторов и оркестраторов, где требуется:
- Сначала обработать глобальные флаги (
--config,--verbose), - Затем — командные опции,
- При этом не прерывать выполнение при ошибках ввода,
- Иметь возможность локализации сообщений.
Пакет @mirta/staged-args предназначен исключительно для Node.js-инструментов (≥ 20.10.0), не используется в рантайме Duktape.
📦 Установка
pnpm add @mirta/staged-args🚀 Быстрый старт
Создайте экземпляр парсера с аргументами командной строки:
import { createStagedArgs } from '@mirta/staged-args'
const staged = createStagedArgs(process.argv.slice(2))Определите схему опций:
const schema = {
config: { type: 'string', default: 'mirta.json' },
verbose: { type: 'boolean' },
} as constВыполните поэтапный разбор:
const result = staged.parse(schema)
if (result.hasErrors) {
// Обработка ошибок
result.errors.forEach(error => {
console.error(`Ошибка: ${error.type}, опция: ${error.option}`)
})
process.exit(1)
}
const { data: globals } = result
console.log('Глобальные флаги:', globals)Позиционные аргументы (например, команда и её параметры) доступны через positionals:
const { positionals } = result.data
// → ['deploy', 'staging']⚠️ Неизвестные опции (например,
--force) не попадают вpositionals— они остаются опциями и могут быть отловлены какunknown-optionпри использованииparseFinal.
Для продолжения разбора используйте stagedArgs:
const { stagedArgs } = result.data
const commandResult = stagedArgs.parseFinal(commandSchema)🔍 Архитектура
Поэтапный парсинг (Staged Parsing)
@mirta/staged-args позволяет:
- Разделить разбор аргументов на этапы.
- Сначала обработать флаги, влияющие на конфигурацию.
- Позже — разобрать команду и её опции, основываясь на загруженных данных.
Это критично для инструментов вроде @mirta/cli, create-mirta, nx, где --config должен быть обработан до выбора команды.
⚠️ Парсер отслеживает, какие позиционные аргументы уже использовались как значения (например,
--port 3000), и помечает их индексы, чтобы избежать повторного присваивания.
Однако сами опции (например,--port) не "исчезают" — они могут быть обработаны снова на следующих этапах, если указаны в схеме.
Это гарантирует, что значениеdeployне будет ошибочно использовано как значение для--portна втором этапе.
Безопасный результат: Result<T, E>
Функции parse и parseFinal возвращают:
type Result<TData, TError>
= | { hasErrors: false, data: TData }
| { hasErrors: true, errors: TError[] }➡️ Пока вы не проверите hasErrors, поле data недоступно в типовой системе.
➡️ Это принуждает к явной обработке ошибок и предотвращает использование некорректных данных.
Почему это важно
if (result.hasErrors) {
// ❌ TypeScript не позволит обратиться к result.data
console.log(result.data.values) // → Ошибка компиляции
}
// ✅ Только после проверки
if (!result.hasErrors) {
console.log(result.data.values) // → OK
console.log(result.data.positionals) // → OK
console.log(result.data.stagedArgs) // → OK — можно продолжить разбор
}Такой подход:
- Гарантирует, что вы не используете данные при наличии ошибок.
- Делает
stagedArgsдоступным только при успешном разборе. - Совместим с системами локализации, такими как
@mirta/i18n.
Гибкие подсказки: suggest?: SuggestFunc
Можно передать функцию подсказки:
const args = createStagedArgs(process.argv.slice(2), {
suggest: (unknown, known) => {
return known.includes('config') ? 'config' : undefined
}
})Или использовать suggestClosest из @mirta/basics/fuzzy:
import { suggestClosest } from '@mirta/basics/fuzzy'
const staged = createStagedArgs(process.argv.slice(2), {
suggest: suggestClosest,
})Это не обязательная зависимость — вы решаете, какую стратегию использовать.
Ошибки времени выполнения и разработки
ParseError— возвращается вResult:{ type: 'unknown-option', option: '--confog', suggestion: 'config' }Подлежит локализации, не прерывает выполнение.
SchemaError— выбрасывается в виде исключения:
Например, при дублировании имён опций.
Это ошибка разработки, не может возникнуть у конечного пользователя.
🧰 API
createStagedArgs(args: string[], options?: { suggest?: SuggestFunc }): StagedArgs
Создаёт экземпляр парсера.
Параметры:
args— массив строк (обычноprocess.argv.slice(2)).options.suggest— функция, возвращающая возможную коррекцию для неизвестной опции.
Возвращает:
Объект с методами parse, parseFinal.
parse<TSchema>(schema: TSchema): Result<Values<TSchema>, ParseError>
Разбирает аргументы по схеме.
Сохраняет состояние: какие опции и значения уже обработаны.
Возвращает:
Result<ParsedArgs<TSchema>, ParseError>, где поле data содержит:
values— распарсенные значения опций,positionals— необработанные позиционные аргументы,stagedArgs— новый этап парсинга, включающий текущую схему (можно продолжать разбор).
✅ Используйте
parseдля многоэтапного разбора.
parseFinal<TSchema>(schema: TSchema): Result<Values<TSchema>, ParseError>
Аналог parse, но считается финальным этапом:
- Проверяет наличие неизвестных опций → ошибка
unknown-option. - Не возвращает
stagedArgs— дальнейший парсинг невозможен.
Возвращает:
Result<ParsedArgsFinal<TSchema>, ParseError>, где поле data содержит:
values— распарсенные значения,positionals— позиционные аргументы.
⚠️ Используйте
parseFinalдля команд или финальной валидации.
getRemainingArgs(): string[]
Возвращает необработанные аргументы для передачи следующему этапу.
type ParseError
Поддерживаемые типы ошибок:
| { type: 'unknown-option', option: string, suggestion?: string }
| { type: 'missing-value', option: string }✅ Пример: многоэтапный CLI
const staged = createStagedArgs(process.argv.slice(2), { suggest: suggestClosest })
// Этап 1: глобальные флаги
const globalSchema = { config: { type: 'string' }, verbose: { type: 'boolean' } } as const
const globalResult = staged.parse(globalSchema)
if (globalResult.hasErrors) {
// Показываем локализованные сообщения
logErrors(globalResult.errors)
process.exit(1)
}
// ✅ data доступно — ошибок нет
const { positionals, stagedArgs } = globalResult.data
// Загружаем конфиг на основе --config
const config = loadConfig(globalResult.data.values.config)
// Этап 2: команда и её позиционные параметры
const command = positionals[0]
if (!command) {
console.error('Команда не указана')
process.exit(1)
}
const commandSchema = config.commands[command]
if (!commandSchema) {
console.error(`Неизвестная команда: ${command}`)
process.exit(1)
}
// Продолжаем разбор: опции вроде --verbose остаются доступными
const commandResult = stagedArgs.parseFinal(commandSchema)
if (commandResult.hasErrors) {
logErrors(commandResult.errors)
process.exit(1)
}
// Выполняем
run(command, globalResult.data.values, commandResult.data.values)🛠 Внутренняя архитектура
- Модульность: каждый компонент — отдельный файл.
- Без зависимостей: только
node:util. - ESM-first: поддержка
#src/*черезimports. - TypeScript: полная типизация, включая вывод
Values<TSchema>.
