diff --git a/.github/workflows/sdk-version-sync.yml b/.github/workflows/sdk-version-sync.yml index 32964d542..a01e45771 100644 --- a/.github/workflows/sdk-version-sync.yml +++ b/.github/workflows/sdk-version-sync.yml @@ -84,11 +84,13 @@ jobs: exit 1 fi - - name: Verify @nself/sdk and @nself/cli-api versions (sdk/ts-sdk, sdk/ts-cli-api) + - name: Verify every sdk npm package version (sdk/*/package.json) run: | TARGET='${{ steps.target.outputs.target }}' - for dir in sdk/ts-sdk sdk/ts-cli-api; do - V=$(node -p "require('./${dir}/package.json').version") + # Globbed, not listed: a package missing from a hand-kept list was skipped by the bumper. + for pkg in sdk/*/package.json; do + dir="${pkg%/package.json}" + V=$(node -p "require('./${pkg}').version") echo "${dir} declared: ${V}" if [ "${V}" != "${TARGET}" ]; then echo "MISMATCH: ${dir} is ${V}, expected ${TARGET}" diff --git a/.github/workflows/version-lockstep.yml b/.github/workflows/version-lockstep.yml index 6806280a7..25de1d87d 100644 --- a/.github/workflows/version-lockstep.yml +++ b/.github/workflows/version-lockstep.yml @@ -29,8 +29,7 @@ on: - '.github/VERSION' - 'internal/version/version.go' - 'sdk/go/doc.go' - - 'sdk/ts/package.json' - - 'sdk/ts-sdk/package.json' + - 'sdk/*/package.json' - 'sdk/py/pyproject.toml' - '.github/workflows/version-lockstep.yml' - 'scripts/check-version-lockstep.sh' @@ -136,20 +135,22 @@ jobs: | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | head -1) check "cli/sdk/go/doc.go" "${V}" 1 - # 4. cli/sdk/ts/package.json (@nself/plugin-sdk) - V=$(node -p "require('${NSELF_REPO_ROOT}/sdk/ts/package.json').version") - check "cli/sdk/ts/package.json" "${V}" 1 - - # 4b. cli/sdk/ts-sdk/package.json (@nself/sdk) + # 4. cli/sdk/*/package.json (@nself/plugin-sdk, @nself/sdk, @nself/cli-api, ...) # - # There are TWO TypeScript packages and only the first was listed - # here, so a release bumped one and left the other behind. Its publish - # workflow then saw its version already on npm, no-opped as - # "already published", and reported success — and the SDK Version - # Coherence check caught the drift a day later on its schedule, - # after the release had shipped. - V=$(node -p "require('${NSELF_REPO_ROOT}/sdk/ts-sdk/package.json').version") - check "cli/sdk/ts-sdk/package.json" "${V}" 1 + # Discovered by glob, never listed by hand: a hand-kept list let a release + # bump one npm package and leave another behind (sdk/ts-sdk once, then + # sdk/ts-cli-api at v1.4.15). bump-version.sh uses the same glob. + n=0 + for pkg in "${NSELF_REPO_ROOT}"/sdk/*/package.json; do + [ -f "${pkg}" ] || continue + n=$((n + 1)) + V=$(node -p "require('${pkg}').version") + check "cli/${pkg#${NSELF_REPO_ROOT}/}" "${V}" 1 + done + if [ "${n}" -eq 0 ]; then + printf '::error::no sdk/*/package.json found\n' + exit 1 + fi # 5. cli/sdk/py/pyproject.toml V=$(grep -E '^version\s*=' "${NSELF_REPO_ROOT}/sdk/py/pyproject.toml" \ diff --git a/cmd/commands/canon_engine_test.go b/cmd/commands/canon_engine_test.go index 4b8cc223c..b8577b1d5 100644 --- a/cmd/commands/canon_engine_test.go +++ b/cmd/commands/canon_engine_test.go @@ -776,7 +776,7 @@ func TestCanonEnginePrepare(t *testing.T) { t.Helper() compattest.Set(t, v15) savedArgs, savedMount := os.Args, mountInstalledPlugins - defer func() { os.Args, mountInstalledPlugins = savedArgs, savedMount }() + t.Cleanup(func() { os.Args, mountInstalledPlugins = savedArgs, savedMount }) mountInstalledPlugins = func(*cobra.Command) {} if mount { mountInstalledPlugins = func(r *cobra.Command) { diff --git a/internal/build/plugins_entitlement_mount_test.go b/internal/build/plugins_entitlement_mount_test.go index b1581d833..41a6ce63e 100644 --- a/internal/build/plugins_entitlement_mount_test.go +++ b/internal/build/plugins_entitlement_mount_test.go @@ -90,6 +90,12 @@ func TestMountIgnoresOverride(t *testing.T) { } func TestMountEmptyFileE125(t *testing.T) { + // Missing-token behavior must not renew a license configured on the test host. + t.Setenv("NSELF_PING_API_URL", "http://127.0.0.1:1") + t.Setenv("NSELF_PLUGIN_LICENSE_KEY", "synthetic-license-key") + for i := 1; i <= 10; i++ { + t.Setenv("NSELF_LICENSE_KEY_"+strconv.Itoa(i), "") + } compattest.Set(t, true) // No licence key from the host: a stored or env key would renew a real JWT online. t.Setenv("HOME", t.TempDir()) diff --git a/internal/license/entitlement/write_test.go b/internal/license/entitlement/write_test.go index be67a5f5e..292dfa9f4 100644 --- a/internal/license/entitlement/write_test.go +++ b/internal/license/entitlement/write_test.go @@ -102,6 +102,12 @@ func TestEntitlementRefreshWrites(t *testing.T) { } func TestBuildE125UnderV15(t *testing.T) { + // Missing-token behavior must not renew a license configured on the test host. + t.Setenv("NSELF_PING_API_URL", "http://127.0.0.1:1") + t.Setenv("NSELF_PLUGIN_LICENSE_KEY", "synthetic-license-key") + for i := 1; i <= 10; i++ { + t.Setenv("NSELF_LICENSE_KEY_"+strconv.Itoa(i), "") + } compattest.Both(t, func(t *testing.T) { // No licence key from the host: a stored or env key would renew a real JWT online. t.Setenv("HOME", t.TempDir()) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 3d5de79f8..b76b30810 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -24,8 +24,8 @@ # 1. cli/.github/VERSION # 2. cli/internal/version/version.go Version string = "x.y.z" (type-annotated) # 3. cli/sdk/go/doc.go const Version = "x.y.z" -# 4. cli/sdk/ts/package.json "version": "x.y.z" (via jq) -# 5. cli/sdk/ts-sdk/package.json "version": "x.y.z" (via jq) +# 4. cli/sdk/*/package.json "version": "x.y.z" (via jq; every TS package +# found under sdk/: ts, ts-sdk, ts-cli-api, ...) # 6. cli/sdk/py/pyproject.toml version = "x.y.z" under [project] # 7. admin/package.json "version": "x.y.z" (via jq) # 8. admin/src/lib/cli-version.ts CLI_VERSION = 'x.y.z' @@ -102,7 +102,7 @@ fi # ── Tool availability checks ────────────────────────────────────────────────── if ! command -v jq >/dev/null 2>&1; then - printf 'ERROR: jq is required for JSON file bumps (sdk/ts/package.json, sdk/ts-sdk/package.json, admin/package.json)\n' >&2 + printf 'ERROR: jq is required for JSON file bumps (sdk/*/package.json, admin/package.json)\n' >&2 printf ' Install: brew install jq (macOS) | apt-get install jq (Linux)\n' >&2 exit 1 fi @@ -114,9 +114,7 @@ VERSION_FILE="${REPO_ROOT}/.github/VERSION" OLD_VERSION="$(tr -d '[:space:]' < "${VERSION_FILE}" 2>/dev/null || true)" VERSION_GO="${REPO_ROOT}/internal/version/version.go" SDK_GO="${REPO_ROOT}/sdk/go/doc.go" -SDK_TS="${REPO_ROOT}/sdk/ts/package.json" SDK_PY="${REPO_ROOT}/sdk/py/pyproject.toml" -SDK_TS_SDK="${REPO_ROOT}/sdk/ts-sdk/package.json" # SDK_FLUTTER removed 2026-06-30 (#159, ASI Policy 2) ADMIN_PKG="${NSELF_ROOT}/admin/package.json" ADMIN_CLIVER="${NSELF_ROOT}/admin/src/lib/cli-version.ts" @@ -248,17 +246,24 @@ update_sdk_go_doc() { report "${f}" "${old}" "${NEW_VERSION}" } -update_sdk_ts_pkg() { - update_json_version "${SDK_TS}" -} - -# sdk/ts-sdk/package.json — the @nself/sdk consumer client (NselfClient), -# distinct from sdk/ts (@nself/plugin-sdk). Publishes in lockstep with the -# CLI per sdk-ts-sdk-publish.yml ("Triggers on: cli release tag (v*)"). -# Added 2026-07-06 after #179 (missing from this bumper caused @nself/sdk -# to lag at 1.1.9 while VERSION moved to 1.2.4 — SDK Version Coherence fail). -update_sdk_ts_sdk_pkg() { - update_json_version "${SDK_TS_SDK}" +# Every sdk/*/package.json is one npm package that ships in lockstep with the CLI +# (sdk/ts = @nself/plugin-sdk, sdk/ts-sdk = @nself/sdk, sdk/ts-cli-api = @nself/cli-api). +# The packages are discovered, never listed by hand: a hand-kept list lost sdk/ts-sdk +# once (#179, @nself/sdk lagged at 1.1.9) and sdk/ts-cli-api again at v1.4.15, where +# SDK Version Sync went red on main. A new sdk//package.json is bumped from the +# moment it exists. The same glob drives the lockstep checks in version-lockstep.yml +# and check-version-lockstep.sh. +update_sdk_ts_pkgs() { + local f n=0 + for f in "${REPO_ROOT}"/sdk/*/package.json; do + [ -f "${f}" ] || continue + update_json_version "${f}" + n=$((n + 1)) + done + if [ "${n}" -eq 0 ]; then + printf 'ERROR: no sdk/*/package.json found under %s\n' "${REPO_ROOT}" >&2 + exit 1 + fi } # pyproject.toml shape: `version = "2.0.0"` under [project] @@ -558,13 +563,12 @@ if [ "${HOMEBREW}" = "true" ]; then exit 0 fi -printf 'Phase 1 — updating 11 lockstep files + 4 version-hash goldens (Homebrew deferred to phase 2)\n\n' +printf 'Phase 1 — updating the lockstep files (every sdk/*/package.json included) + 4 version-hash goldens (Homebrew deferred to phase 2)\n\n' update_version_file update_version_go update_sdk_go_doc -update_sdk_ts_pkg -update_sdk_ts_sdk_pkg +update_sdk_ts_pkgs update_sdk_py_pyproject update_admin_pkg update_admin_cliver diff --git a/scripts/check-version-lockstep.sh b/scripts/check-version-lockstep.sh index 6eff76b1a..3a193d72f 100755 --- a/scripts/check-version-lockstep.sh +++ b/scripts/check-version-lockstep.sh @@ -10,7 +10,7 @@ # 1. cli/internal/version/version.go (Go constant) # 2. cli/.github/VERSION (plain text) # 3. cli/sdk/go/doc.go (Go const) -# 4. cli/sdk/ts/package.json (JSON version field) +# 4. cli/sdk/*/package.json (JSON version field; every sdk npm package) # 5. cli/sdk/py/pyproject.toml (TOML version field) # 6. admin/package.json (JSON version field) # 7. admin/src/lib/cli-version.ts (TS constant) @@ -170,18 +170,22 @@ else log_warn "cli/sdk/go/doc.go" "(missing)" fi -# 4. cli/sdk/ts/package.json (warn-only) -FILE4="${CLI_ROOT}/sdk/ts/package.json" -if [ -f "${FILE4}" ]; then +# 4. cli/sdk/*/package.json (warn-only): every npm package under sdk/ (ts, ts-sdk, +# ts-cli-api, ...) is discovered by glob, the same way bump-version.sh bumps them. +SDK_PKG_N=0 +for FILE4 in "${CLI_ROOT}"/sdk/*/package.json; do + [ -f "${FILE4}" ] || continue + SDK_PKG_N=$((SDK_PKG_N + 1)) if command -v python3 >/dev/null 2>&1; then - V4=$(python3 -c "import json,sys; d=json.load(open('${FILE4}')); print(d['version'])" 2>/dev/null || true) + V4=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d['version'])" "${FILE4}" 2>/dev/null || true) else V4=$(grep -E '"version"\s*:' "${FILE4}" \ | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | head -1) fi - check_version "cli/sdk/ts/package.json" "${V4}" 0 -else - log_warn "cli/sdk/ts/package.json" "(missing)" + check_version "cli/${FILE4#"${CLI_ROOT}/"}" "${V4}" 0 +done +if [ "${SDK_PKG_N}" -eq 0 ]; then + log_warn "cli/sdk/*/package.json" "(none found)" fi # 5. cli/sdk/py/pyproject.toml (warn-only) diff --git a/scripts/release/fixtures/cases4.sh b/scripts/release/fixtures/cases4.sh index 27b57e66b..1bae19e5a 100644 --- a/scripts/release/fixtures/cases4.sh +++ b/scripts/release/fixtures/cases4.sh @@ -15,13 +15,17 @@ mk_bump_env() { B=$(mktemp -d "$BASE/bump.XXXXXX"); C=$B/nself/cli; A=$B/nself/admin # mk_env's TMPDIR was deleted with its env; bump-version.sh's `mktemp -d` needs a live one (GNU mktemp fails without). mkdir -p "$BASE/tmp"; export TMPDIR=$BASE/tmp - mkdir -p "$C/scripts" "$C/internal/version" "$C/sdk/go" "$C/sdk/ts" "$C/sdk/ts-sdk" "$C/sdk/py" "$C/.github" \ + mkdir -p "$C/scripts" "$C/internal/version" "$C/sdk/go" "$C/sdk/py" "$C/.github" \ "$C/internal/build" "$C/cmd/commands" "$A/src/lib" "$B/bin" cp "$ROOT/scripts/bump-version.sh" "$C/scripts/" echo 1.4.13 > "$C/.github/VERSION" printf 'package version\n\nvar (\n\tVersion string = "1.4.13"\n)\n' > "$C/internal/version/version.go" printf 'package sdk\n\nconst Version = "1.4.13"\n' > "$C/sdk/go/doc.go" - for f in sdk/ts sdk/ts-sdk; do echo '{"version":"1.4.13"}' > "$C/$f/package.json"; done + # One fixture package per sdk/*/package.json in the real tree, so a package added there + # is part of every bump test without touching this file. + for f in "$ROOT"/sdk/*/package.json; do + d=$(basename "$(dirname "$f")"); mkdir -p "$C/sdk/$d"; echo '{"version":"1.4.13"}' > "$C/sdk/$d/package.json" + done printf '[project]\nversion = "1.4.13"\n' > "$C/sdk/py/pyproject.toml" echo '{"version":"1.4.13"}' > "$A/package.json" echo "export const CLI_VERSION = '1.4.13'" > "$A/src/lib/cli-version.ts" @@ -65,6 +69,17 @@ check "bump kept the other golden lines" gold_all 'other keep' check "bump ran the build golden with UPDATE_GOLDEN=1" grep -q '^UPDATE_GOLDEN=1 test .*./internal/build ' "$GO_LOG" check "bump ran the cmd golden with UPDATE_GOLDEN=1" grep -q '^UPDATE_GOLDEN=1 test .*./cmd/commands ' "$GO_LOG" check "bump re-ran both tests plain" test "$(grep -c '^UPDATE_GOLDEN= test' "$GO_LOG")" -eq 2 +sdk_pkgs_at() { local f; for f in "$C"/sdk/*/package.json; do grep -q "\"$1\"" "$f" || return 1; done; } +check "bump found the real sdk npm packages (ts-sdk, ts-cli-api among them)" \ + test -f "$C/sdk/ts-sdk/package.json" -a -f "$C/sdk/ts-cli-api/package.json" +check "bump moved every sdk/*/package.json, none skipped" sdk_pkgs_at 9.9.9 +rm -rf "$B" + +# 13b. a package the bumper skips is caught: sdk_pkgs_at fails when one stays behind ------- +mk_bump_env +run_bump 9.9.9 +echo '{"version":"1.4.14"}' > "$C/sdk/ts-cli-api/package.json" +check "a lagging sdk package fails the lockstep assertion" test "$(sdk_pkgs_at 9.9.9 && echo ok || echo lag)" = lag rm -rf "$B" # 14. other drift is refused and the goldens come back -------------------------------------