diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 8e25410..d499a78 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -274,6 +274,14 @@ jobs: echo "Publishing ${dir}" publish_one "${dir%/}" done + # The meta publish runs its prepublishOnly hook ON PURPOSE — unlike + # sdk-publish it must NOT use --ignore-scripts. `napi prepublish` + # injects the per-platform optionalDependencies (absent from the + # committed package.json) and copies the addons; skipping it ships a + # meta with no optionalDependencies and no native binary for + # consumers. The immutable-Release write that hook used to attempt is + # disabled at its source via `--no-gh-release` in the prepublishOnly + # script (crates/akua-napi/package.json). echo "Publishing meta @akua-dev/native" publish_one crates/akua-napi diff --git a/AGENTS.md b/AGENTS.md index 64613ed..61a0dc7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,7 +103,7 @@ That's it. akua does **not** specify `App`, `Environment`, `Cluster`, `Secret`, **Embedded-engine builds + feature wiring** (these cost real time when missed): the engine wasm assets (`crates/{helm,kustomize}-engine-wasm/assets/*.wasm`) are **gitignored** — a fresh clone or `git worktree` can't compile `akua-core` until `task build:engines` runs (or you copy the built assets in). The Go engine source (`crates/helm-engine-wasm/go-src/`) rebuilds via `task build:helm-engine-wasm` (Go→wasip1) — a *different* artifact from the Rust render-worker's `task build:render-worker`; when you change Go engine code, rebuild the engine, not the worker. And a new `akua-core` feature is **dead in the shipped binary/SDK** unless it's added to the `akua-core` dep `features = [...]` in **both** `crates/akua-cli/Cargo.toml` and `crates/akua-napi/Cargo.toml` — otherwise it silently compiles to its `#[cfg(not(feature))]` stub there even though `cargo test -p akua-core --features …` passes. -**Releases are tag-triggered and expensive — batch them.** A pushed `v*` tag fires the full build matrix (Windows + macOS jobs dominate at ~30min and can't be self-hosted), npm publishing, and container publishing. Accumulate fixes on `main` and cut **one** tag when a human explicitly asks — never tag per change/chunk. The release derives its version from the tag (`scripts/set-cargo-version.sh` + a smoke-test guard asserting `akua -V == tag`); the committed `Cargo.toml` version is a dev placeholder, so don't expect hand-bumping it to affect a release. Tags are immutable: never delete and re-push one. Follow [docs/releasing.md](docs/releasing.md) for the release and fail-closed recovery contract. +**Releases are tag-triggered and expensive — batch them.** A pushed `v*` tag fires the full build matrix (Windows + macOS jobs dominate at ~30min and can't be self-hosted), npm publishing, and container publishing. Accumulate fixes on `main` and cut **one** tag when a human explicitly asks — never tag per change/chunk. The release derives its version from the tag (`scripts/set-cargo-version.sh` + a smoke-test guard asserting `akua -V == tag`); the committed `Cargo.toml` version is a dev placeholder, so don't expect hand-bumping it to affect a release. Tags are immutable: never delete and re-push one. The `@akua-dev/native` publish must not use `--ignore-scripts`; see [docs/releasing.md](docs/releasing.md) for the release contract and fail-closed recovery procedure. **Project domain is `akua.dev`.** Reverse-DNS namespaces (OCI annotations, Java-style package roots, anything following the `org.kcllang.*` shape) use **`dev.akua.*`** — *not* `org.akua.*`. The npm scope is `@akua-dev` because npm scopes have to be unique on the registry and bare `@akua` was taken; the scope name doesn't follow the reverse-DNS rule. diff --git a/crates/akua-napi/package.json b/crates/akua-napi/package.json index e6dcbba..b7b2cda 100644 --- a/crates/akua-napi/package.json +++ b/crates/akua-napi/package.json @@ -42,7 +42,7 @@ "scripts": { "build": "napi build --platform --release", "build:debug": "napi build --platform", - "prepublishOnly": "napi prepublish -t npm", + "prepublishOnly": "napi prepublish -t npm --no-gh-release", "artifacts": "napi artifacts", "version": "napi version" }, diff --git a/docs/releasing.md b/docs/releasing.md index 83fc7d0..c17db49 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -25,6 +25,13 @@ prerelease, and do not update the container's `latest` tag. Do not delete and re-push a release tag. If a tagged run fails, use the SHA-bound recovery path below after correcting and reviewing the workflow on `main`. +The `@akua-dev/native` meta-package must publish with lifecycle scripts enabled. Its +`prepublishOnly` hook runs `napi prepublish -t npm --no-gh-release`: `napi prepublish` +injects the generated per-platform `optionalDependencies` and copies the native +addons into the package, while `--no-gh-release` prevents the hook from uploading +assets to the immutable GitHub Release. Do not add `--ignore-scripts` to the native +publish; that flag is only valid for the separately staged SDK publish. + ## npm trusted publisher contract npm trusted-publisher configuration is external registry state. Source code can diff --git a/scripts/check-release-workflows.sh b/scripts/check-release-workflows.sh index ad7e9bc..8b8d290 100644 --- a/scripts/check-release-workflows.sh +++ b/scripts/check-release-workflows.sh @@ -15,6 +15,22 @@ line_in_job() { ' "$file" } +command_line_in_job() { + local file="$1" + local job="$2" + local pattern="$3" + + awk -v job="$job" -v pattern="$pattern" ' + $0 ~ "^ " job ":$" { in_job = 1; next } + in_job && $0 ~ /^ [A-Za-z0-9_-]+:$/ { exit } + in_job { + command = $0 + sub(/^[[:space:]]+/, "", command) + if (command !~ /^#/ && index(command, pattern)) { print NR; exit } + } + ' "$file" +} + assert_before() { local file="$1" local job="$2" @@ -46,13 +62,26 @@ assert_job_contains() { local pattern="$3" local line - line="$(line_in_job "$file" "$job" "$pattern")" + line="$(command_line_in_job "$file" "$job" "$pattern")" if [[ -z "$line" ]]; then echo "ERROR: $file job '$job' is missing '$pattern'" >&2 exit 1 fi } +assert_job_excludes() { + local file="$1" + local job="$2" + local pattern="$3" + local line + + line="$(command_line_in_job "$file" "$job" "$pattern")" + if [[ -n "$line" ]]; then + echo "ERROR: $file job '$job' still contains forbidden text '$pattern'" >&2 + exit 1 + fi +} + assert_file_contains() { local file="$1" local pattern="$2" @@ -273,6 +302,14 @@ assert_before ".github/workflows/release-publish.yml" \ "native-publish" \ "publish_one crates/akua-native-engines-npm" \ "publish_one crates/akua-napi" +if [[ "$(jq -r '.scripts.prepublishOnly' crates/akua-napi/package.json)" != \ + "napi prepublish -t npm --no-gh-release" ]]; then + echo "ERROR: native meta prepublish must preserve package injection while disabling GitHub Release uploads" >&2 + exit 1 +fi +assert_job_excludes ".github/workflows/release-publish.yml" \ + "native-publish" \ + "--ignore-scripts" assert_job_contains ".github/workflows/release-publish.yml" \ "sdk-publish" \ "needs: [detect-version, native-publish]"