47ff0804a5
* feat(cli): add --port flag as the user-facing port knob
Introduces --port (and KANDEV_PORT) as the primary, command-aware port
flag: in start/run it sets the Go backend port (the proxy front door
the browser actually hits), and in dev it sets the Next dev server port.
--backend-port and --web-port still work but emit a one-time stderr
deprecation note pointing at --port and --web-internal-port respectively.
* feat(cli): print labelled "open:" URL on startup
Replaces the ambiguous "[kandev] url:" / "ready at" lines with a
labelled block that always shows the user-facing URL first, plus the
backend (api) and web (internal) URLs when they differ. Each command
now declares its own publicUrl: backend in start/run (where the Go
server reverse-proxies Next.js), web in dev (browser hits Next direct).
* style(cli): apply prettier formatting to args.test.ts
* refactor(cli): tighten arg parsing per review feedback
- Validate value-taking flags (--port, --backend-port, --web-internal-port,
--web-port, --version) and reject missing/non-numeric values with a clear
ParseError instead of leaking NaN into the port validator.
- Drop the test-only _resetWarnedFlagsForTest export and the module-level
warned-flag set; parseArgs now returns deduped deprecatedFlags and cli.ts
emits warnings command-aware (e.g. --backend-port → KANDEV_BACKEND_PORT
in dev, where --port maps to web).
- Stop double-printing the "open:" URL: logStartupInfo now lists labelled
backend (api) / web (internal) URLs only, leaving the post-readiness
open: line as the single source of truth.
- Fix help text: --web-internal-port works in dev too.
- Drop multi-line JSDoc blocks per CLAUDE.md style guide.
* fix(cli): validate env-var ports and collapse comment to one-liner
Catches non-numeric KANDEV_PORT / KANDEV_BACKEND_PORT / KANDEV_WEB_PORT in
envPort with a clear ParseError instead of letting NaN reach
ensureValidPort. Drops a multi-line comment per CLAUDE.md style.
* fix(cli): reject non-integer port values
Number.isFinite accepts floats like 3000.5 which then fail downstream
in ensureValidPort with a generic message. Tighten parsePort and
envPort to require integers and add float tests for both flag and
env-var paths.
* fix(cli): reject out-of-range port values with flag-named errors
Adds 1-65535 range checks to parsePort and envPort so values like 0,
-1, or 65536 fail with a flag/env-var-named ParseError instead of
reaching ensureValidPort with a generic 'backend port' label.
* test(cli): cover KANDEV_WEB_PORT happy path and range validation
* test(cli): cover KANDEV_WEB_PORT in run/start mode too
* fix(cli): explicit CLI flag wins over env var for port resolution
Previously KANDEV_BACKEND_PORT silently overrode --port because each
process port resolved its own env var before the generic --port
fallback. New precedence per process: explicit CLI flag > generic
--port flag > explicit env var > generic KANDEV_PORT. Also relabel
the startup info from "backend (api)" / "web (internal)" to plain
"backend" / "web" — "internal" was misleading in dev where the web
URL is what users open.
* style(cli): collapse resolvePorts comment to one line
* fix(cli): reject empty --version= and empty port env vars
- --version= (no value after =) now throws ParseError instead of
silently storing the empty string as the version tag.
- envPort distinguishes unset (undefined) from empty (""); an empty
KANDEV_PORT / KANDEV_BACKEND_PORT / KANDEV_WEB_PORT is now treated
as misconfiguration and fails fast.
* refactor(cli): make --port a plain alias for --backend-port
Drops the command-aware split where --port mapped to web in dev and
backend in run/start. --port and --backend-port now both set
options.backendPort regardless of command, and KANDEV_PORT is a plain
fallback for KANDEV_BACKEND_PORT. The whole point of the original
fix was that the *backend port* is the URL users actually open in
run/start (because the Go server reverse-proxies Next.js); a single
flag that always means "the backend port" matches that mental model
without per-command surprises.
Removes deprecationReplacement and the command-aware warning logic
since the two flags are now interchangeable. --web-port → --web-internal-port
deprecation stays.
* test(cli): re-add KANDEV_WEB_PORT run-mode coverage dropped in refactor
135 lines
4.0 KiB
TypeScript
135 lines
4.0 KiB
TypeScript
export type Command = "run" | "dev" | "start";
|
|
|
|
export type CliOptions = {
|
|
command: Command;
|
|
version?: string;
|
|
backendPort?: number;
|
|
webPort?: number;
|
|
verbose?: boolean;
|
|
debug?: boolean;
|
|
};
|
|
|
|
export type ParseResult = {
|
|
options: CliOptions;
|
|
showHelp: boolean;
|
|
/** Deprecated flags seen on the command line. cli.ts emits warnings after parsing. */
|
|
deprecatedFlags: string[];
|
|
};
|
|
|
|
export class ParseError extends Error {}
|
|
|
|
export function parseArgs(argv: string[]): ParseResult {
|
|
const opts: CliOptions = { command: "run" };
|
|
let showHelp = false;
|
|
const deprecatedFlags: string[] = [];
|
|
const noteDeprecated = (flag: string) => {
|
|
if (!deprecatedFlags.includes(flag)) deprecatedFlags.push(flag);
|
|
};
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const arg = argv[i];
|
|
if (arg === "--help" || arg === "-h") {
|
|
showHelp = true;
|
|
continue;
|
|
}
|
|
if (arg === "dev" || arg === "run" || arg === "start") {
|
|
opts.command = arg;
|
|
continue;
|
|
}
|
|
if (arg === "--version") {
|
|
opts.version = takeValue(argv, i, "--version");
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (arg.startsWith("--version=")) {
|
|
const value = arg.slice("--version=".length);
|
|
if (value.length === 0) throw new ParseError("--version requires a value");
|
|
opts.version = value;
|
|
continue;
|
|
}
|
|
if (arg === "--dev") {
|
|
opts.command = "dev";
|
|
continue;
|
|
}
|
|
// --port is an alias for --backend-port (the user-facing port in run/start).
|
|
if (arg === "--port" || arg === "--backend-port") {
|
|
opts.backendPort = parsePort(takeValue(argv, i, arg), arg);
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (arg.startsWith("--port=") || arg.startsWith("--backend-port=")) {
|
|
const flag = arg.startsWith("--port=") ? "--port" : "--backend-port";
|
|
opts.backendPort = parsePort(arg.slice(flag.length + 1), flag);
|
|
continue;
|
|
}
|
|
if (arg === "--web-internal-port") {
|
|
opts.webPort = parsePort(takeValue(argv, i, "--web-internal-port"), "--web-internal-port");
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (arg.startsWith("--web-internal-port=")) {
|
|
opts.webPort = parsePort(arg.slice("--web-internal-port=".length), "--web-internal-port");
|
|
continue;
|
|
}
|
|
if (arg === "--web-port") {
|
|
opts.webPort = parsePort(takeValue(argv, i, "--web-port"), "--web-port");
|
|
noteDeprecated("--web-port");
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (arg.startsWith("--web-port=")) {
|
|
opts.webPort = parsePort(arg.slice("--web-port=".length), "--web-port");
|
|
noteDeprecated("--web-port");
|
|
continue;
|
|
}
|
|
if (arg === "--verbose" || arg === "-v") {
|
|
opts.verbose = true;
|
|
continue;
|
|
}
|
|
if (arg === "--debug") {
|
|
opts.debug = true;
|
|
continue;
|
|
}
|
|
}
|
|
return { options: opts, showHelp, deprecatedFlags };
|
|
}
|
|
|
|
function takeValue(argv: string[], i: number, flag: string): string {
|
|
const v = argv[i + 1];
|
|
if (v === undefined || v.startsWith("-")) {
|
|
throw new ParseError(`${flag} requires a value`);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
function parsePort(raw: string, flag: string): number {
|
|
const n = Number(raw);
|
|
if (raw === "" || !Number.isInteger(n) || n < 1 || n > 65535) {
|
|
throw new ParseError(`${flag} value must be an integer between 1 and 65535, got "${raw}"`);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
export type ResolvedPorts = {
|
|
backendPort?: number;
|
|
webPort?: number;
|
|
};
|
|
|
|
// CLI flags beat env vars; KANDEV_PORT is an alias for KANDEV_BACKEND_PORT.
|
|
export function resolvePorts(options: CliOptions, env: NodeJS.ProcessEnv): ResolvedPorts {
|
|
return {
|
|
backendPort:
|
|
options.backendPort ?? envPort(env, "KANDEV_BACKEND_PORT") ?? envPort(env, "KANDEV_PORT"),
|
|
webPort: options.webPort ?? envPort(env, "KANDEV_WEB_PORT"),
|
|
};
|
|
}
|
|
|
|
function envPort(env: NodeJS.ProcessEnv, name: string): number | undefined {
|
|
const val = env[name];
|
|
if (val === undefined) return undefined;
|
|
const n = Number(val);
|
|
if (val === "" || !Number.isInteger(n) || n < 1 || n > 65535) {
|
|
throw new ParseError(`${name} must be an integer between 1 and 65535, got "${val}"`);
|
|
}
|
|
return n;
|
|
}
|