8c37a8adb0
`getPackageDir()` walked up from `import.meta.dir` and fell back to `getProjectDir()` (the user's `cwd`) when no `package.json` was located. Inside `bun --compile` binaries `import.meta.dir` resolves to `/$bunfs/root`, so the walk hit the filesystem root and `omp` ended up reading the host project's `CHANGELOG.md` as its own — both in startup "What's New" display and `/changelog`. Worse, parsing succeeded on any `## [x.y.z]` heading, so `lastChangelogVersion` was persisted into `~/.omp/agent/config.yml` based on the host project's file. - Made `getPackageDir()` return `string | undefined`; removed the `cwd` fallback so package-asset lookup never bleeds into the host project. - Extracted the walk-up into a pure `walkUpForPackageDir(startDir)` so the resolution contract is unit-testable from arbitrary directories. - Made `getChangelogPath()` propagate `undefined` and taught `parseChangelog` to accept it, returning `[]`. Existing callers (`main.ts` startup, `/changelog` TUI handler, `/changelog` slash command) already gate on empty entries, so the compiled-binary path now skips changelog display cleanly without mutating settings. - Added `test/issue-1423-repro.test.ts` covering the resolver contract, the `parseChangelog(undefined)` path, the `PI_PACKAGE_DIR` override, and the negative assertion that a host `## [99.0.0]` heading never surfaces as an omp entry. Fixes #1423
107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
|
|
export interface ChangelogEntry {
|
|
major: number;
|
|
minor: number;
|
|
patch: number;
|
|
content: string;
|
|
}
|
|
|
|
/**
|
|
* Parse changelog entries from the file at `changelogPath`. Scans for `## [x.y.z]`
|
|
* headings and collects each block until the next heading or EOF.
|
|
*
|
|
* Returns `[]` when `changelogPath` is `undefined` (package directory not
|
|
* resolvable — see `getChangelogPath`) or the file is missing. Callers MUST NOT
|
|
* synthesize a fallback path from the host project's cwd; doing so caused issue
|
|
* #1423 (the host project's `CHANGELOG.md` was rendered as omp's).
|
|
*/
|
|
export async function parseChangelog(changelogPath: string | undefined): Promise<ChangelogEntry[]> {
|
|
if (!changelogPath) {
|
|
return [];
|
|
}
|
|
try {
|
|
const content = await Bun.file(changelogPath).text();
|
|
const lines = content.split("\n");
|
|
const entries: ChangelogEntry[] = [];
|
|
|
|
let currentLines: string[] = [];
|
|
let currentVersion: { major: number; minor: number; patch: number } | null = null;
|
|
|
|
for (const line of lines) {
|
|
// Check if this is a version header (## [x.y.z] ...)
|
|
if (line.startsWith("## ")) {
|
|
// Save previous entry if exists
|
|
if (currentVersion && currentLines.length > 0) {
|
|
entries.push({
|
|
...currentVersion,
|
|
content: currentLines.join("\n").trim(),
|
|
});
|
|
}
|
|
|
|
// Try to parse version from this line
|
|
const versionMatch = line.match(/##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/);
|
|
if (versionMatch) {
|
|
currentVersion = {
|
|
major: Number.parseInt(versionMatch[1], 10),
|
|
minor: Number.parseInt(versionMatch[2], 10),
|
|
patch: Number.parseInt(versionMatch[3], 10),
|
|
};
|
|
currentLines = [line];
|
|
} else {
|
|
// Reset if we can't parse version
|
|
currentVersion = null;
|
|
currentLines = [];
|
|
}
|
|
} else if (currentVersion) {
|
|
// Collect lines for current version
|
|
currentLines.push(line);
|
|
}
|
|
}
|
|
|
|
// Save last entry
|
|
if (currentVersion && currentLines.length > 0) {
|
|
entries.push({
|
|
...currentVersion,
|
|
content: currentLines.join("\n").trim(),
|
|
});
|
|
}
|
|
|
|
return entries;
|
|
} catch (error) {
|
|
if (isEnoent(error)) {
|
|
return [];
|
|
}
|
|
logger.error(`Warning: Could not parse changelog: ${error}`);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
|
|
*/
|
|
export function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {
|
|
if (v1.major !== v2.major) return v1.major - v2.major;
|
|
if (v1.minor !== v2.minor) return v1.minor - v2.minor;
|
|
return v1.patch - v2.patch;
|
|
}
|
|
|
|
/**
|
|
* Get entries newer than lastVersion
|
|
*/
|
|
export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {
|
|
// Parse lastVersion
|
|
const parts = lastVersion.split(".").map(Number);
|
|
const last: ChangelogEntry = {
|
|
major: parts[0] || 0,
|
|
minor: parts[1] || 0,
|
|
patch: parts[2] || 0,
|
|
content: "",
|
|
};
|
|
|
|
return entries.filter(entry => compareVersions(entry, last) > 0);
|
|
}
|
|
|
|
// Re-export getChangelogPath from paths.ts for convenience
|
|
export { getChangelogPath } from "../config";
|