Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const originalEnv = Object.fromEntries(
);
const token = 'synthetic-homebrew-token';
const pin = '10.34.7';
// short_fingerprint(PINNED_PNPM_INTEGRITY) in crates/vp_setup/src/install.rs (the
// first 32 hex of its SHA-256). The bundled pnpm is pinned, so setup verifies its
// download against that integrity; a fake tarball cannot match it, so tests seed
// the stub at the pinned install's content key instead.
const SETUP_PNPM_KEY = '2f9ea5a98bd92b2d1d6694aedb029783';
const lockfile = "lockfileVersion: '9.0'\n";
fs.writeFileSync(
path.join(root, 'package.json'),
Expand Down Expand Up @@ -191,7 +196,47 @@ function keg(version) {
return prefix;
}

function environment(name, auth = true, seedRuntime = true, seedInstallRuntime = seedRuntime) {
// Write a runnable stub pnpm install under `dataDir`, at both the pinned install's
// content key and the version-directory root (for the unpinned `pn`/`pnx` shim),
// so neither the setup bootstrap nor the shim downloads it.
function seedStubPnpm(dataDir) {
for (const key of [SETUP_PNPM_KEY, null]) {
// A pinned install lives under the hidden verified namespace; the unpinned
// install stays at the version-directory root.
const install = key
? path.join(dataDir, 'package_manager/pnpm/.verified-v1', pin, key, 'pnpm')
: path.join(dataDir, 'package_manager/pnpm', pin, 'pnpm');
const bin = path.join(install, 'bin');
fs.mkdirSync(bin, { recursive: true });
fs.writeFileSync(
path.join(install, 'package.json'),
JSON.stringify({
name: 'pnpm',
version: pin,
engines: { node: '>=18.12' },
bin: { pnpm: 'bin/pnpm.cjs', pnpx: 'bin/pnpx.cjs' },
}),
);
for (const tool of ['pnpm', 'pnpx']) {
fs.writeFileSync(path.join(bin, `${tool}.cjs`), pnpmCode);
fs.writeFileSync(path.join(bin, tool), `#!/bin/sh\nexec node "$(dirname "$0")/${tool}.cjs" "$@"\n`, {
mode: 0o755,
});
}
}
}

function environment(
name,
auth = true,
seedRuntime = true,
seedInstallRuntime = seedRuntime,
// Default to `auth`: an unauthenticated scenario's failure is at the
// auth-protected pnpm download, so it must download (and 401) rather than reuse
// a seed. Authenticated scenarios seed so setup reuses pnpm instead of failing
// the integrity pin on a fake download.
seedPnpm = auth,
) {
const home = path.join(root, name);
const vpHome = path.join(home, '.vite-plus');
fs.mkdirSync(home, { recursive: true });
Expand All @@ -212,6 +257,7 @@ function environment(name, auth = true, seedRuntime = true, seedInstallRuntime =
// Existing installations already have their tool and dependency-install runtimes.
fs.writeFileSync(path.join(vpHome, '.vp-owned'), 'vite-plus-owned-directory\n');
}
if (seedPnpm) seedStubPnpm(vpHome);
fs.writeFileSync(
path.join(home, '.npmrc'),
`registry=${url}/registry/\n${auth ? `//127.0.0.1:${server.address().port}/registry/:_authToken=\${NPM_TOKEN}\n` : ''}`,
Expand Down Expand Up @@ -273,7 +319,7 @@ try {
rejectInstall = false;
const retry = await Promise.all([run(publicVp, ['--help'], env), run(publicVp, ['--help'], env)]);
assert.equal(installs, 2, 'retry must install once across concurrent launches');
assert.equal(downloads, 1, 'the retry must reuse bootstrapped pnpm');
assert.equal(downloads, 0, 'setup and retry must reuse the provisioned pnpm, never downloading it');
assert.equal(indexRequests, 0, 'dependency retries must also use the release pin');
assert.ok(retry.every((result) => !result.stderr.includes('manage Node')));
assert.equal(fs.readFileSync(settingsFile, 'utf8'), preferences);
Expand Down Expand Up @@ -584,7 +630,9 @@ try {
}

for (const split of [false, true]) {
const fresh = environment(`fresh-${split ? 'split' : 'single'}`, true, false);
// seedPnpm = false: the fresh-root assertions below require an empty start, so
// the stub pnpm is seeded inside the loop, after that check, instead.
const fresh = environment(`fresh-${split ? 'split' : 'single'}`, true, false, false, false);
const data = split ? path.join(fresh.HOME, 'data') : fresh.VP_HOME;
const cache = split ? path.join(fresh.HOME, 'cache') : path.join(data, 'cache');
const bin = split ? path.join(fresh.HOME, 'bin') : path.join(data, 'bin');
Expand All @@ -607,6 +655,11 @@ try {
const npmrc = fs.readFileSync(path.join(fresh.HOME, '.npmrc'), 'utf8');
for (let attempt = 0; attempt < 2; attempt++) {
for (const directory of roots) assert.ok(!fs.existsSync(directory), directory);
// Seed the stub pnpm now that the empty-start check has passed, so fresh
// setup reuses it instead of downloading a fake tarball that fails the pin.
// Mark the data root vp-owned so setup accepts the pre-seeded directory.
seedStubPnpm(data);
fs.writeFileSync(path.join(data, '.vp-owned'), 'vite-plus-owned-directory\n');
const before = {
installs,
indexRequests,
Expand Down Expand Up @@ -642,7 +695,11 @@ try {
['direct', `${url}/registry/`],
['interpolated', '${TEST_REGISTRY}/registry/'],
]) {
const override = environment(`${name}-registry-override`);
// seedPnpm = false: the override must carry the pnpm download to the
// configured registry, so assert the protected request happens instead of
// letting a seeded cache hide a broken override. The fixture's stub tarball
// then fails the integrity pin, so setup stops with a nonzero status.
const override = environment(`${name}-registry-override`, true, true, true, false);
const config = path.join(override.HOME, '.npmrc');
fs.writeFileSync(
config,
Expand All @@ -651,7 +708,12 @@ try {
.replace(`${url}/registry/`, 'http://127.0.0.1:1/unavailable/'),
);
override.NPM_CONFIG_REGISTRY = registry;
await run(publicVp, ['--help'], override);
const before = downloads;
await run(publicVp, ['--help'], override, root, 1);
assert.ok(
downloads > before,
`${name} registry override must carry the pnpm download to the configured registry`,
);
}

for (const [name, selector] of [
Expand All @@ -670,16 +732,22 @@ try {
'Relative, tilde, and environment-variable selectors reach pnpm as the same absolute user config.',
);

const basic = environment('basic-auth');
// seedPnpm = false: the Basic-auth credential must carry the pnpm download, so
// assert the protected request happens instead of letting a seeded cache pass
// even when the credential is wrong. The fixture's stub tarball then fails the
// integrity pin, so setup stops with a nonzero status.
const basic = environment('basic-auth', true, true, true, false);
const encoded = Buffer.from('fixture:synthetic-password').toString('base64');
fs.writeFileSync(
path.join(basic.HOME, '.npmrc'),
`registry=${url}/registry/\n//127.0.0.1:${server.address().port}/registry/:_auth=${encoded}\n`,
);
authorization = `Basic ${encoded}`;
await run(publicVp, ['--help'], basic);
const beforeBasic = downloads;
await run(publicVp, ['--help'], basic, root, 1);
assert.ok(downloads > beforeBasic, 'basic authentication must carry the protected pnpm download');
authorization = `Bearer ${token}`;
console.log('Registry overrides and basic authentication work with the shared downloader.');
console.log('Registry overrides and basic authentication reach the shared downloader.');

const unauthenticated = environment('unauthenticated', false);
await run(publicVp, ['--help'], unauthenticated, root, 1);
Expand Down Expand Up @@ -729,7 +797,9 @@ try {
assert.ok(registryRequests > requestsBeforeAlias, 'shim arguments must not bypass setup');

corrupt = true;
await run(publicVp, ['--help'], environment('corrupt'), root, 1);
// seedPnpm = false: this scenario needs a real (corrupt) pnpm download to verify
// that a tarball failing verification stops setup.
await run(publicVp, ['--help'], environment('corrupt', true, true, true, false), root, 1);
console.log('Missing authentication and corrupted pnpm tarballs stop setup.');
unlock(replacement);
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ steps = [["node", "verify-refresh.mjs", "homebrew-sources"]]
name = "command_self_setup_homebrew_user_dependencies"
vp = "global"
skip-platforms = ["windows"]
steps = [{ argv = ["node", "homebrew.mjs"], timeout = 120000 }]
# The heaviest external case: it drives ~15 full self-setup scenarios in one
# `node homebrew.mjs` run, so it needs a wider timeout than the 120s default,
# especially under parallel load.
steps = [{ argv = ["node", "homebrew.mjs"], timeout = 300000 }]

[[case]]
name = "command_self_setup_homebrew_collision"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Cached Node, package managers, and global tools run without CLI dependency repai
CLI aliases, explicit installers, and unconfigured shims still prepare CLI dependencies.
User installations and cleanup stay independent.
Relative, tilde, and environment-variable selectors reach pnpm as the same absolute user config.
Registry overrides and basic authentication work with the shared downloader.
Registry overrides and basic authentication reach the shared downloader.
After authentication failure, diagnostics, upgrade guidance, and cleanup remain available.
Missing authentication and corrupted pnpm tarballs stop setup.
```
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ let runtimeDownloads = 0;
let checksumRequests = 0;
let indexRequests = 0;

const pnpmArchive = createArchive({
'package/package.json': JSON.stringify({
name: 'pnpm',
version: '10.34.7',
bin: { pnpm: 'bin/pnpm.cjs' },
}),
'package/bin/pnpm.cjs': `
const pnpmManifest = JSON.stringify({
name: 'pnpm',
version: '10.34.7',
bin: { pnpm: 'bin/pnpm.cjs' },
});
const pnpmCjs = `
const fs = require('node:fs');
const assert = require('node:assert/strict');
assert.ok(process.argv.includes('--frozen-lockfile'));
Expand All @@ -61,9 +60,26 @@ const pnpmArchive = createArchive({
fs.writeFileSync('node_modules/vite-plus/package.json', '{"name":"vite-plus"}');
if (!response.ok) process.exit(17);
});
`,
`;
const pnpmArchive = createArchive({
'package/package.json': pnpmManifest,
'package/bin/pnpm.cjs': pnpmCjs,
});

// The bundled pnpm is pinned, so setup verifies its download against the bundled
// integrity. A fake tarball cannot match that pin, so seed the stub at the pinned
// install's content key instead; the bootstrap reuses it and skips the download.
// Key = short_fingerprint(PINNED_PNPM_INTEGRITY) in crates/vp_setup/src/install.rs
// (the first 32 hex of its SHA-256).
const SETUP_PNPM_KEY = '2f9ea5a98bd92b2d1d6694aedb029783';
const seededPnpm = join(home, 'package_manager/pnpm/.verified-v1/10.34.7', SETUP_PNPM_KEY, 'pnpm');
mkdirSync(join(seededPnpm, 'bin'), { recursive: true });
writeFileSync(join(seededPnpm, 'package.json'), pnpmManifest);
writeFileSync(join(seededPnpm, 'bin/pnpm.cjs'), pnpmCjs);
for (const shim of ['pnpm', 'pnpm.cmd', 'pnpm.ps1']) {
writeFileSync(join(seededPnpm, 'bin', shim), 'unused');
}

// The request is made after the CLI draws each phase. Hold its response until
// the runner captures the milestone, without sleeps or polling terminal text.
const phases = [];
Expand Down Expand Up @@ -182,7 +198,7 @@ try {
assert.equal(existsSync(marker), false);
assert.equal(existsSync(receipt), resolutionRetry);
assert.equal(existsSync(resolutionFile), false);
assert.deepEqual(phases, ['download', 'prepare', 'dependencies']);
assert.deepEqual(phases, ['prepare', 'dependencies']);
console.log(
resolutionRetry
? 'A failed resolution-file write leaves dependencies installed and setup incomplete.'
Expand Down Expand Up @@ -220,8 +236,8 @@ try {
assert.deepEqual(
phases,
dependencyRetry
? ['download', 'prepare', 'dependencies', 'dependencies']
: ['download', 'prepare', 'dependencies'],
? ['prepare', 'dependencies', 'dependencies']
: ['prepare', 'dependencies'],
);
assert.equal(indexRequests, 0);
assert.equal(checksumRequests, 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const { generateInstallLockfile, installManifest, INSTALL_PNPM_VERSION } = await
pathToFileURL(join(cli, 'generate-install-lockfile.ts')).href
);
const version = JSON.parse(readFileSync(join(cli, 'package.json'), 'utf8')).version;
// Setup installs the pinned pnpm under a content key derived from its integrity:
// short_fingerprint(PINNED_PNPM_INTEGRITY) in crates/vp_setup/src/install.rs (the
// first 32 hex of its SHA-256). Seed the install there so the pinned bootstrap
// reuses it instead of downloading.
const SETUP_PNPM_KEY = '2f9ea5a98bd92b2d1d6694aedb029783';

// Only the pinned pnpm distribution uses the runner's cached registry. Real pnpm
// is necessary to exercise configuration precedence and frozen-lockfile checks.
Expand Down Expand Up @@ -201,7 +206,11 @@ function createInstallation(name, settings = []) {
join(installNodeBin, 'node'),
);
}
cpSync(pnpmRoot, join(home, 'package_manager/pnpm', INSTALL_PNPM_VERSION), { recursive: true });
cpSync(
join(pnpmRoot, 'pnpm'),
join(home, 'package_manager/pnpm/.verified-v1', INSTALL_PNPM_VERSION, SETUP_PNPM_KEY, 'pnpm'),
{ recursive: true },
);
return { directory, home, ...user };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ name = "installation_progress_interactive"
vp = "global"
skip-platforms = ["windows"]
steps = [{ argv = ["node", "install.mjs", "interactive"], interactions = [
{ expect-milestone = "install:download:ready" },
{ write-key = "enter" },
{ expect-milestone = "install:prepare:ready" },
{ write-key = "enter" },
{ expect-milestone = "install:dependencies:ready" },
Expand All @@ -41,8 +39,6 @@ name = "installation_progress_failure"
vp = "global"
skip-platforms = ["windows"]
steps = [{ argv = ["node", "install.mjs", "interactive", "failure"], interactions = [
{ expect-milestone = "install:download:ready" },
{ write-key = "enter" },
{ expect-milestone = "install:prepare:ready" },
{ write-key = "enter" },
{ expect-milestone = "install:dependencies:ready" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@

## `node install.mjs interactive failure`

**→ expect-milestone:** `install:download:ready`

```
Before installation: preserve this output.
info: installing vite-plus@<version>...
⠿ Downloading pnpm <version>... <size> B (<size> B/s)
```

**← write-key:** `enter`

**→ expect-milestone:** `install:prepare:ready`

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@

## `node install.mjs interactive`

**→ expect-milestone:** `install:download:ready`

```
Before installation: preserve this output.
info: installing vite-plus@<version>...
⠿ Downloading pnpm <version>... <size> B (<size> B/s)
```

**← write-key:** `enter`

**→ expect-milestone:** `install:prepare:ready`

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ const version = JSON.parse(readFileSync(join(cli, 'package.json'), 'utf8')).vers
const pnpmVersion = /export const INSTALL_PNPM_VERSION = '([^']+)';/.exec(
readFileSync(join(cli, 'generate-install-lockfile.ts'), 'utf8'),
)[1];
// Setup installs the pinned pnpm under a content key derived from its integrity:
// short_fingerprint(PINNED_PNPM_INTEGRITY) in crates/vp_setup/src/install.rs (the
// first 32 hex of its SHA-256). Seed the stub there so the pinned bootstrap reuses
// it instead of downloading.
const SETUP_PNPM_KEY = '2f9ea5a98bd92b2d1d6694aedb029783';
const home = resolve('upgrade-home');
const user = resolve('upgrade-user');
const current = join(home, 'current');
Expand Down Expand Up @@ -109,7 +114,7 @@ for (const nodeVersion of [installNode, targetNode]) {
mkdirSync(dirname(destination), { recursive: true });
copyFileSync(join(runnerHome, 'js_runtime/node', nodeVersion, nodePath), destination);
}
const pnpm = join(home, 'package_manager/pnpm', pnpmVersion, 'pnpm');
const pnpm = join(home, 'package_manager/pnpm/.verified-v1', pnpmVersion, SETUP_PNPM_KEY, 'pnpm');
write(join(pnpm, 'package.json'), JSON.stringify({
version: pnpmVersion,
engines: { node: incompatible ? `>=${installNode}` : '*' },
Expand Down
Loading
Loading