379230892b
* chore: ignore local secret file
* feat(cli): replace self-update with package-manager-owned runtime resolution
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(release): unified vX.Y.Z release flow with brew + npm sibling channels
GitHub release artifacts become the canonical build output. npm and Homebrew
are independent sibling channels — both consume the same GitHub release
tarballs; neither depends on the other.
Workflow changes (.github/workflows/release.yml):
- Trigger is now workflow_dispatch only (maintainer-only, gated by repo write
access). Inputs: bump dropdown (patch/minor/major) + dry_run checkbox.
- Single job graph orchestrates everything in one run:
prepare (bump, PR, merge, tag) → build-web/build-cli → build-bundles (5
platforms) → docker → publish-release → publish-npm + update-homebrew-tap.
- The prepare job inlines version bump, optionalDependencies sync, CHANGELOG
generation, PR create + squash-merge, and tag push. Replaces what
release-pr.sh used to do locally.
- Tag-push trigger removed; everything runs from the dispatch flow.
New scripts (called by the workflow):
- package-cli.sh: builds + esbuild-bundles the CLI for inclusion in release
bundles (cli/bin/cli.js + cli/dist/cli.bundle.js + package.json).
- package-npm-runtime.sh: generates per-platform @kdlbs/runtime-* npm
packages from release bundles. Sets npm os/cpu fields so npm 7+ skips
non-matching platforms during npx kandev install.
- publish-npm.sh: verifies GitHub assets exist, downloads them, repackages,
publishes all 5 @kdlbs/runtime-* packages + main kandev@X.Y.Z.
- update-homebrew-tap.sh: clones kdlbs/homebrew-kandev via SSH using the
HOMEBREW_TAP_DEPLOY_KEY repo secret, generates Formula/kandev.rb pointing
at the GitHub release assets, pushes directly to main.
Removed scripts:
- release.sh and release-pr.sh — both are redundant with the new workflow.
Required CI secrets:
- NPM_TOKEN: granular access token with publish access to kandev + @kdlbs/*
- HOMEBREW_TAP_DEPLOY_KEY: SSH private key for kdlbs/homebrew-kandev
* docs: brew install, runtime resolution, and GHA-driven release flow
- README.md: add Homebrew quick start (brew install kdlbs/kandev/kandev),
update NPX docs to require npm 7+, document the three update paths.
- docs/cli.md: rewrite architecture diagram to show three-step runtime
resolution. Replace --version <tag> docs with --version (CLI semver) +
--runtime-version <tag> (advanced/debug). Remove KANDEV_NO_UPDATE_PROMPT.
- apps/cli/README.md: add Homebrew install path; document
package-manager-owned updates.
- apps/cli/README_internal.md: rewrite to describe the npm package vs
GitHub bundle artifact-shape distinction and the three-step resolver.
Document the GHA-driven release UI (Run workflow + bump dropdown).
- AGENTS.md: add Release & Versioning Convention section describing the
unified single-SemVer flow, the sibling channel model, and the GHA flow.
* fix(release): release-bundle bin/cli.js must require the esbuild bundle
The previous package-cli.sh copied apps/cli/bin/cli.js verbatim into the
release bundle. That file requires `../dist/cli.js` (unbundled), which
only works when node_modules is present alongside it.
In the GitHub release bundle (used by Homebrew and manual installs),
there is no node_modules — `require('tree-kill')` and `require('tar')`
inside dist/cli.js would crash at startup.
Now package-cli.sh writes a release-specific bin/cli.js that requires
`../dist/cli.bundle.js` instead. The bundled file has all Node deps
inlined via esbuild, so it runs standalone in the Cellar.
The npm package keeps its own bin/cli.js (in apps/cli/bin/) requiring
the unbundled dist/cli.js, since npm installs tree-kill etc. normally.
Caught while smoke-testing the release bundle locally with
KANDEV_BUNDLE_DIR — backend + web boot succeeded after this fix.
* feat(release): switch npm publishing to Trusted Publishers (OIDC)
The previous flow required an NPM_TOKEN secret. Local testing with that
token failed with EOTP because the granular access token still required a
2FA OTP on publish despite the account-level setting change. npm now
recommends Trusted Publishers (OIDC) over long-lived tokens.
Workflow change (release.yml):
- publish-npm job declares `permissions: id-token: write` so GitHub Actions
can mint OIDC tokens.
- Removes NODE_AUTH_TOKEN env var. The npm CLI auto-detects the OIDC
environment and exchanges the GitHub OIDC token for a short-lived
npmjs.com publish credential.
- Comment block updated to reflect the new auth model. NPM_TOKEN is no
longer listed in required secrets (already deleted from repo secrets).
Script change (publish-npm.sh):
- Updates `npm publish` calls to include `--provenance` so each published
package ships with a signed attestation linking it back to this commit
and workflow run.
- Header comment explains the OIDC auth model.
Setup required on npmjs.com (per package, before first OIDC publish):
For each of the 6 packages — kandev plus @kdlbs/runtime-{linux-x64,
linux-arm64,darwin-x64,darwin-arm64,win32-x64} — go to the package's
"Settings → Publishing access" and add a Trusted Publisher pointing at
this repo (kdlbs/kandev), workflow filename release.yml, and (optionally)
an environment.
* fix(release): pin optionalDependencies to bootstrap version in committed source
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.
* fix: address PR review comments on release flow + runtime resolution
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).
* fix: address second-round PR review (cleanup traps, host-key pinning, supply chain)
Latest review (Claude + Greptile) flagged three security/cleanup items
on top of the previous fix commit. All three addressed below.
publish-npm.sh:
- Add `trap 'rm -rf "$WORK_DIR"' EXIT` immediately after `mktemp -d` so
the tmpdir is cleaned up even when `pnpm build` or `npm publish` fails
partway through (set -e would otherwise abort before the explicit
rm at the end).
- Remove redundant `pnpm build` before `npm publish` — the `prepublishOnly`
script in package.json already runs the build.
update-homebrew-tap.sh:
- Combine `cleanup_ssh` + WORK_DIR cleanup into one EXIT trap so `git clone`
/ `git push` failures don't leak the tmpdir.
- Pin GitHub's published SSH host keys (ed25519, ECDSA, RSA) into a temp
`known_hosts` file instead of using `accept-new` + `/dev/null`. This
closes the "trust any host key on first connect" gap. The pinned keys
come from https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints.
release.yml:
- Replace the curl|tar git-cliff install with `orhun/git-cliff-action@v4`
(already used in publish-release). The action pins to a digest, so we
no longer need to verify a tarball checksum manually. Removes a
supply-chain attack surface where a tampered git-cliff binary could run
during `prepare` (which has contents:write + pull-requests:write).
* revert: keep curl install for git-cliff, add sha512 verification instead
The previous commit (18b2a5c) replaced our `curl | tar` install with
orhun/git-cliff-action@v4 under the banner "supply chain". On reflection
that swap doesn't actually improve anything:
- @v4 is a moving tag, not a digest pin — same trust as a tagged curl URL.
- Both depend on trusting the same maintainer (orhun).
- The action adds a third-party step we don't audit.
The legitimate concern from the review was "no integrity verification on
the downloaded tarball." Address that directly: pin the asset version,
embed its published sha512, and abort if the download doesn't match.
The hash for v2.6.1's x86_64-linux-gnu tarball came from
https://github.com/orhun/git-cliff/releases/download/v2.6.1/<asset>.sha512.
This restores the original setup with a real integrity check and one
fewer external dependency.
* fix: address remaining PR nits (env restore, sha256 download dir)
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.
* fix: address third-round PR comments
- update-homebrew-tap.sh: Homebrew formula was calling
`bin.write_env_script` on the bin directory with target `cli/bin/cli.js`,
which would create the wrapper at `$HOMEBREW_PREFIX/bin/cli.js` (named
after the target's basename) instead of `$HOMEBREW_PREFIX/bin/kandev`.
Result: `brew install kandev` would have produced `command not found`.
Use `(bin/"kandev").write_env_script(...)` to name the wrapper
explicitly. Caught by Claude as a P0 blocker.
- publish-npm.sh: main `kandev` publish lacked the EPUBLISHCONFLICT
idempotency handling that the runtime-package loop has. If publish-npm
was re-run after a partial failure (e.g. the runtime packages succeeded
but the main package failed and was then manually published), the
re-run would fail on the main package even though the version was
already on the registry. Now: capture output, treat
"already published" as success, fail loudly with full output on real
errors. Caught by Greptile as P1.
* refactor(release): direct push to main instead of PR-and-merge dance
The prepare job was opening a release branch, creating a PR, and
immediately squash-merging it via gh CLI. That pattern was carried over
from the old release-pr.sh, but in this workflow it earns nothing:
- main has no branch protection, so direct push is allowed.
- The PR was force-merged in the same step (no `--auto`, no waiting for
required checks), so it provided no pre-merge validation.
- Discoverability is fine without a PR — git tag, GitHub release, workflow
run, and main commit are all visible.
The PR-and-merge sequence had ~5 GitHub API roundtrips, each a potential
failure point, plus added 30+ lines of YAML and required pull-requests:
write on the job. Replaced with a straight-line commit + tag + push:
git fetch origin main && git checkout main && git reset --hard origin/main
... bump pkg.json, regen CHANGELOG, npm install ...
git add ... && git commit && git tag -a && git push origin main "$TAG"
Job permissions tightened to contents: write only.
Race condition note: if main advances between fetch and push, the push
fails non-fast-forward — same outcome as the old PR pattern, where the
auto-merge would have failed. For workflow_dispatch releases with the
maintainer actively driving, the race is essentially never going to
happen, and the recovery (re-run the workflow) is the same.
The dry-run path still bumps + regenerates CHANGELOG inside the runner
so the maintainer can verify the computed values look right, just
without committing or pushing anything.
* chore(release): drop verbose comment about direct-push rationale
* docs: drop 'no first-run download' note from user-facing docs
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.
* Revert "chore: ignore local secret file"
The /secret entry was added defensively after the bootstrap test, but it
clutters .gitignore with a project-specific path that nobody else needs.
Drop it; secrets stay out of the repo by other means (don't commit them).
* fix: address third-pass review (stale workflow comments + missing test)
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.
* fix(release): push commit + tag atomically
Without --atomic, git push can succeed for the tag ref and fail for the
main ref on a non-fast-forward, leaving an orphaned tag at the pre-bump
commit. The orphan then trips the 'Tag already exists on origin' guard
on the next re-run, requiring manual tag deletion to unblock.
--atomic makes the multi-ref push succeed-or-fail as a unit.
Caught by Greptile as P1.
* refactor(cli): drop runtime.ts step 3 — cache fallback was unreachable
resolveRuntime's runtimeVersion parameter and cache-fallback step were
dead code from production's perspective. The --runtime-version path is
handled entirely in run.ts (findCachedRelease + downloadRuntimeVersion +
findBundleRoot + validateBundle), and run.ts only ever called
resolveRuntime() with no argument. The parameter and step 3 only existed
because tests exercised them.
Two parallel "compute cache dir" implementations (runtime.ts:65 and
run.ts:157) were a drift hazard if the cache layout ever changes.
Changes:
- Drop the runtimeVersion parameter from resolveRuntime.
- Drop the cache step from runtime.ts entirely; remove the unused
CACHE_DIR + findBundleRoot imports.
- Narrow RuntimeSource from "env" | "npm" | "cache" to "env" | "npm",
and drop the now-unused tag field from ResolvedRuntime.
- Remove the three "cache fallback" tests from runtime.test.ts. The
--runtime-version cache lookup is still covered by run.test.ts via
findCachedRelease tests.
Tests: 134 → 131 (three deleted tests are obsolete, the production
behavior they were verifying is now covered by run.test.ts).
* chore(test): drop dead mock setup left over from runtime.ts step 3 removal
After feb7c5a removed the cache fallback from runtime.ts, the test file
had two dangling artifacts:
- The vi.mock("./constants", ...) block + the mockCacheDir holder + the
mockCacheDir.value = tmpDir assignment in beforeEach. runtime.ts no
longer imports anything from ./constants, so the mock was a no-op
that would mislead readers tracing why constants are mocked here.
- The PLATFORM = "macos-arm64" constant was unused — platform
determinism is already enforced by the vi.mock("./platform", ...)
block one section above.
Caught by Claude and the github-code-quality bot.
204 lines
7.6 KiB
Bash
Executable File
204 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Update the kdlbs/homebrew-kandev tap with a new formula for the given release.
|
|
#
|
|
# Downloads .sha256 files from the GitHub release to populate the formula,
|
|
# generates Formula/kandev.rb, and pushes (or opens a PR).
|
|
#
|
|
# Usage:
|
|
# update-homebrew-tap.sh <version> <tag>
|
|
#
|
|
# Arguments:
|
|
# version SemVer string (e.g. 0.17.0)
|
|
# tag Git tag (e.g. v0.17.0)
|
|
#
|
|
# Environment:
|
|
# HOMEBREW_TAP_REPO Override tap repo (default: kdlbs/homebrew-kandev).
|
|
# HOMEBREW_TAP_DEPLOY_KEY Private SSH key (PEM) for the tap repo. When set,
|
|
# clone via SSH and push directly to main. This is
|
|
# the default CI path. Deploy keys cannot open PRs.
|
|
# HOMEBREW_TAP_PUSH When no deploy key is present, set to "1" to push
|
|
# directly via `gh`/HTTPS instead of opening a PR.
|
|
set -euo pipefail
|
|
|
|
VERSION="${1:?Usage: $0 <version> <tag>}"
|
|
TAG="${2:?Usage: $0 <version> <tag>}"
|
|
TAP_REPO="${HOMEBREW_TAP_REPO:-kdlbs/homebrew-kandev}"
|
|
PUSH_DIRECT="${HOMEBREW_TAP_PUSH:-0}"
|
|
|
|
bold() { printf '\033[1m%s\033[0m' "$*"; }
|
|
green() { printf '\033[32m%s\033[0m' "$*"; }
|
|
red() { printf '\033[31m%s\033[0m' "$*"; }
|
|
|
|
log() { echo " >> $*"; }
|
|
log_ok() { echo " $(green "ok") $*"; }
|
|
|
|
die() {
|
|
echo "$(red "Error:") $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
# -- Determine clone strategy: deploy key (SSH) or gh (HTTPS) -----------------
|
|
|
|
CLONE_METHOD="gh"
|
|
WORK_DIR="$(mktemp -d)"
|
|
TAP_DIR="$WORK_DIR/tap"
|
|
SSH_KEY_FILE=""
|
|
KNOWN_HOSTS_FILE=""
|
|
|
|
cleanup() {
|
|
[[ -n "${SSH_KEY_FILE:-}" && -f "$SSH_KEY_FILE" ]] && rm -f "$SSH_KEY_FILE"
|
|
[[ -n "${KNOWN_HOSTS_FILE:-}" && -f "$KNOWN_HOSTS_FILE" ]] && rm -f "$KNOWN_HOSTS_FILE"
|
|
[[ -n "${WORK_DIR:-}" && -d "$WORK_DIR" ]] && rm -rf "$WORK_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
if [[ -n "${HOMEBREW_TAP_DEPLOY_KEY:-}" ]]; then
|
|
CLONE_METHOD="ssh"
|
|
# Materialize the deploy key + a custom GIT_SSH_COMMAND so it doesn't pollute ~/.ssh
|
|
SSH_KEY_FILE="$(mktemp)"
|
|
chmod 600 "$SSH_KEY_FILE"
|
|
printf '%s\n' "$HOMEBREW_TAP_DEPLOY_KEY" > "$SSH_KEY_FILE"
|
|
# Ensure trailing newline (some secret stores strip it; openssh requires it)
|
|
if [[ "$(tail -c1 "$SSH_KEY_FILE")" != "" ]]; then
|
|
echo "" >> "$SSH_KEY_FILE"
|
|
fi
|
|
|
|
# Pin GitHub's published SSH host keys so we don't trust whatever key the
|
|
# remote presents. Source: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
|
|
KNOWN_HOSTS_FILE="$(mktemp)"
|
|
chmod 600 "$KNOWN_HOSTS_FILE"
|
|
cat > "$KNOWN_HOSTS_FILE" <<'EOF'
|
|
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
|
|
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
|
|
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
|
|
EOF
|
|
export GIT_SSH_COMMAND="ssh -i $SSH_KEY_FILE -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$KNOWN_HOSTS_FILE -o LogLevel=ERROR"
|
|
log "Using SSH deploy key for tap auth (pinned github.com host keys)"
|
|
fi
|
|
|
|
# -- Fetch sha256 values from GitHub release ----------------------------------
|
|
|
|
fetch_sha256() {
|
|
local platform="$1"
|
|
local sha_file="kandev-${platform}.tar.gz.sha256"
|
|
local content
|
|
# Download into WORK_DIR so the EXIT trap cleans it up automatically.
|
|
content="$(gh release download "$TAG" --pattern "$sha_file" --dir "$WORK_DIR" 2>/dev/null && cat "$WORK_DIR/$sha_file")" || {
|
|
die "Failed to download $sha_file from release $TAG."
|
|
}
|
|
# sha256 files contain: <hash> <filename> — extract just the hash
|
|
echo "$content" | awk '{print $1}'
|
|
}
|
|
|
|
log "Fetching sha256 values from GitHub release $TAG..."
|
|
SHA_MACOS_ARM64="$(fetch_sha256 macos-arm64)"
|
|
SHA_MACOS_X64="$(fetch_sha256 macos-x64)"
|
|
SHA_LINUX_ARM64="$(fetch_sha256 linux-arm64)"
|
|
SHA_LINUX_X64="$(fetch_sha256 linux-x64)"
|
|
log_ok "sha256 values retrieved"
|
|
|
|
# -- Clone tap repo -----------------------------------------------------------
|
|
# (WORK_DIR and TAP_DIR were created near the top so the EXIT trap covers them.)
|
|
|
|
log "Cloning $TAP_REPO..."
|
|
if [[ "$CLONE_METHOD" == "ssh" ]]; then
|
|
git clone "git@github.com:${TAP_REPO}.git" "$TAP_DIR"
|
|
else
|
|
gh repo clone "$TAP_REPO" "$TAP_DIR"
|
|
fi
|
|
log_ok "Tap cloned to $TAP_DIR"
|
|
|
|
# -- Generate formula ---------------------------------------------------------
|
|
|
|
FORMULA_PATH="$TAP_DIR/Formula/kandev.rb"
|
|
mkdir -p "$(dirname "$FORMULA_PATH")"
|
|
|
|
GITHUB_BASE="https://github.com/kdlbs/kandev/releases/download/${TAG}"
|
|
|
|
cat > "$FORMULA_PATH" <<EOF
|
|
class Kandev < Formula
|
|
desc "Manage tasks, orchestrate agents, review changes, and ship value"
|
|
homepage "https://github.com/kdlbs/kandev"
|
|
license "AGPL-3.0-only"
|
|
version "$VERSION"
|
|
|
|
# Node is required: the CLI launcher and Next.js standalone server both need it.
|
|
depends_on "node"
|
|
|
|
on_macos do
|
|
if Hardware::CPU.arm?
|
|
url "${GITHUB_BASE}/kandev-macos-arm64.tar.gz"
|
|
sha256 "$SHA_MACOS_ARM64"
|
|
else
|
|
url "${GITHUB_BASE}/kandev-macos-x64.tar.gz"
|
|
sha256 "$SHA_MACOS_X64"
|
|
end
|
|
end
|
|
|
|
on_linux do
|
|
if Hardware::CPU.arm?
|
|
url "${GITHUB_BASE}/kandev-linux-arm64.tar.gz"
|
|
sha256 "$SHA_LINUX_ARM64"
|
|
else
|
|
url "${GITHUB_BASE}/kandev-linux-x64.tar.gz"
|
|
sha256 "$SHA_LINUX_X64"
|
|
end
|
|
end
|
|
|
|
def install
|
|
libexec.install Dir["*"]
|
|
# cli/bin/cli.js has #!/usr/bin/env node shebang. (bin/"kandev").write_env_script
|
|
# creates a wrapper at \$HOMEBREW_PREFIX/bin/kandev that sets KANDEV_BUNDLE_DIR
|
|
# (so the CLI launcher finds bin/ and web/ in the Cellar) and KANDEV_VERSION
|
|
# (read by run.ts at startup so the launcher logs "release: X.Y.Z" instead of
|
|
# "release: (env)"). Calling write_env_script on the bin directory itself would
|
|
# name the wrapper after the target's basename (cli.js), giving the wrong name.
|
|
(bin/"kandev").write_env_script libexec/"cli/bin/cli.js",
|
|
KANDEV_BUNDLE_DIR: libexec.to_s,
|
|
KANDEV_VERSION: version.to_s
|
|
end
|
|
|
|
test do
|
|
assert_match "kandev launcher", shell_output("#{bin}/kandev --help")
|
|
end
|
|
end
|
|
EOF
|
|
|
|
log_ok "Formula written to Formula/kandev.rb"
|
|
|
|
# -- Commit and push (or open PR) ---------------------------------------------
|
|
|
|
cd "$TAP_DIR"
|
|
git config user.email "release-bot@kandev"
|
|
git config user.name "kandev release bot"
|
|
|
|
git add Formula/kandev.rb
|
|
|
|
if git diff --cached --quiet; then
|
|
log "Formula unchanged — nothing to commit"
|
|
exit 0
|
|
fi
|
|
|
|
git commit -m "kandev $VERSION"
|
|
|
|
# Deploy keys can push but cannot open PRs (PR creation requires user/app auth).
|
|
# So when using SSH (deploy key path) or HOMEBREW_TAP_PUSH=1, push directly.
|
|
if [[ "$CLONE_METHOD" == "ssh" || "$PUSH_DIRECT" == "1" ]]; then
|
|
git push origin HEAD:main
|
|
log_ok "Pushed directly to main in $TAP_REPO"
|
|
else
|
|
BRANCH="update-kandev-$VERSION"
|
|
git branch -m "$BRANCH"
|
|
git push -u origin "$BRANCH"
|
|
gh pr create \
|
|
--repo "$TAP_REPO" \
|
|
--title "kandev $VERSION" \
|
|
--body "Updates formula to [kandev $VERSION](https://github.com/kdlbs/kandev/releases/tag/$TAG)." \
|
|
--head "$BRANCH" \
|
|
--base main
|
|
log_ok "PR opened in $TAP_REPO"
|
|
fi
|
|
|
|
# WORK_DIR cleanup happens via the EXIT trap.
|
|
echo "$(green "$(bold "Homebrew tap updated to $VERSION!")")"
|