fix(cli): declare @wrnexus/authz dependency, exit cleanly on bad authz input
Round-1 review fixes for Task 13: - packages/cli/package.json was missing @wrnexus/authz, and packages/authz/package.json was missing @wrnexus/core despite importing its types in index.ts/middleware.ts/advanced.ts. Both only worked in-repo because bare "@wrnexus/*" specifiers resolve through the root tsconfig.json paths map; a standalone install of @wrnexus/cli or @wrnexus/authz would fail at runtime. - authz.ts's unknown/missing-subcommand and bad --dialect paths now console.error + process.exit(1), matching db.ts's convention, instead of throwing — index.ts's top-level catch previously printed those as a raw stack trace. Added a subprocess-level test that spawns the real CLI and asserts stderr has the usage line with no stack frame. - nextMigrationNumber now extracts the leading-digit run the same way db/migrate.ts's nextNumber does, instead of a fixed slice(0, 4) that would have undercounted once a migration number passed 9999.
This commit is contained in:
+20
-10
@@ -41,23 +41,33 @@ export async function loadAuthzCatalog(appDir: string): Promise<AuthzCatalog> {
|
||||
return mergeCatalogs(sources);
|
||||
}
|
||||
|
||||
/** Print a message and exit non-zero, matching db.ts's convention for user-facing
|
||||
* CLI errors: never throw, so index.ts's generic `main().catch` handler (which
|
||||
* prints the raw error, stack and all) is never reached for an expected failure. */
|
||||
function fail(message: string): never {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Same leading-digit extraction as db/migrate.ts's `nextNumber`: a fixed
|
||||
// `slice(0, 4)` would undercount once a migration number grows past 9999.
|
||||
function nextMigrationNumber(dir: string): string {
|
||||
if (!existsSync(dir)) return "0001";
|
||||
const numbers = readdirSync(dir)
|
||||
.map((name) => Number.parseInt(name.slice(0, 4), 10))
|
||||
.filter((value) => Number.isInteger(value));
|
||||
return String((numbers.length ? Math.max(...numbers) : 0) + 1).padStart(4, "0");
|
||||
let max = 0;
|
||||
for (const name of readdirSync(dir)) {
|
||||
const match = /^(\d+)/.exec(name);
|
||||
if (match) max = Math.max(max, Number(match[1]));
|
||||
}
|
||||
return String(max + 1).padStart(4, "0");
|
||||
}
|
||||
|
||||
/** Parse `--dialect=<value>` from CLI args. Defaults to sqlite; rejects unknown values. */
|
||||
function resolveDialect(args: string[]): Dialect {
|
||||
const flag = args.find((arg) => arg.startsWith("--dialect="));
|
||||
if (!flag) return "sqlite";
|
||||
const value = flag.split("=")[1];
|
||||
if ((DIALECTS as readonly string[]).includes(value ?? "")) return value as Dialect;
|
||||
throw new Error(
|
||||
`WRN-AUTHZ-INIT: unrecognised --dialect='${value}'. Use one of: ${DIALECTS.join(", ")}.`,
|
||||
);
|
||||
const value = flag.split("=")[1] ?? "";
|
||||
if ((DIALECTS as readonly string[]).includes(value)) return value as Dialect;
|
||||
return fail(`Unrecognised --dialect='${value}'. Use one of: ${DIALECTS.join(", ")}.`);
|
||||
}
|
||||
|
||||
export async function runAuthzCommand(
|
||||
@@ -118,6 +128,6 @@ export async function runAuthzCommand(
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(USAGE);
|
||||
fail(USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user