mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(coding-agent): refresh unpinned temporary npm extensions when a newer version exists
Temporary -e npm: sources were installed once and reused forever. Like temporary git sources, check npm on load (unless offline) and reinstall with name@latest or the configured spec when a newer version exists, keeping the cached install if the refresh fails.
This commit is contained in:
parent
31ade0e0dc
commit
cd6a8f7f3c
2 changed files with 58 additions and 0 deletions
|
|
@ -1409,6 +1409,22 @@ export class DefaultPackageManager implements PackageManager {
|
|||
let installedPath = this.getNpmInstallPath(parsed, resolvedScope);
|
||||
const needsInstall =
|
||||
!existsSync(installedPath) || !(await this.installedNpmMatchesConfiguredVersion(parsed, installedPath));
|
||||
if (
|
||||
!needsInstall &&
|
||||
resolvedScope === "temporary" &&
|
||||
!parsed.pinned &&
|
||||
(await this.npmHasAvailableUpdate(parsed, installedPath))
|
||||
) {
|
||||
// Temporary (-e) sources have no `pi update` path, so refresh unpinned ones on load like
|
||||
// temporary git sources. Like `pi update`, install `name@latest` for unversioned specs because a bare
|
||||
// `npm install name` keeps the range already saved in the temporary package.json.
|
||||
const spec = parsed.version ? parsed.spec : `${parsed.name}@latest`;
|
||||
try {
|
||||
await this.installNpm({ ...parsed, spec }, resolvedScope, true);
|
||||
} catch {
|
||||
// Keep the cached install if the refresh fails.
|
||||
}
|
||||
}
|
||||
if (needsInstall) {
|
||||
const installed = await installMissing();
|
||||
if (!installed) continue;
|
||||
|
|
|
|||
|
|
@ -2940,6 +2940,48 @@ export default function(api) { api.registerTool({ name: "test", description: "te
|
|||
expect(result.extensions.some((r) => pathEndsWith(r.path, "extensions/old.ts"))).toBe(false);
|
||||
});
|
||||
|
||||
// https://github.com/earendil-works/pi/issues/11375
|
||||
it("should refresh unpinned temporary npm sources when a newer version exists", async () => {
|
||||
const managerWithInternals = packageManager as unknown as PackageManagerInternals;
|
||||
const source = "npm:example";
|
||||
const parsed = managerWithInternals.parseSource(source);
|
||||
if (parsed.type !== "npm") throw new Error("Expected npm source");
|
||||
const installedPath = managerWithInternals.getNpmInstallPath(parsed, "temporary");
|
||||
mkdirSync(join(installedPath, "extensions"), { recursive: true });
|
||||
writeFileSync(join(installedPath, "package.json"), JSON.stringify({ name: "example", version: "1.0.0" }));
|
||||
writeFileSync(join(installedPath, "extensions", "index.ts"), "export default function() {};");
|
||||
|
||||
vi.spyOn(packageManager as any, "runCommandCapture").mockResolvedValue('"1.1.0"');
|
||||
const runNpmCommandSpy = vi
|
||||
.spyOn(packageManager as any, "runNpmCommand")
|
||||
.mockRejectedValue(new Error("network down"));
|
||||
|
||||
const result = await packageManager.resolveExtensionSources([source], { temporary: true });
|
||||
expect(runNpmCommandSpy).toHaveBeenCalledTimes(1);
|
||||
expect(runNpmCommandSpy.mock.calls[0][0]).toContain("example@latest");
|
||||
// A failed refresh keeps loading the cached install.
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "extensions/index.ts") && r.enabled)).toBe(true);
|
||||
});
|
||||
|
||||
it("should not refresh temporary npm sources when already up to date or pinned", async () => {
|
||||
const managerWithInternals = packageManager as unknown as PackageManagerInternals;
|
||||
for (const source of ["npm:example", "npm:example@1.0.0"]) {
|
||||
const parsed = managerWithInternals.parseSource(source);
|
||||
if (parsed.type !== "npm") throw new Error("Expected npm source");
|
||||
const installedPath = managerWithInternals.getNpmInstallPath(parsed, "temporary");
|
||||
mkdirSync(installedPath, { recursive: true });
|
||||
writeFileSync(join(installedPath, "package.json"), JSON.stringify({ name: "example", version: "1.0.0" }));
|
||||
}
|
||||
|
||||
const runCommandCaptureSpy = vi.spyOn(packageManager as any, "runCommandCapture").mockResolvedValue('"1.0.0"');
|
||||
const runNpmCommandSpy = vi.spyOn(packageManager as any, "runNpmCommand").mockResolvedValue(undefined);
|
||||
|
||||
await packageManager.resolveExtensionSources(["npm:example"], { temporary: true });
|
||||
await packageManager.resolveExtensionSources(["npm:example@1.0.0"], { temporary: true });
|
||||
expect(runCommandCaptureSpy).toHaveBeenCalledTimes(1);
|
||||
expect(runNpmCommandSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not run npm view during resolve for installed unpinned packages", async () => {
|
||||
process.env.PI_OFFLINE = "1";
|
||||
const installedPath = join(tempDir, ".pi", "npm", "node_modules", "example");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue