From 71999be071ca3f347500decf4d0628db989cd69b Mon Sep 17 00:00:00 2001 From: arzafran Date: Wed, 3 Jun 2026 16:54:12 -0300 Subject: [PATCH] refactor: make lockfile priority explicit and derive the PM type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the LOCKFILES object (priority encoded implicitly via JS key order) with an ordered tuple array, and collapses the package-manager union + runtime set into a single const source of truth with a type guard, removing a cast. Behavior-preserving — all 13 tests still pass. Adds CHANGELOG.md documenting the 0.1.3 release. --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.ts | 43 +++++++++++++++++++++---------------------- 2 files changed, 68 insertions(+), 22 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e128914 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog + +All notable changes to this project are documented here. The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.3] - 2026-06-03 + +### Fixed + +- **Monorepo detection now works.** The directory walk previously exited after + checking only the starting directory, so running `omnes` from a sub-package + fell back to npm instead of finding the lockfile at the repository root. + Detection now walks up to the filesystem root. +- `--version` reads the version from `package.json` instead of a hardcoded + string that had drifted out of sync. +- `--help` and `--version` print to stdout instead of stderr, so they pipe and + redirect like other CLIs. +- A missing package manager now exits with code `127` (was `1`), matching the + documented API. + +### Added + +- Corepack `packageManager` field detection. An explicit + `"packageManager": "pnpm@9.x"` in `package.json` takes precedence over a + lockfile in the same directory. +- The informational `Using:` line now shows the fully resolved command + (e.g. `Using bun: bun run dev`). +- Unit tests covering traversal, Corepack detection, lockfile priority, and the + npm argument transform; Biome lint/format; and a GitHub Actions CI workflow. + +### Changed + +- Internal: pure detection and argument logic extracted into `src/lib.ts`, with + `src/omnes.ts` reduced to a thin executable entry point. +- Internal: `LOCKFILES` is an ordered tuple array rather than an object, so + detection priority is explicit instead of relying on object key order. No + behavior change. +- Internal: the supported package-manager list is a single `const` source of + truth, with the `PackageManager` type derived from it and a type guard + replacing a cast in Corepack-field parsing. + +## [0.1.2] and earlier + +Initial releases and documentation. See the git history for details. + +[0.1.3]: https://github.com/darkroomengineering/omnes/releases/tag/v0.1.3 diff --git a/src/lib.ts b/src/lib.ts index 9f2d551..3f6656e 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -1,25 +1,24 @@ import { existsSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; -// Supported package managers -export type PackageManager = "bun" | "pnpm" | "npm" | "yarn"; - -// Lockfile → package manager. Key order is the detection priority: the first -// match within a directory wins. Mirrors the table in the README. -export const LOCKFILES: Record = { - "bun.lockb": "bun", - "bun.lock": "bun", - "pnpm-lock.yaml": "pnpm", - "yarn.lock": "yarn", - "package-lock.json": "npm", -}; - -const PACKAGE_MANAGERS: ReadonlySet = new Set([ - "bun", - "pnpm", - "npm", - "yarn", -]); +// Supported package managers — single source of truth for the name list. The +// PackageManager type is derived from it so the two cannot drift. +export const PACKAGE_MANAGERS = ["bun", "pnpm", "npm", "yarn"] as const; +export type PackageManager = (typeof PACKAGE_MANAGERS)[number]; + +function isPackageManager(value: string): value is PackageManager { + return (PACKAGE_MANAGERS as readonly string[]).includes(value); +} + +// Lockfile → package manager, in detection-priority order: within a directory +// the first match wins. Mirrors the table in the README. +export const LOCKFILES: ReadonlyArray = [ + ["bun.lockb", "bun"], + ["bun.lock", "bun"], + ["pnpm-lock.yaml", "pnpm"], + ["yarn.lock", "yarn"], + ["package-lock.json", "npm"], +]; // npm built-in commands that don't require a "run" prefix export const NPM_BUILTIN_COMMANDS = new Set([ @@ -128,8 +127,8 @@ export function parsePackageManagerField( return undefined; } const name = value.split("@", 1)[0]?.trim(); - if (name !== undefined && PACKAGE_MANAGERS.has(name)) { - return name as PackageManager; + if (name !== undefined && isPackageManager(name)) { + return name; } return undefined; } @@ -170,7 +169,7 @@ export function detectPackageManager(startDir: string): PackageManager { return hint; } - for (const [lockfile, pm] of Object.entries(LOCKFILES)) { + for (const [lockfile, pm] of LOCKFILES) { if (existsSync(join(currentDir, lockfile))) { return pm; }