Files
pnpm/__utils__/assert-project/src/index.ts
Zoltan Kochan 4d7cd56ccc chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2 (#11332)
* chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2

- Add explicit `types: ["node"]` to the shared tsconfig because tsgo
  20260421 no longer auto-acquires `@types/*` from `node_modules`.
- Refactor test files to explicitly import jest globals (`describe`,
  `it`, `test`, `expect`, `beforeEach`, etc.) from `@jest/globals`
  instead of relying on `@types/jest` ambient declarations. Under the
  new tsgo build, `import { jest } from '@jest/globals'` shadows the
  ambient `jest` namespace, breaking `@types/jest`'s `declare var
  describe: jest.Describe;` globals.
- Add `@jest/globals` to each package's devDependencies where tests
  now import from it, and add `@types/node` to packages that need it
  but were relying on hoisted resolution.
- Replace `fail()` calls with `throw new Error(...)` since `fail` is
  no longer globally available.

* chore: fix remaining tsgo type-strictness errors

- Strip `as <PnpmType>` casts on objects passed to toMatchObject /
  toStrictEqual / toEqual; @jest/globals rejects the typed objects
  (which include AsymmetricMatchers) vs. the repo-specific type.
- Type `jest.fn<...>()` explicitly where the mock's signature matters
  for toHaveBeenCalledWith.
- Replace `beforeEach(() => X)` with `beforeEach(() => { X })` so the
  return value is void, as the stricter jest typing requires.
- Use `expect.objectContaining({...})` in one place where the full
  expected object triggered stricter type resolution.
- Cast `prompt.mock.calls` arg through `as unknown as Record<...>[]`
  for patch.test.ts's nested-array matchers.
- Fix off-by-one `<reference path>` in pnpm/test/getConfig.test.ts
  that only surfaced now.
- Move `@jest/globals` from devDependencies to dependencies in the
  two `__utils__` packages that import it from `src/`.
- Clean up unused imports from the @jest/globals migration.

* chore: address Copilot review on #11332

- Move misplaced `@jest/globals` imports to the top import block in
  checkEngine, run.ts, and workspace/root-finder tests where the
  script dropped them below executable code.
- Replace `try { await x(); throw new Error('should have thrown') } catch`
  in bins/linker, lockfile/fs, and resolving/local-resolver tests with
  `await expect(x()).rejects.toMatchObject({...})`. The old pattern
  swallowed an unrelated `throw` if the under-test call silently
  succeeded, which would fail on the catch-block assertion with a
  misleading message.
2026-04-21 23:21:52 +02:00

186 lines
6.7 KiB
TypeScript

import fs from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import util from 'node:util'
import { expect } from '@jest/globals'
import { assertStore } from '@pnpm/assert-store'
import { WANTED_LOCKFILE } from '@pnpm/constants'
import type { Modules } from '@pnpm/installing.modules-yaml'
import type { LockfileFile } from '@pnpm/lockfile.types'
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import yaml from 'js-yaml'
import { readYamlFileSync } from 'read-yaml-file'
import { writePackageSync } from 'write-package'
import isExecutable from './isExecutable.js'
const require = createRequire(import.meta.url)
export { isExecutable, type Modules }
export interface Project {
// eslint-disable-next-line
requireModule: (moduleName: string) => any
dir: () => string
has: (pkgName: string, modulesDir?: string) => void
hasNot: (pkgName: string, modulesDir?: string) => void
getStorePath: () => string
resolve: (pkgName: string, version?: string, relativePath?: string) => string
getPkgIndexFilePath: (pkgName: string, version: string) => string
cafsHas: (pkgName: string, version: string) => void
cafsHasNot: (pkgName: string, version: string) => void
storeHas: (pkgName: string, version?: string) => string
storeHasNot: (pkgName: string, version?: string) => void
isExecutable: (pathToExe: string) => void
/**
* TODO: Remove the `Required<T>` cast.
*
* https://github.com/microsoft/TypeScript/pull/32695 might help with this.
*/
readCurrentLockfile: () => Required<LockfileFile>
readModulesManifest: () => Modules | null
/**
* TODO: Remove the `Required<T>` cast.
*
* https://github.com/microsoft/TypeScript/pull/32695 might help with this.
*/
readLockfile: (lockfileName?: string) => Required<LockfileFile>
writePackageJson: (pkgJson: object) => void
}
export function assertProject (projectPath: string, encodedRegistryName?: string): Project {
const ern = encodedRegistryName ?? `localhost+${REGISTRY_MOCK_PORT}`
const modules = path.join(projectPath, 'node_modules')
interface StoreInstance {
storePath: string
getPkgIndexFilePath: (pkgName: string, version: string) => string
cafsHas: (pkgName: string, version: string) => void
cafsHasNot: (pkgName: string, version: string) => void
storeHas: (pkgName: string, version?: string) => void
storeHasNot: (pkgName: string, version?: string) => void
resolve: (pkgName: string, version?: string, relativePath?: string) => string
}
let cachedStore: StoreInstance
function getStoreInstance (): StoreInstance {
if (!cachedStore) {
const modulesYaml = readModulesManifest(modules)
if (modulesYaml == null) {
throw new Error(`Cannot find module store. No .modules.yaml found at "${modules}"`)
}
const storePath = modulesYaml.storeDir
cachedStore = {
storePath,
...assertStore(storePath, ern),
}
}
return cachedStore
}
function getVirtualStoreDir (): string {
const modulesYaml = readModulesManifest(modules)
if (modulesYaml == null) {
return path.join(modules, '.pnpm')
}
if (path.isAbsolute(modulesYaml.virtualStoreDir)) {
return modulesYaml.virtualStoreDir
}
return path.join(modules, modulesYaml.virtualStoreDir)
}
// eslint-disable-next-line
const ok = (value: any) => expect(value).toBeTruthy()
// eslint-disable-next-line
const notOk = (value: any) => expect(value).toBeFalsy()
return {
dir: () => projectPath,
requireModule (pkgName: string) {
return require(path.join(modules, pkgName))
},
has (pkgName: string, _modulesDir?: string) {
const md = _modulesDir ? path.join(projectPath, _modulesDir) : modules
ok(fs.existsSync(path.join(md, pkgName)))
},
hasNot (pkgName: string, _modulesDir?: string) {
const md = _modulesDir ? path.join(projectPath, _modulesDir) : modules
notOk(fs.existsSync(path.join(md, pkgName)))
},
getStorePath () {
const store = getStoreInstance()
return store.storePath
},
resolve (pkgName: string, version?: string, relativePath?: string) {
const store = getStoreInstance()
return store.resolve(pkgName, version, relativePath)
},
getPkgIndexFilePath (pkgName: string, version: string): string {
const store = getStoreInstance()
return store.getPkgIndexFilePath(pkgName, version)
},
cafsHas (pkgName: string, version: string) {
const store = getStoreInstance()
store.cafsHas(pkgName, version)
},
cafsHasNot (pkgName: string, version: string) {
const store = getStoreInstance()
store.cafsHasNot(pkgName, version)
},
storeHas (pkgName: string, version?: string) {
const store = getStoreInstance()
return store.resolve(pkgName, version)
},
storeHasNot (pkgName: string, version?: string) {
try {
const store = getStoreInstance()
store.storeHasNot(pkgName, version)
} catch (err: unknown) {
if (util.types.isNativeError(err) && err.message.startsWith('Cannot find module store')) {
return
}
throw err
}
},
isExecutable (pathToExe: string) {
isExecutable(ok, path.join(modules, pathToExe))
},
readCurrentLockfile () {
try {
return readYamlFileSync(path.join(getVirtualStoreDir(), 'lock.yaml'))
} catch (err: unknown) {
if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') return null!
throw err
}
},
readModulesManifest: () => readModulesManifest(modules),
readLockfile (lockfileName: string = WANTED_LOCKFILE) {
try {
const raw = fs.readFileSync(path.join(projectPath, lockfileName), 'utf8')
// Skip the env lockfile document if present (first document in combined format).
// Cannot import from @pnpm/lockfile.fs here due to circular dependency.
let content = raw
if (raw.startsWith('---\n')) {
const sep = raw.indexOf('\n---\n')
content = sep !== -1 ? raw.slice(sep + '\n---\n'.length) : ''
}
if (!content.trim()) return null!
return yaml.load(content) as Required<LockfileFile>
} catch (err: unknown) {
if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') return null!
throw err
}
},
writePackageJson (pkgJson: object) {
writePackageSync(projectPath, pkgJson as any) // eslint-disable-line
},
}
}
function readModulesManifest (modulesDir: string): Modules {
try {
return readYamlFileSync<Modules>(path.join(modulesDir, '.modules.yaml'))
} catch (err: unknown) {
if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') return null!
throw err
}
}