feat: new command: pnpm fetch (#3294)

Co-authored-by: Zoltan Kochan <zkochan@users.noreply.github.com>

close #3207
This commit is contained in:
Xingcan LAN
2021-04-02 02:18:02 +08:00
committed by GitHub
parent 2627d0d7ed
commit 735d2ac792
16 changed files with 483 additions and 68 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/assert-project": minor
---
expose dir of project

View File

@@ -0,0 +1,6 @@
---
"pnpm": minor
"@pnpm/plugin-commands-installation": minor
---
add new command `pnpm fetch`

View File

@@ -0,0 +1,6 @@
---
"@pnpm/headless": minor
"supi": minor
---
support fetch package without package manifest

View File

@@ -80,6 +80,7 @@ export interface HeadlessOptions {
engineStrict: boolean
extraBinPaths?: string[]
ignoreScripts: boolean
ignorePackageManifest?: boolean
include: IncludedDependencies
projects: Array<{
binsDir: string
@@ -143,11 +144,13 @@ export default async (opts: HeadlessOptions) => {
const hoistedModulesDir = path.join(virtualStoreDir, 'node_modules')
const publicHoistedModulesDir = rootModulesDir
for (const { id, manifest, rootDir } of opts.projects) {
if (!satisfiesPackageManifest(wantedLockfile, manifest, id)) {
throw new PnpmError('OUTDATED_LOCKFILE',
`Cannot install with "frozen-lockfile" because ${WANTED_LOCKFILE} is not up-to-date with ` +
path.relative(lockfileDir, path.join(rootDir, 'package.json')))
if (!opts.ignorePackageManifest) {
for (const { id, manifest, rootDir } of opts.projects) {
if (!satisfiesPackageManifest(wantedLockfile, manifest, id)) {
throw new PnpmError('OUTDATED_LOCKFILE',
`Cannot install with "frozen-lockfile" because ${WANTED_LOCKFILE} is not up-to-date with ` +
path.relative(lockfileDir, path.join(rootDir, 'package.json')))
}
}
}
@@ -160,7 +163,7 @@ export default async (opts: HeadlessOptions) => {
unsafePerm: opts.unsafePerm || false,
}
if (!opts.ignoreScripts) {
if (!opts.ignoreScripts && !opts.ignorePackageManifest) {
await runLifecycleHooksConcurrently(
['preinstall'],
opts.projects,
@@ -170,7 +173,7 @@ export default async (opts: HeadlessOptions) => {
}
const skipped = opts.skipped || new Set<string>()
if (currentLockfile != null) {
if (currentLockfile != null && !opts.ignorePackageManifest) {
await prune(
opts.projects,
{
@@ -207,7 +210,10 @@ export default async (opts: HeadlessOptions) => {
registries: opts.registries,
skipped,
}
const filteredLockfile = filterLockfileByImportersAndEngine(wantedLockfile, opts.projects.map(({ id }) => id), {
const importerIds = opts.ignorePackageManifest
? Object.keys(wantedLockfile.importers)
: opts.projects.map(({ id }) => id)
const filteredLockfile = filterLockfileByImportersAndEngine(wantedLockfile, importerIds, {
...filterOpts,
currentEngine: opts.currentEngine,
engineStrict: opts.engineStrict,
@@ -220,7 +226,7 @@ export default async (opts: HeadlessOptions) => {
opts.force ? null : currentLockfile,
{
...opts,
importerIds: opts.projects.map(({ id }) => id),
importerIds,
lockfileDir,
skipped,
virtualStoreDir,
@@ -275,7 +281,7 @@ export default async (opts: HeadlessOptions) => {
})
let newHoistedDependencies!: HoistedDependencies
if (opts.hoistPattern != null || opts.publicHoistPattern != null) {
if (opts.ignorePackageManifest !== true && (opts.hoistPattern != null || opts.publicHoistPattern != null)) {
// It is important to keep the skipped packages in the lockfile which will be saved as the "current lockfile".
// pnpm is comparing the current lockfile to the wanted one and they should much.
// But for hoisting, we need a version of the lockfile w/o the skipped packages, so we're making a copy.
@@ -296,27 +302,6 @@ export default async (opts: HeadlessOptions) => {
newHoistedDependencies = {}
}
await Promise.all(opts.projects.map(async ({ rootDir, id, manifest, modulesDir }) => {
if (opts.symlink !== false) {
await linkRootPackages(filteredLockfile, {
importerId: id,
importerModulesDir: modulesDir,
lockfileDir,
projectDir: rootDir,
projects: opts.projects,
registries: opts.registries,
rootDependencies: directDependenciesByImporterId[id],
})
}
// Even though headless installation will never update the package.json
// this needs to be logged because otherwise install summary won't be printed
packageManifestLogger.debug({
prefix: rootDir,
updated: manifest,
})
}))
if (opts.ignoreScripts) {
for (const { id, manifest } of opts.projects) {
if (opts.ignoreScripts && ((manifest?.scripts) != null) &&
@@ -337,7 +322,7 @@ export default async (opts: HeadlessOptions) => {
)
} else {
const directNodes = new Set<string>()
for (const { id } of opts.projects) {
for (const id of importerIds) {
R
.values(directDependenciesByImporterId[id])
.filter((loc) => graph[loc])
@@ -370,32 +355,6 @@ export default async (opts: HeadlessOptions) => {
})
}
await linkAllBins(graph, { optional: opts.include.optionalDependencies, warn })
await Promise.all(opts.projects.map(async (project) => {
if (opts.publicHoistPattern?.length && path.relative(opts.lockfileDir, project.rootDir) === '') {
await linkBinsOfImporter(project)
} else {
const directPkgDirs = Object.values(directDependenciesByImporterId[project.id])
await linkBinsOfPackages(
(
await Promise.all(
directPkgDirs.map(async (dir) => ({
location: dir,
manifest: await safeReadProjectManifestOnly(dir),
}))
)
)
.filter(({ manifest }) => manifest != null) as Array<{ location: string, manifest: DependencyManifest }>,
project.binsDir,
{ warn: (message: string) => logger.info({ message, prefix: project.rootDir }) }
)
}
}))
if ((currentLockfile != null) && !R.equals(opts.projects.map(({ id }) => id).sort(), Object.keys(filteredLockfile.importers).sort())) {
Object.assign(filteredLockfile.packages, currentLockfile.packages)
}
await writeCurrentLockfile(virtualStoreDir, filteredLockfile)
await writeModulesYaml(rootModulesDir, {
hoistedDependencies: newHoistedDependencies,
hoistPattern: opts.hoistPattern,
@@ -412,8 +371,59 @@ export default async (opts: HeadlessOptions) => {
storeDir: opts.storeDir,
virtualStoreDir,
})
}
await linkAllBins(graph, { optional: opts.include.optionalDependencies, warn })
if ((currentLockfile != null) && !R.equals(importerIds.sort(), Object.keys(filteredLockfile.importers).sort())) {
Object.assign(filteredLockfile.packages, currentLockfile.packages)
}
await writeCurrentLockfile(virtualStoreDir, filteredLockfile)
/** Skip linking and due to no project manifest */
if (!opts.ignorePackageManifest) {
await Promise.all(opts.projects.map(async ({ rootDir, id, manifest, modulesDir }) => {
if (opts.symlink !== false) {
await linkRootPackages(filteredLockfile, {
importerId: id,
importerModulesDir: modulesDir,
lockfileDir,
projectDir: rootDir,
projects: opts.projects,
registries: opts.registries,
rootDependencies: directDependenciesByImporterId[id],
})
}
// Even though headless installation will never update the package.json
// this needs to be logged because otherwise install summary won't be printed
packageManifestLogger.debug({
prefix: rootDir,
updated: manifest,
})
}))
await Promise.all(opts.projects.map(async (project) => {
if (opts.publicHoistPattern?.length && path.relative(opts.lockfileDir, project.rootDir) === '') {
await linkBinsOfImporter(project)
} else {
const directPkgDirs = Object.values(directDependenciesByImporterId[project.id])
await linkBinsOfPackages(
(
await Promise.all(
directPkgDirs.map(async (dir) => ({
location: dir,
manifest: await safeReadProjectManifestOnly(dir),
}))
)
)
.filter(({ manifest }) => manifest != null) as Array<{ location: string, manifest: DependencyManifest }>,
project.binsDir,
{ warn: (message: string) => logger.info({ message, prefix: project.rootDir }) }
)
}
}))
}
}
// waiting till package requests are finished
await Promise.all(depNodes.map(({ finishing }) => finishing))
@@ -421,7 +431,7 @@ export default async (opts: HeadlessOptions) => {
await opts.storeController.close()
if (!opts.ignoreScripts) {
if (!opts.ignoreScripts && !opts.ignorePackageManifest) {
await runLifecycleHooksConcurrently(
['install', 'postinstall', 'prepublish', 'prepare'],
opts.projects,

View File

@@ -0,0 +1,2 @@
link-workspace-packages=true
shared-workspace-lockfile=true

View File

@@ -0,0 +1,103 @@
dependencies:
is-positive: 1.0.0
rimraf: 2.7.1
devDependencies:
is-negative: 2.1.0
lockfileVersion: 5.1
optionalDependencies:
colors: 1.2.0
packages:
/balanced-match/1.0.0:
dev: false
resolution:
integrity: sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
/brace-expansion/1.1.11:
dependencies:
balanced-match: 1.0.0
concat-map: 0.0.1
dev: false
resolution:
integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
/colors/1.2.0:
dev: false
engines:
node: '>=0.1.90'
optional: true
resolution:
integrity: sha512-lweugcX5nailCqZBttArTojZZpHGWhmFJX78KJHlxwhM8tLAy5QCgRgRxrubrksdvA+2Y3inWG5TToyyjL82BQ==
/concat-map/0.0.1:
dev: false
resolution:
integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
/fs.realpath/1.0.0:
dev: false
resolution:
integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
/glob/7.1.6:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: false
resolution:
integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
/inflight/1.0.6:
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: false
resolution:
integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
/inherits/2.0.4:
dev: false
resolution:
integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
/is-negative/2.1.0:
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-8Nhjd6oVpkw0lh84rCqb4rQKEYc=
/is-positive/1.0.0:
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-iACYVrZKLx632LsBeUGEJK4EUss=
/minimatch/3.0.4:
dependencies:
brace-expansion: 1.1.11
dev: false
resolution:
integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
/once/1.4.0:
dependencies:
wrappy: 1.0.2
dev: false
resolution:
integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
/path-is-absolute/1.0.1:
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
/rimraf/2.7.1:
dependencies:
glob: 7.1.6
dev: false
hasBin: true
resolution:
integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
/wrappy/1.0.2:
dev: false
resolution:
integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
specifiers:
colors: 1.2.0
is-negative: ^2.1.0
is-positive: ^1.0.0
rimraf: ^2.6.2

View File

@@ -123,6 +123,81 @@ test('installing only dev deps', async () => {
await project.hasNot('colors')
})
test('installing with package manifest ignored', async () => {
const prefix = path.join(fixtures, 'ignore-package-manifest')
await rimraf(path.join(prefix, 'node_modules'))
const opt = await testDefaults({
projects: [],
include: {
dependencies: true,
devDependencies: true,
optionalDependencies: true,
},
lockfileDir: prefix,
})
await headless({ ...opt, ignorePackageManifest: true })
const project = assertProject(prefix)
const currentLockfile = await project.readCurrentLockfile()
expect(currentLockfile.packages).toHaveProperty(['/is-positive/1.0.0'])
expect(currentLockfile.packages).toHaveProperty(['/is-negative/2.1.0'])
await project.storeHas('is-negative')
await project.storeHas('is-positive')
await project.hasNot('is-negative')
await project.hasNot('is-positive')
})
test('installing only prod package with package manifest ignored', async () => {
const prefix = path.join(fixtures, 'ignore-package-manifest')
await rimraf(path.join(prefix, 'node_modules'))
const opt = await testDefaults({
projects: [],
include: {
dependencies: true,
devDependencies: false,
optionalDependencies: true,
},
lockfileDir: prefix,
})
await headless({ ...opt, ignorePackageManifest: true })
const project = assertProject(prefix)
const currentLockfile = await project.readCurrentLockfile()
expect(currentLockfile.packages).not.toHaveProperty(['/is-negative/2.1.0'])
expect(currentLockfile.packages).toHaveProperty(['/is-positive/1.0.0'])
await project.storeHasNot('is-negative')
await project.storeHas('is-positive')
await project.hasNot('is-negative')
await project.hasNot('is-positive')
})
test('installing only dev package with package manifest ignored', async () => {
const prefix = path.join(fixtures, 'ignore-package-manifest')
await rimraf(path.join(prefix, 'node_modules'))
const opt = await testDefaults({
projects: [],
include: {
dependencies: false,
devDependencies: true,
optionalDependencies: false,
},
lockfileDir: prefix,
})
await headless({ ...opt, ignorePackageManifest: true })
const project = assertProject(prefix)
const currentLockfile = await project.readCurrentLockfile()
expect(currentLockfile.packages).toHaveProperty(['/is-negative/2.1.0'])
expect(currentLockfile.packages).not.toHaveProperty(['/is-positive/1.0.0'])
await project.storeHasNot('is-negative')
await project.storeHas('is-positive')
await project.hasNot('is-negative')
await project.hasNot('is-positive')
})
test('installing non-prod deps then all deps', async () => {
const prefix = path.join(fixtures, 'prod-dep-is-dev-subdep')

View File

@@ -56,6 +56,8 @@ export default async function testDefaults (
},
engineStrict: false,
force: false,
hoistedDependencies: {},
hoistPattern: ['*'],
include,
lockfileDir,
packageManager: {

View File

@@ -0,0 +1,72 @@
import { docsUrl } from '@pnpm/cli-utils'
import { UNIVERSAL_OPTIONS } from '@pnpm/common-cli-options-help'
import { Config, types as allTypes } from '@pnpm/config'
import { createOrConnectStoreController, CreateStoreControllerOptions } from '@pnpm/store-connection-manager'
import { InstallOptions, mutateModules } from 'supi'
import * as R from 'ramda'
import renderHelp from 'render-help'
export const rcOptionsTypes = cliOptionsTypes
export function cliOptionsTypes () {
return R.pick([
'production',
'dev',
], allTypes)
}
export const commandNames = ['fetch']
export function help () {
return renderHelp({
description: 'Fetch packages from a lockfile into virtual store, package manifest is ignored. WARNING! This is an experimental command. Breaking changes may be introduced in non-major versions of the CLI',
descriptionLists: [
{
title: 'Options',
list: [
{
description: 'Only development packages will be fetched',
name: '--dev',
},
{
description: 'Development packages will not be fetched',
name: '--prod',
},
...UNIVERSAL_OPTIONS,
],
},
],
url: docsUrl('fetch'),
usages: ['pnpm fetch [--dev | --prod]'],
})
}
export async function handler (
opts: Pick<Config, 'production' | 'dev'> & CreateStoreControllerOptions
) {
const store = await createOrConnectStoreController(opts)
const include = {
dependencies: opts.production !== false,
devDependencies: opts.dev !== false,
// when including optional deps, production is also required when perform headless install
optionalDependencies: opts.production !== false,
}
return mutateModules([
{
buildIndex: 0,
manifest: {},
mutation: 'install',
pruneDirectDependencies: true,
rootDir: process.cwd(),
},
], {
...opts,
ignorePackageManifest: true,
include,
modulesCacheMaxAge: 0,
pruneStore: true,
storeController: store.ctrl,
storeDir: store.dir,
} as InstallOptions)
}

View File

@@ -1,9 +1,10 @@
import * as add from './add'
import * as install from './install'
import * as fetch from './fetch'
import * as link from './link'
import * as prune from './prune'
import * as remove from './remove'
import * as unlink from './unlink'
import * as update from './update'
export { add, install, link, prune, remove, unlink, update }
export { add, fetch, install, link, prune, remove, unlink, update }

View File

@@ -263,7 +263,6 @@ export type InstallCommandOptions = Pick<Config,
| 'lockfileOnly'
| 'pnpmfile'
| 'production'
| 'rawLocalConfig'
| 'registries'
| 'save'
| 'saveDev'

View File

@@ -0,0 +1,121 @@
import path from 'path'
import { install, fetch } from '@pnpm/plugin-commands-installation'
import prepare from '@pnpm/prepare'
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import rimraf from 'rimraf'
import { promisify } from 'util'
const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}`
const DEFAULT_OPTIONS = {
argv: {
original: [],
},
bail: false,
bin: 'node_modules/.bin',
cliOptions: {},
include: {
dependencies: true,
devDependencies: true,
optionalDependencies: true,
},
lock: true,
pnpmfile: '.pnpmfile.cjs',
rawConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },
registries: {
default: REGISTRY_URL,
},
sort: true,
workspaceConcurrency: 1,
}
test('fetch dependencies', async () => {
const project = prepare({
dependencies: { 'is-positive': '1.0.0' },
devDependencies: { 'is-negative': '1.0.0' },
})
const storeDir = path.resolve('store')
await install.handler({
...DEFAULT_OPTIONS,
dir: process.cwd(),
linkWorkspacePackages: true,
storeDir,
})
await promisify(rimraf)(path.resolve(project.dir(), 'node_modules'))
await promisify(rimraf)(path.resolve(project.dir(), './package.json'))
await project.storeHasNot('is-negative')
await project.storeHasNot('is-positive')
await fetch.handler({
...DEFAULT_OPTIONS,
dir: process.cwd(),
storeDir,
})
await project.storeHas('is-positive')
await project.storeHas('is-negative')
})
test('fetch production dependencies', async () => {
const project = prepare({
dependencies: { 'is-positive': '1.0.0' },
devDependencies: { 'is-negative': '1.0.0' },
})
const storeDir = path.resolve('store')
await install.handler({
...DEFAULT_OPTIONS,
dir: process.cwd(),
linkWorkspacePackages: true,
storeDir,
})
await promisify(rimraf)(path.resolve(project.dir(), 'node_modules'))
await promisify(rimraf)(path.resolve(project.dir(), './package.json'))
await project.storeHasNot('is-negative')
await project.storeHasNot('is-positive')
await fetch.handler({
...DEFAULT_OPTIONS,
dev: true,
dir: process.cwd(),
storeDir,
})
await project.storeHasNot('is-negative')
await project.storeHas('is-positive')
})
test('fetch only dev dependencies', async () => {
const project = prepare({
dependencies: { 'is-positive': '1.0.0' },
devDependencies: { 'is-negative': '1.0.0' },
})
const storeDir = path.resolve('store')
await install.handler({
...DEFAULT_OPTIONS,
dir: process.cwd(),
linkWorkspacePackages: true,
storeDir,
})
await promisify(rimraf)(path.resolve(project.dir(), 'node_modules'))
await promisify(rimraf)(path.resolve(project.dir(), './package.json'))
await project.storeHasNot('is-negative')
await project.storeHasNot('is-positive')
await fetch.handler({
...DEFAULT_OPTIONS,
dev: true,
dir: process.cwd(),
storeDir,
})
await project.storeHas('is-negative')
await project.storeHasNot('is-positive')
})

View File

@@ -2,7 +2,7 @@ import { CompletionFunc } from '@pnpm/command'
import { types as allTypes } from '@pnpm/config'
import { audit } from '@pnpm/plugin-commands-audit'
import { importCommand } from '@pnpm/plugin-commands-import'
import { add, install, link, prune, remove, unlink, update } from '@pnpm/plugin-commands-installation'
import { add, fetch, install, link, prune, remove, unlink, update } from '@pnpm/plugin-commands-installation'
import { list, ll, why } from '@pnpm/plugin-commands-listing'
import { outdated } from '@pnpm/plugin-commands-outdated'
import { pack, publish } from '@pnpm/plugin-commands-publishing'
@@ -59,6 +59,7 @@ const commands: Array<{
audit,
bin,
exec,
fetch,
importCommand,
install,
installTest,

View File

@@ -21,6 +21,7 @@ export interface StrictInstallOptions {
useLockfile: boolean
linkWorkspacePackagesDepth: number
lockfileOnly: boolean
ignorePackageManifest: boolean
preferFrozenLockfile: boolean
saveWorkspaceProtocol: boolean
preferWorkspacePackages: boolean
@@ -116,6 +117,7 @@ const defaults = async (opts: InstallOptions) => {
lockfileOnly: false,
nodeVersion: process.version,
ownLifecycleHooksStdio: 'inherit',
ignorePackageManifest: false,
packageManager,
preferFrozenLockfile: true,
preferWorkspacePackages: false,

View File

@@ -168,9 +168,11 @@ export async function mutateModules (
? cacheExpired(ctx.modulesFile.prunedAt, opts.modulesCacheMaxAge)
: true
for (const { manifest, rootDir } of ctx.projects) {
if (!manifest) {
throw new Error(`No package.json found in "${rootDir}"`)
if (!maybeOpts.ignorePackageManifest) {
for (const { manifest, rootDir } of ctx.projects) {
if (!manifest) {
throw new Error(`No package.json found in "${rootDir}"`)
}
}
}
@@ -196,6 +198,7 @@ export async function mutateModules (
installsOnly &&
(
frozenLockfile ||
opts.ignorePackageManifest ||
!needsFullResolution &&
opts.preferFrozenLockfile &&
(!opts.pruneLockfileImporters || Object.keys(ctx.wantedLockfile.importers).length === ctx.projects.length) &&
@@ -216,7 +219,11 @@ export async function mutateModules (
throw new Error(`Headless installation requires a ${WANTED_LOCKFILE} file`)
}
} else {
logger.info({ message: 'Lockfile is up-to-date, resolution step is skipped', prefix: opts.lockfileDir })
if (maybeOpts.ignorePackageManifest) {
logger.info({ message: 'Importing packages to virtual store', prefix: opts.lockfileDir })
} else {
logger.info({ message: 'Lockfile is up-to-date, resolution step is skipped', prefix: opts.lockfileDir })
}
try {
await headless({
currentEngine: {
@@ -231,6 +238,7 @@ export async function mutateModules (
hoistedDependencies: ctx.hoistedDependencies,
hoistPattern: ctx.hoistPattern,
ignoreScripts: opts.ignoreScripts,
ignorePackageManifest: opts.ignorePackageManifest,
include: opts.include,
lockfileDir: ctx.lockfileDir,
modulesDir: opts.modulesDir,
@@ -261,7 +269,7 @@ export async function mutateModules (
unsafePerm: opts.unsafePerm,
userAgent: opts.userAgent,
virtualStoreDir: ctx.virtualStoreDir,
wantedLockfile: ctx.wantedLockfile,
wantedLockfile: maybeOpts.ignorePackageManifest ? undefined : ctx.wantedLockfile,
})
return projects
} catch (error) {

View File

@@ -16,6 +16,7 @@ export type RawLockfile = Lockfile & Partial<ProjectSnapshot>
export interface Project {
// eslint-disable-next-line
requireModule: (moduleName: string) => any
dir: () => string
has: (pkgName: string, modulesDir?: string) => Promise<void>
hasNot: (pkgName: string, modulesDir?: string) => Promise<void>
getStorePath: () => Promise<string>
@@ -82,6 +83,7 @@ export default (projectPath: string, encodedRegistryName?: string): Project => {
// eslint-disable-next-line
const notOk = (value: any) => expect(value).toBeFalsy()
return {
dir: () => projectPath,
requireModule (pkgName: string) {
// eslint-disable-next-line
return require(path.join(modules, pkgName))