Files
skills/docs/security-stack/crypto/crypto-bin/src/bin/hash-password.md
2026-05-29 08:20:56 +01:00

4.2 KiB

suaveplan-hash-password bin entry

(no submodules)

Purpose

This file is the platform-runtime entry for the suaveplan-hash-password binary shipped by @suaveplan/crypto-bin. It is intentionally a minimal shim: it constructs an HashPasswordCliEnv adapter from process.stdout and process.stderr, then delegates to the testable main() function in ../hash-password/hash-password.ts. All argument parsing, algorithm dispatch, and exit-code logic live in that pure core so they can be unit tested by direct invocation rather than by spawning a subprocess.

The shim exists so the package can publish a Node-style bin entry under ./dist/bin/hash-password.js (with the #!/usr/bin/env node shebang preserved by Vite) without coupling the testable core to process.* globals. Anything that touches process.argv / process.exit / process.stdout is confined to this module.

Features

  • Forwards process.argv.slice(2) into main() and exits with the returned numeric code.
  • Wires process.stdout.write / process.stderr.write into the HashPasswordCliEnv adapter — the core never references process directly.
  • Auto-exec guard: only runs main() when invoked as the top-level module (process.argv[1] ends with hash-password.js). Importing this module from a test or another script does not trigger an exit.
  • Coverage-excluded by design — this module is exercised end-to-end via the bin install path; the logic it wraps is fully covered by ../hash-password/hash-password.test.ts.
  • Shebang line emitted verbatim into the dist bundle so the file is directly executable once symlinked by bun install / npm install.

Usage: Basic Example

# Once installed, the bin is on PATH via the package.json `bin` map
$ suaveplan-hash-password --password "correct horse battery staple"
$argon2id$v=19$m=65536,t=3,p=1$<salt>$<hash>
$ echo $?
0

Usage: Advanced Example

# Pipe-friendly verify (exit 0 = match, 1 = mismatch, 3+ = error)
$ STORED='$argon2id$v=19$m=65536,t=3,p=1$<salt>$<hash>'
$ if suaveplan-hash-password --password "$PW" --verify "$STORED"; then
>     echo "login ok"
> else
>     case $? in
>         1) echo "wrong password" ;;
>         3) echo "stored hash has unrecognised algorithm" ;;
>         4) echo "stored hash is corrupt" ;;
>         *) echo "argument error" ;;
>     esac
> fi

For programmatic invocation that does not spawn a process, import the testable core directly instead of this shim:

import { main } from "@suaveplan/crypto-bin";
const code = main(["--password", "pw"], {
    write: (s) => process.stdout.write(s),
    writeError: (s) => process.stderr.write(s),
});

API Reference

This module has no public exports — it is a side-effect entry point. Behaviour:

  • On import, registers the auto-exec guard and (if invoked as the top-level script) calls main(argv, env) and exits with its return value.
  • argv: process.argv.slice(2) — same shape main() accepts.
  • env.write(s): forwards s to process.stdout.write synchronously.
  • env.writeError(s): forwards s to process.stderr.write synchronously.

For the contract of main() itself (exit codes, accepted flags, error classes) see ../hash-password/hash-password.md.

Implementation Notes

The auto-exec guard checks process.argv[1]?.endsWith("hash-password.js") rather than import.meta.url === ... so the same module survives Vite's ES-module bundling without ESM-vs-CJS surprises in older Node hosts. The guard string matches the bundled filename (hash-password.js) deliberately — the source is .ts and is never executed directly in production.

/* istanbul ignore */ markers wrap the guard and the process.exit call because the test suite invokes main() directly with a captured env adapter (see ../hash-password/hash-password.test.ts). Spawning a subprocess to cover this two-line wrapper would add filesystem and Node runtime coupling to the test suite without exercising any logic that is not already covered. The vitest.config.ts coverage exclude list documents this decision in a sibling comment so future readers do not mistake the exclusion for an oversight.