Node.js Daq command-line tool simple ghItlh
internal CLI tools law' framework poQ pagh. files batch rename, internal API Qu', repetitive git sequence wrap script - wa' Node file shebang line 'ej package.json entry. Commander pagh, Yargs pagh, build step pagh. real subcommands 'ej generated --help poQ vaj framework lo'; qaSpa' overhead (Qagh ghaj pagh).
minimum viable CLI
Node CLI Hoch rap qaS: shebang (shell Sov), process.argv raw arguments vaD.
#!/usr/bin/env node
const args = process.argv.slice(2);
console.log('You passed:', args);
chmod +x rename-files.js
./rename-files.js --dry-run photos/
# You passed: [ '--dry-run', 'photos/' ]
process.argv Node binary path 'ej script path vo' ghoS - .slice(2) cha' drop, user actual typed ghaj.
dependency pagh flags parse
wa' cha' flags Dung hand-rolled parser tedious 'ach full argument-parsing library poQ pagh. Node built-in util.parseArgs (Node 20 vo' stable) common cases cover:
#!/usr/bin/env node
import { parseArgs } from 'node:util';
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
options: {
'dry-run': { type: 'boolean', default: false },
output: { type: 'string', short: 'o', default: './out' },
verbose: { type: 'boolean', short: 'v', default: false },
},
allowPositionals: true,
});
console.log(values); // { 'dry-run': true, output: './out', verbose: false }
console.log(positionals); // ['photos/']
./rename-files.js --dry-run -o ./renamed photos/
named flags, short aliases, defaults, type coercion (--dry-run real boolean, string "true" pagh) - external dependencies pagh. library habit qaSpa' Sov.
files laD 'ej actual work
rename tool directory laD, entry Hoch act. Node fs/promises API async/await readable (nested callbacks pagh):
import { readdir, rename } from 'node:fs/promises';
import { join, extname } from 'node:path';
async function renamePhotos(dir, { dryRun }) {
const entries = await readdir(dir);
const photos = entries.filter((name) => ['.jpg', '.png'].includes(extname(name).toLowerCase()));
for (const [index, name] of photos.entries()) {
const ext = extname(name);
const newName = `photo-${String(index + 1).padStart(3, '0')}${ext}`;
const from = join(dir, name);
const to = join(dir, newName);
if (dryRun) {
console.log(`${name} -> ${newName} (dry run)`);
} else {
await rename(from, to);
console.log(`${name} -> ${newName}`);
}
}
}
dry-run branch legh: filesystem mutate Hoch vaD preview mode (exactly nuq qaS, Qu'be') ten extra lines worth. real directory tool trust vs backup neH first dare.
useful exit codes 'ej errors
script reH 0 exit shell scripting 'ej CI Daq invisible - && chains if checks exit code Dung (log output parse pagh).
async function main() {
const dir = positionals[0];
if (!dir) {
console.error('Error: missing target directory');
console.error('Usage: rename-files.js [--dry-run] [-o <dir>] <directory>');
process.exit(1);
}
try {
await renamePhotos(dir, { dryRun: values['dry-run'] });
} catch (err) {
console.error('Failed:', err.message);
process.exit(1);
}
}
main();
process.exit(1) usage error 'ej caught exception cha' Daq: rename-files.js photos/ && echo done rename Qapla' vaj neH "done" print - pipeline pagh pre-commit hook vaD Dun.
real command installable
script wa' repo Daq node scripts/rename-files.js Qu' - one-off Dun. real command (rename-photos anywhere) bin field package.json Daq chel:
{
"name": "rename-photos",
"version": "1.0.0",
"type": "module",
"bin": {
"rename-photos": "./rename-files.js"
}
}
npm link
rename-photos --dry-run photos/
npm link symlink global node_modules/.bin Daq chenmoH, command system-wide available (development). npm Daq published bin field npx rename-photos pagh global npm install -g Qu'.
filesystem mock pagh test
renamePhotos directory path parameter nob (hardcode pagh) - tests Daq real temporary directory point (fs mock pagh):
import { mkdtemp, writeFile, readdir, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { test, expect } from 'vitest';
test('renames jpg and png files sequentially', async () => {
const dir = await mkdtemp(join(tmpdir(), 'rename-test-'));
await writeFile(join(dir, 'holiday.jpg'), '');
await writeFile(join(dir, 'notes.txt'), '');
await renamePhotos(dir, { dryRun: false });
const files = await readdir(dir);
expect(files).toContain('photo-001.jpg');
expect(files).toContain('notes.txt'); // untouched — not a photo extension
await rm(dir, { recursive: true });
});
real (temporary) directory path-joining 'ej extension-handling bugs catch (mocked fs module paper over).
framework lo'
tool real subcommands (mytool deploy, mytool rollback, mytool status) ghaj, flags 'ej help text Hoch, hand-rolled dispatch logic Dun pagh - Commander pagh similar library subcommand routing 'ej generated --help vaD. single-purpose script vaD parseArgs 'ej bin entry real shareable command nob, dependency pagh.
yaj
useful CLI framework poQ pagh - shebang line, argument parsing (modern Node parseArgs), meaningful exit codes, dry-run mode (filesystem touch vaj). bin field chel node scripts/thing.js type Qo' 'ej command name type.