-
Notifications
You must be signed in to change notification settings - Fork 134
fix(release): never let a prerelease version fall back to the latest dist-tag
#1234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /** | ||
| * Which npm dist-tag a publish lands on. | ||
| * | ||
| * Getting this wrong in the `latest` direction moves every existing user onto | ||
| * whatever was published, and the documented recovery | ||
| * (`npm dist-tag add …@<good> latest`) needs publish credentials most of the | ||
| * team does not hold — so the decision is pinned here rather than left to an | ||
| * inline expression whose only protection is an env var reaching one workflow | ||
| * step. See #1233. | ||
| */ | ||
|
|
||
| import { describe, test, expect } from "bun:test" | ||
| import { resolveChannel } from "../../../script/src/channel" | ||
|
|
||
| describe("resolveChannel: a prerelease must never reach `latest`", () => { | ||
| test("an explicit channel always wins", () => { | ||
| // This is the path the release workflow actually takes. | ||
| expect(resolveChannel({ OPENCODE_CHANNEL: "beta", OPENCODE_VERSION: "v0.10.0-beta.1" })).toBe("beta") | ||
| expect(resolveChannel({ OPENCODE_CHANNEL: "latest", OPENCODE_VERSION: "v0.10.0" })).toBe("latest") | ||
| }) | ||
|
|
||
| test("a prerelease tag falls back to `beta`, not `latest`", () => { | ||
| // The regression this guards. With OPENCODE_CHANNEL unset, the version is | ||
| // the tag name — and before the fix, `0.10.0-beta.1` failed the `0.0.0-` | ||
| // test and returned "latest", moving the stable dist-tag onto a beta. | ||
| for (const v of ["v0.10.0-beta.1", "0.10.0-beta.1", "v1.0.0-rc.2", "v0.9.6-beta.10"]) { | ||
| expect(resolveChannel({ OPENCODE_VERSION: v })).toBe("beta") | ||
| } | ||
| }) | ||
|
|
||
| test("a plain release version still resolves to `latest`", () => { | ||
| // The fix must not push ordinary stable releases off `latest`. | ||
| for (const v of ["v0.10.0", "0.10.0", "v1.2.3"]) { | ||
| expect(resolveChannel({ OPENCODE_VERSION: v })).toBe("latest") | ||
| } | ||
| }) | ||
|
|
||
| test("`0.0.0-` preview builds still defer to the branch channel", () => { | ||
| // These carry a `-` too, but they must keep falling through to the | ||
| // branch-name channel rather than being captured as `beta`. | ||
| expect(resolveChannel({ OPENCODE_VERSION: "0.0.0-release/v0.10.0-202609012234" })).toBeNull() | ||
| expect(resolveChannel({ OPENCODE_VERSION: "v0.0.0-somebranch-123" })).toBeNull() | ||
| }) | ||
|
|
||
| test("an explicit bump means a stable release", () => { | ||
| expect(resolveChannel({ OPENCODE_BUMP: "minor" })).toBe("latest") | ||
| }) | ||
|
|
||
| test("no signal at all defers to the caller", () => { | ||
| expect(resolveChannel({})).toBeNull() | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||
| // altimate_change start — extracted from the CHANNEL IIFE in ./index.ts so the | ||||||||||||||
| // dist-tag decision can be tested without importing that module, which checks | ||||||||||||||
| // the bun version, shells out to git and fetches the npm registry at import. | ||||||||||||||
| // | ||||||||||||||
| // This decides which npm dist-tag a publish lands on. Getting it wrong in the | ||||||||||||||
| // `latest` direction moves every existing user onto whatever was published, and | ||||||||||||||
| // recovery needs npm credentials most of the team does not hold — so it is | ||||||||||||||
| // worth having as a pure, tested function rather than an inline expression. | ||||||||||||||
| // (#1233) | ||||||||||||||
|
|
||||||||||||||
| export type ChannelEnv = { | ||||||||||||||
| OPENCODE_CHANNEL?: string | undefined | ||||||||||||||
| OPENCODE_BUMP?: string | undefined | ||||||||||||||
| OPENCODE_VERSION?: string | undefined | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** Resolve the npm dist-tag, or null when the caller should fall back to the | ||||||||||||||
| * current git branch name (the local/preview path). */ | ||||||||||||||
| export function resolveChannel(env: ChannelEnv): string | null { | ||||||||||||||
| if (env.OPENCODE_CHANNEL) return env.OPENCODE_CHANNEL | ||||||||||||||
| if (env.OPENCODE_BUMP) return "latest" | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: When Prompt for AI agents |
||||||||||||||
| if (env.OPENCODE_VERSION) { | ||||||||||||||
| const version = env.OPENCODE_VERSION.replace(/^v/, "") | ||||||||||||||
| // `0.0.0-` preview builds keep falling through to the branch-name channel. | ||||||||||||||
| if (version.startsWith("0.0.0-")) return null | ||||||||||||||
| // A semver prerelease belongs on `beta`, never `latest`. Before this, a | ||||||||||||||
| // `v0.10.0-beta.1` tag reaching this branch returned "latest" and would | ||||||||||||||
| // have auto-upgraded the entire stable user base onto a beta. | ||||||||||||||
| return version.includes("-") ? "beta" : "latest" | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Check only the SemVer prerelease component. Line 29 checks for As per the PR objectives, stable versions must resolve to Proposed fix- return version.includes("-") ? "beta" : "latest"
+ const versionWithoutBuild = version.split("+", 1)[0]
+ return versionWithoutBuild.includes("-") ? "beta" : "latest"📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Stable versions with hyphens in build metadata, such as Prompt for AI agents
Suggested change
|
||||||||||||||
| } | ||||||||||||||
| return null | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||
| import { $ } from "bun" | ||||||
| import semver from "semver" | ||||||
| import path from "path" | ||||||
| import { resolveChannel } from "./channel" | ||||||
|
|
||||||
| const rootPkgPath = path.resolve(import.meta.dir, "../../../package.json") | ||||||
| const rootPkg = await Bun.file(rootPkgPath).json() | ||||||
|
|
@@ -24,9 +25,9 @@ const env = { | |||||
| OPENCODE_RELEASE: process.env["OPENCODE_RELEASE"], | ||||||
| } | ||||||
| const CHANNEL = await (async () => { | ||||||
| if (env.OPENCODE_CHANNEL) return env.OPENCODE_CHANNEL | ||||||
| if (env.OPENCODE_BUMP) return "latest" | ||||||
| if (env.OPENCODE_VERSION && !env.OPENCODE_VERSION.replace(/^v/, "").startsWith("0.0.0-")) return "latest" | ||||||
| // altimate_change — see ./channel.ts for why this is a separate pure function. (#1233) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: This fork divergence is not wrapped in an
Suggested change
Also add a matching Reply with |
||||||
| const resolved = resolveChannel(env) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This refactor leaves the existing version-normalization test asserting the removed inline channel expression, so the package test fails. Update the test to exercise Prompt for AI agents |
||||||
| if (resolved) return resolved | ||||||
| return await $`git branch --show-current`.text().then((x) => x.trim()) | ||||||
| })() | ||||||
| const IS_PREVIEW = CHANNEL !== "latest" | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SUGGESTION:
altimate_change startis never closed — the matchingaltimate_change endis missing.Line 1 opens a marker block with no
end. Every other marker block in this codebase uses paired start/end markers, and the repo's own tooling flags unbalanced blocks (runRequireMarkersCheckinscript/upstream/analyze.tsreportsunbalanced markers (1 starts, 0 ends)). Add// altimate_change endat the end of the file to close the block.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.