Files
pnpm/pnpm11/cache/commands/test/cacheDelete.cmd.test.ts
Zoltan Kochan 430d892741 feat(pnpr)!: serve every ecosystem under its own prefix (#17087)
pnpr served npm at the server root when npm was the only configured
ecosystem and moved it under /npm/ as soon as a second ecosystem was
added. Adding an ecosystem therefore broke every .npmrc registry URL,
every tarball URL already written into a lockfile, named-registry URLs,
and OIDC workload publish paths, and /npm meant the package `npm` on one
server and the ecosystem prefix on another. This settles section A1 of
pnpm/tasks#131: every ecosystem is always prefixed.

- Ecosystem::base_path is now independent of the configuration. The
  per-configuration Registries::base_path and is_only_ecosystem are gone,
  and the routes, the resolver's tarball URLs, OIDC workload paths and
  pnpr's own-origin route classification all use it.
- The default image registry still answers at /v2/ on the host root; an
  image client cannot be pointed at a prefix. The registry directory keeps
  its `prefixed` and `namedPrefixed` flags for the web UI.
- The read-only token allowlist matched the npm audit endpoints at the
  root only, so read-only tokens were already refused for audits on
  multi-ecosystem servers. It now matches them under the npm prefix.
- /metrics no longer switches between Prometheus metrics and the npm
  package `metrics` on the Accept header; the package is /npm/metrics.
- The pnpm v11 and v12 test harnesses, the registry-mock crate and the
  integrated benchmark address the fixture registry at /npm/ and keep the
  server origin for the pnpr protocol endpoints. The benchmark asks each
  revision mock's registry directory whether npm is prefixed, so it can
  still compare against a pnpr revision from before this change.
2026-10-18 16:55:55 +02:00

108 lines
3.7 KiB
TypeScript

import fs from 'node:fs'
import path from 'node:path'
import { beforeEach, describe, expect, test } from '@jest/globals'
import { cache } from '@pnpm/cache.commands'
import { ABBREVIATED_META_DIR, FULL_FILTERED_META_DIR, FULL_META_DIR } from '@pnpm/constants'
import { prepare } from '@pnpm/prepare'
import { REGISTRY_MOCK_PORT } from '@pnpm/testing.registry-mock'
import { rimrafSync } from '@zkochan/rimraf'
import { safeExeca as execa } from 'execa'
const pnpmBin = path.join(import.meta.dirname, '../../../pnpm/bin/pnpm.mjs')
const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}/npm/`
describe('cache delete', () => {
let cacheDir: string
let storeDir: string
beforeEach(async () => {
prepare()
cacheDir = path.resolve('cache')
storeDir = path.resolve('store')
await execa('node', [
pnpmBin,
'add',
'is-negative@2.1.0',
`--store-dir=${storeDir}`,
`--cache-dir=${cacheDir}`,
'--config.resolution-mode=highest',
// The update check resolves `pnpm@latest` through the same cache, which
// would add a `pnpm.jsonl` entry to what these tests expect to find. It
// is off under CI, so leaving it on would only fail locally.
'--config.update-notifier=false',
`--registry=${REGISTRY}`,
])
rimrafSync('node_modules')
rimrafSync('pnpm-lock.yaml')
await execa('node', [
pnpmBin,
'add',
'is-negative@2.1.0',
'is-positive@1.0.0',
`--store-dir=${storeDir}`,
`--cache-dir=${cacheDir}`,
'--config.resolution-mode=highest',
'--config.update-notifier=false',
])
})
test('delete all metadata from the cache that matches a pattern', async () => {
await cache.handler({
cacheDir,
cliOptions: {},
pnpmHomeDir: storeDir,
}, ['delete', '*-positive'])
const result = await cache.handler({
cacheDir,
cliOptions: {},
pnpmHomeDir: storeDir,
}, ['list'])
expect(result).toBe(`http%3A+localhost+${REGISTRY_MOCK_PORT}%2Fnpm/is-negative.jsonl
https%3A+registry.npmjs.org/is-negative.jsonl`)
})
})
describe('cache delete across metadata directories', () => {
test('deletes a package from every metadata cache directory, not only the one the current mode reads', async () => {
prepare()
const cacheDir = path.resolve('cache')
const registryName = 'https%3A+registry.npmjs.org'
const metaDirs = [ABBREVIATED_META_DIR, FULL_META_DIR, FULL_FILTERED_META_DIR]
const sentinel = (metaDir: string) => path.join(cacheDir, metaDir, registryName, '@vue', 'compiler-core.jsonl')
for (const metaDir of metaDirs) {
fs.mkdirSync(path.dirname(sentinel(metaDir)), { recursive: true })
fs.writeFileSync(sentinel(metaDir), '')
}
const result = await cache.handler({
cacheDir,
cliOptions: {},
pnpmHomeDir: cacheDir,
}, ['delete', '@vue/compiler-core'])
for (const metaDir of metaDirs) {
expect(fs.existsSync(sentinel(metaDir))).toBe(false)
}
expect(result).toBe(`${registryName}/@vue/compiler-core.jsonl`)
})
test('deletes metadata cached under a non-current mode even when the other directories are absent', async () => {
prepare()
const cacheDir = path.resolve('cache')
// The default mode reads `metadata`, but the package is only cached under
// `metadata-full-filtered` and the other directories don't exist.
const sentinel = path.join(cacheDir, FULL_FILTERED_META_DIR, 'https%3A+registry.npmjs.org', '@vue', 'compiler-core.jsonl')
fs.mkdirSync(path.dirname(sentinel), { recursive: true })
fs.writeFileSync(sentinel, '')
await cache.handler({
cacheDir,
cliOptions: {},
pnpmHomeDir: cacheDir,
}, ['delete', '@vue/compiler-core'])
expect(fs.existsSync(sentinel)).toBe(false)
})
})