mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-05 16:04:43 +00:00
Fix registry plugin installs through npm mirrors (#8756)
* fix(plugins): install registry npm pins through the configured registry Registry npm pins carry an npmjs.org tarball URL. Passing that URL to npm made it rewrite the host onto the configured registry and drop any path, so mirrors such as https://mirrors.tencent.com/npm/ returned 404. Install the pinned version instead and identify the reviewed artifact by version and integrity, since a mirror records its own tarball URL. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(plugins): address review on npm artifact comparison Take an object parameter in isSameNpmArtifact, and clean up the mirror fixture when test setup fails. Co-authored-by: Cursor <cursoragent@cursor.com> * test(plugins): verify mirror artifacts with a warm npm cache A shared npm cache lets the mirror regression reuse tarballs from earlier fixture installations, so successful installs made no tarball requests. Check the installed artifact's mirror URL, version and integrity instead of requiring network transfers. Co-authored-by: finofliu <finofliu@tencent.com> --------- Co-authored-by: finofliu <finofliu@tencent.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
finofliu
Cursor
parent
b920243c74
commit
92c8c55f31
@@ -575,3 +575,53 @@ it("installs and updates only the npm artifacts pinned by the plugin registry",
|
||||
else process.env.npm_config_userconfig = previous;
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
it("installs a registry npm pin through the configured npm mirror", async () => {
|
||||
const { startNpmRegistry, npmPluginPackages } =
|
||||
await import("../../../../../scripts/test-support/npm-registry.mjs");
|
||||
const { readNpmArtifact, resolveNpm } = await import("./managed-source/npm.js");
|
||||
const mirror = await startNpmRegistry(npmPluginPackages());
|
||||
const previous = process.env.npm_config_userconfig;
|
||||
process.env.npm_config_userconfig = mirror.userconfig;
|
||||
// The public registry pins npmjs.org tarball URLs, which a mirror does not serve at the same path.
|
||||
const pin = {
|
||||
version: "1.0.0",
|
||||
resolved: "https://registry.npmjs.org/paseo-fixture-plugin/-/paseo-fixture-plugin-1.0.0.tgz",
|
||||
integrity: "",
|
||||
};
|
||||
const server = createServer((_request, response) => {
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
id: "acme/example",
|
||||
artifact: { kind: "npm", package: "paseo-fixture-plugin", ...pin },
|
||||
}),
|
||||
);
|
||||
});
|
||||
try {
|
||||
const home = await mkdtemp(path.join(tmpdir(), "paseo-registry-npm-mirror-"));
|
||||
roots.push(home);
|
||||
pin.integrity = (await resolveNpm("paseo-fixture-plugin", "1.0.0", home)).integrity;
|
||||
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
const address = server.address();
|
||||
if (!address || typeof address === "string") throw new Error("Missing registry address");
|
||||
const sources = new ManagedPluginSources(home, {
|
||||
defaultUrl: `http://127.0.0.1:${address.port}`,
|
||||
});
|
||||
const candidate = await sources.place(
|
||||
"example",
|
||||
await sources.prepareInstall({ source: "acme/example" }),
|
||||
);
|
||||
await sources.verifyCandidate("example", candidate);
|
||||
sources.commit("example", candidate.record);
|
||||
expect((await sources.describe("example", candidate.directory)).currentRevision).toBe("1.0.0");
|
||||
expect(
|
||||
await readNpmArtifact(path.resolve(candidate.directory, "../.."), "paseo-fixture-plugin"),
|
||||
).toEqual({ ...pin, resolved: `${mirror.url}/artifacts/1.tgz` });
|
||||
} finally {
|
||||
server.closeAllConnections();
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
await mirror.close();
|
||||
if (previous === undefined) delete process.env.npm_config_userconfig;
|
||||
else process.env.npm_config_userconfig = previous;
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
@@ -22,7 +22,13 @@ import {
|
||||
import { runGitCommand } from "../../utils/run-git-command.js";
|
||||
import { ensurePrivateDirectory, writePrivateFileAtomicSync } from "../private-files.js";
|
||||
import { readPluginManifest, type PluginManifest } from "./manifest.js";
|
||||
import { acquireNpm, isNpmSource, readNpmArtifact, resolveNpm } from "./managed-source/npm.js";
|
||||
import {
|
||||
acquireNpm,
|
||||
isNpmSource,
|
||||
isSameNpmArtifact,
|
||||
readNpmArtifact,
|
||||
resolveNpm,
|
||||
} from "./managed-source/npm.js";
|
||||
|
||||
const GIT_TIMEOUT_MS = 120_000;
|
||||
const GIT_ENV = { GIT_TERMINAL_PROMPT: "0" } as const;
|
||||
@@ -346,11 +352,7 @@ export class ManagedPluginSources {
|
||||
throw new Error("Preparation changed the reviewed Git commit");
|
||||
if (target?.kind === "npm" && location.identity.kind === "npm") {
|
||||
const artifact = await readNpmArtifact(location.versionRoot, location.identity.packageName);
|
||||
if (
|
||||
artifact.version !== target.version ||
|
||||
artifact.integrity !== target.integrity ||
|
||||
artifact.resolved !== target.resolved
|
||||
)
|
||||
if (!isSameNpmArtifact({ installed: artifact, target }))
|
||||
throw new Error("Preparation changed the reviewed npm artifact");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function acquireNpm(source: string, installRoot: string, target?: N
|
||||
name: "paseo-plugin-installation",
|
||||
version: "1.0.0",
|
||||
private: true,
|
||||
dependencies: { [packageName]: target?.resolved ?? requestedSpec },
|
||||
dependencies: { [packageName]: requestedSpec },
|
||||
}),
|
||||
);
|
||||
const npm = await requireNpm();
|
||||
@@ -70,23 +70,28 @@ export async function acquireNpm(source: string, installRoot: string, target?: N
|
||||
{ cwd: installRoot, timeout: 120_000, maxBuffer: 1024 * 1024 },
|
||||
);
|
||||
const installed = await readNpmArtifact(installRoot, packageName);
|
||||
if (
|
||||
target &&
|
||||
(installed.version !== target.version ||
|
||||
installed.integrity !== target.integrity ||
|
||||
installed.resolved !== target.resolved)
|
||||
) {
|
||||
if (target && !isSameNpmArtifact({ installed, target })) {
|
||||
throw new Error(`npm artifact changed since review for ${packageName}`);
|
||||
}
|
||||
return { packageName, ...installed };
|
||||
}
|
||||
|
||||
// Mirrors serve the reviewed tarball from their own URL, so the content hash identifies it.
|
||||
export function isSameNpmArtifact({ installed, target }: NpmArtifactComparison): boolean {
|
||||
return installed.version === target.version && installed.integrity === target.integrity;
|
||||
}
|
||||
|
||||
export interface NpmArtifact {
|
||||
version: string;
|
||||
resolved: string;
|
||||
integrity: string;
|
||||
}
|
||||
|
||||
export interface NpmArtifactComparison {
|
||||
installed: NpmArtifact;
|
||||
target: NpmArtifact;
|
||||
}
|
||||
|
||||
export async function readNpmArtifact(
|
||||
installRoot: string,
|
||||
packageName: string,
|
||||
|
||||
Reference in New Issue
Block a user