Argumentos de línea de órdenes en TypeScript con parseArgs

Node trae parseArgs para leer los argumentos de un programa de terminal, sin instalar nada. Con TypeScript tiene un extra: el tipo de cada opción sale de la propia configuración. Si una opción es booleana y no tiene valor por omisión, TypeScript sabe que puede faltar.

El programa

import { parseArgs } from "node:util";

// La configuración de las opciones define también sus tipos.
const { values, positionals } = parseArgs({
  options: {
    nombre: { type: "string", short: "n", default: "mundo" },
    veces: { type: "string", short: "v", default: "1" },
    gritar: { type: "boolean", short: "g" },
  },
  allowPositionals: true,
});

// values.nombre: string · values.gritar: boolean | undefined
const veces = Number(values.veces);
if (!Number.isInteger(veces) || veces < 1) {
  console.error(`--veces tiene que ser un entero positivo, no «${values.veces}»`);
  process.exit(2);
}
let saludo = `Hola, ${values.nombre}`;
if (values.gritar) saludo = saludo.toUpperCase() + "!";
for (let i = 0; i < veces; i++) console.log(saludo);
if (positionals.length > 0) console.log("Además:", positionals.join(" "));
npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es saludo.ts
tsc(sin errores)

Probándolo

node saludo.ts
salidaHola, mundo
node saludo.ts -n Ana --veces 2 -g
salidaHOLA, ANA!
HOLA, ANA!
node saludo.ts --nombre=Luis extra1 extra2
salidaHola, Luis
Además: extra1 extra2
node saludo.ts -v cero
salida--veces tiene que ser un entero positivo, no «cero»
(código de salida 2)

Una opción que no está en la configuración hace que parseArgs lance un error y el programa termine con código 1:

node saludo.ts --color azul
salidanode:internal/util/parse_args/parse_args:102
      throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(
            ^

TypeError [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '--color'. To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- "--color"
  code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION'
}

Node.js v24.22.0
(código de salida 1)

Los tipos salen de la configuración

OpciónConfiguraciónTipo de values.…
nombretype: "string" con defaultstring
vecestype: "string" con defaultstring: los números llegan como texto
gritartype: "boolean", sin defaultboolean | undefined

Por eso este programa, que olvida que nombre no tiene valor por omisión y usa una opción que no existe, no compila:

import { parseArgs } from "node:util";
const { values } = parseArgs({
  options: {
    nombre: { type: "string", short: "n" },
    gritar: { type: "boolean", short: "g" },
  },
});
console.log(values.nombre.toUpperCase()); // sin default: puede no venir
console.log(values.veces);                 // no existe esa opción
const g: string = values.gritar;           // es boolean | undefined
npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es tipos-error.ts
tsctipos-error.ts(8,13): error TS18048: "values.nombre" es posiblemente "undefined".
tipos-error.ts(9,20): error TS2339: La propiedad 'veces' no existe en el tipo '{ nombre?: string | undefined; gritar?: boolean | undefined; }'.
tipos-error.ts(10,7): error TS2322: El tipo 'boolean | undefined' no se puede asignar al tipo 'string'.
  El tipo 'undefined' no se puede asignar al tipo 'string'.
Los números llegan como texto. parseArgs solo conoce "string" y "boolean". Para --veces 2 hay que convertir con Number() y comprobar el resultado, como hace el programa: el tipo string te lo recuerda, porque values.veces * 2 no compilaría.
Salir con un código distinto de 0. process.exit(2) avisa a quien lanzó el programa, por ejemplo un script de shell, de que algo fue mal. Por convención, 2 significa «se usó mal».

Todo el código de esta página se comprobó con TypeScript 7.0.2 en modo estricto (--strict --locale es) y se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; los mensajes de error y las salidas están copiados de esa ejecución. Documentación oficial: util.parseArgs en la documentación de Node.js.