mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(coding-agent): ignore prerelease suffix of last seen changelog version (#11753)
Fixes #11703. **Problem:** `getNewEntries()` parsed the saved version with `split(".")`. `0.1.5-20261020.7` became `["0", "1", "5-20261020", "7"]`, `Number("5-20261020")` is `NaN`, and `|| 0` turned it into `0.1.0`. The `[0.1.1]` to `[0.1.5]` notes counted as new, were shown, and the same prerelease string was saved again, so they repeated on every fresh launch. **Fix:** read only the leading `major.minor.patch` of the saved version, the same way changelog headings are parsed. `0.1.5-20261020.7` and `0.1.5+build.3` now count as `0.1.5`. If the saved version has no `major.minor.patch` (for example `latest`), nothing is shown and the installed version is saved in its place, like on a fresh install. Before, it was read as `0.0.0` and the whole changelog was shown. Saving the installed version matters because callers only save when there are new entries, so an unreadable value would otherwise silence the changelog for good. Alternatives considered (noted in a code comment): - plain string comparison: orders `0.10.0` before `0.9.0` - `localeCompare` with `numeric: true`: works only because `0.1.5-x` happens to sort after `0.1.5`, and depends on locale collation - strict semver: orders `0.1.5-x` before `0.1.5`, so the `[0.1.5]` notes would still repeat forever Tests added in `test/changelog.test.ts` and `test/interactive-mode-extension-changelog.test.ts`. `npm run check` passes.
This commit is contained in:
parent
4d70507ffb
commit
9a19539617
5 changed files with 73 additions and 8 deletions
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
- A failed token refresh of an MCP connection no longer registers an OAuth client or replaces the PKCE verifier of a sign-in in progress, which made that sign-in fail. MCP sign-in now refreshes the stored tokens with the stored client even when the loopback callback port changed, instead of registering a new client and opening the browser. Ctrl+C in `/mcp` and the `--timeout` of `pi mcp login` now also stop a sign-in while it waits up to 25 seconds for another pi process's token refresh, and a refresh lock that fails for another reason than that wait now fails the sign-in instead of opening the browser.
|
||||
- Fixed RPC mode ignoring an extension's `ctx.shutdown()` while idle, for example from a timer: the process kept running until the client sent another command. pi now shuts down right away when idle, and a shutdown requested by an extension command waits until the command's `prompt` response is written ([#11599](https://github.com/earendil-works/pi/issues/11599))
|
||||
- Fixed changelog notes repeating on every fresh launch when the installed package version has a prerelease suffix, such as `0.1.5-20261020.7`: the saved version was read as `0.1.0`, so the `0.1.1` to `0.1.5` notes counted as new each time. A saved version without `major.minor.patch` now shows no notes and is replaced by the installed version, instead of showing the whole changelog ([#11703](https://github.com/earendil-works/pi/issues/11703))
|
||||
|
||||
|
||||
## [1.5.0] - 2026-11-02
|
||||
|
|
|
|||
|
|
@ -1372,6 +1372,11 @@ export class InteractiveMode {
|
|||
}
|
||||
|
||||
const newEntries = getNewEntries(entries, lastVersion);
|
||||
if (!newEntries) {
|
||||
// Unreadable saved version: show nothing and record the current one so later updates compare again
|
||||
this.settingsManager.setLastChangelogVersion(VERSION);
|
||||
return undefined;
|
||||
}
|
||||
if (newEntries.length > 0) {
|
||||
this.settingsManager.setLastChangelogVersion(VERSION);
|
||||
this.reportInstallTelemetry(VERSION);
|
||||
|
|
@ -1413,6 +1418,11 @@ export class InteractiveMode {
|
|||
}
|
||||
|
||||
const newEntries = getNewEntries(parseChangelog(sourceInfo.changelogPath), lastVersion);
|
||||
if (!newEntries) {
|
||||
// Unreadable saved version: show nothing and record the current one so later updates compare again
|
||||
this.settingsManager.setExtensionChangelogVersion(key, pkg.version);
|
||||
continue;
|
||||
}
|
||||
if (newEntries.length > 0) {
|
||||
this.settingsManager.setExtensionChangelogVersion(key, pkg.version);
|
||||
const name = pkg.name || sourceInfo.source;
|
||||
|
|
|
|||
|
|
@ -177,15 +177,20 @@ export function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number
|
|||
}
|
||||
|
||||
/**
|
||||
* Get entries newer than lastVersion
|
||||
* Get entries newer than lastVersion, or undefined when lastVersion has no major.minor.patch to compare against
|
||||
*/
|
||||
export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {
|
||||
// Parse lastVersion
|
||||
const parts = lastVersion.split(".").map(Number);
|
||||
export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] | undefined {
|
||||
// Read only the leading major.minor.patch, like the changelog headings, so a prerelease such as
|
||||
// 0.1.5-20261020.7 counts as 0.1.5 and its notes are not shown again on every launch (#11703).
|
||||
// Alternatives considered: plain string comparison orders 0.10.0 before 0.9.0; numeric localeCompare
|
||||
// works by accident and is locale-dependent; strict semver orders 0.1.5-x before 0.1.5, which repeats
|
||||
// the [0.1.5] notes forever.
|
||||
const parts = lastVersion.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
||||
if (!parts) return undefined;
|
||||
const last: ChangelogEntry = {
|
||||
major: parts[0] || 0,
|
||||
minor: parts[1] || 0,
|
||||
patch: parts[2] || 0,
|
||||
major: Number(parts[1]),
|
||||
minor: Number(parts[2]),
|
||||
patch: Number(parts[3]),
|
||||
content: "",
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { type ChangelogEntry, normalizeChangelogLinks } from "../src/utils/changelog.ts";
|
||||
import { type ChangelogEntry, getNewEntries, normalizeChangelogLinks } from "../src/utils/changelog.ts";
|
||||
|
||||
const entry: ChangelogEntry = {
|
||||
major: 0,
|
||||
|
|
@ -47,3 +47,30 @@ describe("normalizeChangelogLinks", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getNewEntries", () => {
|
||||
const entries: ChangelogEntry[] = [
|
||||
{ major: 0, minor: 1, patch: 5, content: "0.1.5" },
|
||||
{ major: 0, minor: 1, patch: 4, content: "0.1.4" },
|
||||
];
|
||||
const newContent = (lastVersion: string) => getNewEntries(entries, lastVersion)?.map((e) => e.content);
|
||||
|
||||
// #11703
|
||||
test("ignores prerelease and build suffixes of the last seen version", () => {
|
||||
expect(newContent("0.1.5-20261020.7")).toEqual([]);
|
||||
expect(newContent("0.1.5+build.3")).toEqual([]);
|
||||
expect(newContent("0.1.4-beta.1")).toEqual(["0.1.5"]);
|
||||
});
|
||||
|
||||
test("compares release versions numerically", () => {
|
||||
expect(newContent("0.1.5")).toEqual([]);
|
||||
expect(newContent("0.1.3")).toEqual(["0.1.5", "0.1.4"]);
|
||||
expect(getNewEntries([{ major: 0, minor: 10, patch: 0, content: "0.10.0" }], "0.9.0")?.length).toBe(1);
|
||||
});
|
||||
|
||||
test("returns undefined for a last seen version without major.minor.patch", () => {
|
||||
expect(newContent("latest")).toBeUndefined();
|
||||
expect(newContent("0.1")).toBeUndefined();
|
||||
expect(newContent("")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -117,6 +117,28 @@ describe("InteractiveMode extension changelog", () => {
|
|||
expect(display()).toEqual([]);
|
||||
});
|
||||
|
||||
// #11703
|
||||
test("does not repeat entries for a prerelease version", () => {
|
||||
writePackage("1.0.0");
|
||||
display();
|
||||
writePackage("1.2.0-20261020.7");
|
||||
|
||||
expect(display()).toHaveLength(1);
|
||||
expect(display()).toEqual([]);
|
||||
});
|
||||
|
||||
test("shows nothing for an unreadable last seen version and records the current one", () => {
|
||||
const key = getSourcePackageKey(sourceInfo);
|
||||
settingsManager.setExtensionChangelogVersion(key, "latest");
|
||||
writePackage("1.0.0");
|
||||
|
||||
expect(display()).toEqual([]);
|
||||
expect(settingsManager.getExtensionChangelogVersion(key)).toBe("1.0.0");
|
||||
|
||||
writePackage("1.2.0");
|
||||
expect(display()).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("skips resumed sessions", () => {
|
||||
writePackage("1.0.0");
|
||||
display();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue