release.yml:
- The header comment block and dry_run input description still referenced
the PR-and-merge step that was removed in 53c2e45. Updated both to
describe the direct-commit flow.
runtime.test.ts:
- The runtime resolver's npm-package branch (step 2 — the primary path
after npx kandev@latest or npm install -g) had no positive coverage. In
the test env, require.resolve for @kdlbs/runtime-* throws
MODULE_NOT_FOUND, so the existing tests only exercised step 1 (env var)
and step 3 (cache via --runtime-version), plus the no-runtime-found
fallthrough.
Added two tests using vi.spyOn on Module._resolveFilename to point at a
fake bundle inside tmpDir:
- happy path: installed npm package resolves to source=npm with the
right bundleDir
- corrupt-bundle path: validateBundle errors propagate (rather than
being swallowed and falling through to "no runtime found")
These cover the real-world scenarios users will hit on either install
channel.
Tests: 132 → 134.
The phrasing only makes sense to readers who knew the previous model
required a first-run download. New users have no baseline to compare
against, so it adds noise. Removed from docs/cli.md and apps/cli/README.md
(which renders on npmjs.com). Kept in apps/cli/README_internal.md as a
design note for contributors.
runtime.test.ts: drop the redundant `delete process.env.KANDEV_BUNDLE_DIR`
in afterEach — `process.env = { ...origEnv }` already restores the
original environment. The redundant delete would silently un-set the var
if a developer had it in their shell, leaking test side effects across
suites.
update-homebrew-tap.sh: fetch_sha256 now downloads into $WORK_DIR instead
of /tmp/. The EXIT trap covers WORK_DIR, so the .sha256 files are cleaned
up automatically. Avoids leaking 5 tmpfiles per release run on long-lived
runners.
PR reviewers (Greptile, Claude, CodeRabbit) flagged six concrete issues.
Each is addressed below.
Critical:
- publish-npm.sh: previously exited 0 even when runtime packages failed to
publish, which would leave kandev@VERSION on the registry pointing at
@kdlbs/runtime-*@VERSION that never got published. Now: capture full npm
output on failure, distinguish "already published" (idempotent skip)
from real failures, abort before publishing main kandev if any runtime
fails.
- release.yml: build-bundles referenced needs.prepare.outputs.tag without
declaring prepare in its needs array. Static analysis flagged this.
Added prepare to the needs list.
Major:
- update-homebrew-tap.sh: the formula's write_env_script only injected
KANDEV_BUNDLE_DIR. run.ts:174 reads KANDEV_VERSION to display the
running version in startup logs; without it Homebrew users would see
"release: (env)" instead of "release: 0.17.0". Pass KANDEV_VERSION:
version.to_s through the wrapper.
- runtime.ts: the catch {} block silently swallowed validateBundle errors,
hiding "package installed but corrupt" cases behind the generic
"no runtime found" message. Split the lookup so MODULE_NOT_FOUND falls
through, but validation errors propagate.
- run.ts: the --runtime-version code path skipped validateBundle, missing
the file-existence checks that the npm/env paths get. Added it.
Minor:
- release.yml: workflow-level permissions defaulted to write for everyone.
Tightened to read-only at the workflow level; jobs that need writes
(prepare, docker, publish-release) declare them explicitly.
- args.test.ts: added missing tests for --runtime-version with no value
and --runtime-version followed by another flag, parallel to the
existing --port tests.
Tests: 132 passing (was 130; +2 new).
CI was failing because apps/cli/package.json declared optionalDependencies
at 0.16.0 but only 0.0.0-bootstrap exists on npm for those packages. The
lockfile couldn't resolve, and `pnpm install --frozen-lockfile` rejected
the working tree.
Pin optionalDependencies to 0.0.0-bootstrap in the committed source so the
lockfile resolves cleanly. The release workflow now bumps them to the real
version inside publish-npm.sh, just before publishing main kandev — at
which point the matching @kdlbs/runtime-*@VERSION tarballs have already
been published earlier in the same step. This avoids the chicken-and-egg
where the prepare job tried to bump optional deps to a version that didn't
yet exist on npm.
Workflow changes:
- prepare job: only bumps the main package version, not optional deps.
- publish-npm.sh: rewrites optionalDependencies to $VERSION right before
the main `npm publish`, after the 5 runtime packages have been published.
The published kandev@X.Y.Z still has its optionalDependencies pinned to
matching @kdlbs/runtime-*@X.Y.Z. Only the local source uses the bootstrap
pin for development convenience.
The CLI no longer downloads runtime bundles from GitHub on first run. Instead:
- A new resolveRuntime() in apps/cli/src/runtime.ts resolves the bundle in
three steps: KANDEV_BUNDLE_DIR env var (Homebrew wrapper, tests) → installed
@kdlbs/runtime-{platform} npm package → cached download (only when
--runtime-version is given explicitly).
- apps/cli/src/run.ts is split: launch logic separated from download logic.
The download path stays for the explicit --runtime-version flag only.
- The --version flag now prints CLI semver (was: runtime tag selector). The
old behavior moves to --runtime-version, documented as advanced/debug.
- The npm self-update prompt (apps/cli/src/update.ts) is removed entirely.
- apps/cli/package.json declares optionalDependencies for five
@kdlbs/runtime-{platform} packages and engines.npm: ">=7" (required for
optional-dep resolution during npx).
- Adds esbuild + a "bundle" script that produces dist/cli.bundle.js — a
single-file CJS bundle with all Node deps inlined. The Homebrew formula
uses this; the npm package keeps the unbundled dist/cli.js.
Tests: 130/130 passing. Bundle smoke-tested in a directory with no
node_modules to confirm tree-kill is correctly inlined.
* 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