feat: enable-modules-dir=false

PR #3051
This commit is contained in:
Zoltan Kochan
2021-01-04 23:37:37 +02:00
committed by GitHub
parent 90485aef41
commit f40bc59277
8 changed files with 187 additions and 140 deletions

View File

@@ -0,0 +1,7 @@
---
"@pnpm/config": minor
"@pnpm/headless": minor
"supi": patch
---
New option added: enableModulesDir. When `false`, pnpm will not write any files to the modules directory. This is useful for when you want to mount the modules directory with FUSE.

View File

@@ -117,6 +117,7 @@ export interface Config {
recursiveInstall?: boolean
symlink: boolean
enablePnp?: boolean
enableModulesDir: boolean
registries: Registries
ignoreWorkspaceRootCheck: boolean

View File

@@ -32,6 +32,7 @@ export const types = Object.assign({
color: ['always', 'auto', 'never'],
dev: [null, true],
dir: String,
'enable-modules-dir': Boolean,
'fetching-concurrency': Number,
filter: [String, Array],
'frozen-lockfile': Boolean,
@@ -146,6 +147,7 @@ export default async (
const npmConfig = loadNpmConf(cliOptions, rcOptionsTypes, {
bail: true,
color: 'auto',
'enable-modules-dir': true,
'fetch-retries': 2,
'fetch-retry-factor': 10,
'fetch-retry-maxtimeout': 60000,

View File

@@ -116,6 +116,7 @@ export interface HeadlessOptions {
ownLifecycleHooksStdio?: 'inherit' | 'pipe'
pendingBuilds: string[]
skipped: Set<string>
enableModulesDir?: boolean
}
export default async (opts: HeadlessOptions) => {
@@ -245,26 +246,6 @@ export default async (opts: HeadlessOptions) => {
prefix: lockfileDir,
})
await Promise.all(depNodes.map((depNode) => fs.mkdir(depNode.modules, { recursive: true })))
await Promise.all([
opts.symlink === false
? Promise.resolve()
: linkAllModules(depNodes, {
lockfileDir,
optional: opts.include.optionalDependencies,
}),
linkAllPkgs(opts.storeController, depNodes, {
force: opts.force,
lockfileDir: opts.lockfileDir,
targetEngine: opts.sideEffectsCacheRead && ENGINE_NAME || undefined,
}),
])
stageLogger.debug({
prefix: lockfileDir,
stage: 'importing_done',
})
function warn (message: string) {
logger.info({
message,
@@ -272,134 +253,156 @@ export default async (opts: HeadlessOptions) => {
})
}
let newHoistedDependencies!: HoistedDependencies
if (opts.hoistPattern != null || opts.publicHoistPattern != null) {
newHoistedDependencies = await hoist({
lockfile: filteredLockfile,
lockfileDir,
privateHoistedModulesDir: hoistedModulesDir,
privateHoistPattern: opts.hoistPattern ?? [],
publicHoistedModulesDir,
publicHoistPattern: opts.publicHoistPattern ?? [],
virtualStoreDir,
})
} else {
newHoistedDependencies = {}
}
if (opts.enableModulesDir !== false) {
await Promise.all(depNodes.map((depNode) => fs.mkdir(depNode.modules, { recursive: true })))
await Promise.all([
opts.symlink === false
? Promise.resolve()
: linkAllModules(depNodes, {
lockfileDir,
optional: opts.include.optionalDependencies,
}),
linkAllPkgs(opts.storeController, depNodes, {
force: opts.force,
lockfileDir: opts.lockfileDir,
targetEngine: opts.sideEffectsCacheRead && ENGINE_NAME || undefined,
}),
])
await Promise.all(opts.projects.map(async ({ rootDir, id, manifest, modulesDir }) => {
if (opts.symlink !== false) {
await linkRootPackages(filteredLockfile, {
importerId: id,
importerModulesDir: modulesDir,
stageLogger.debug({
prefix: lockfileDir,
stage: 'importing_done',
})
let newHoistedDependencies!: HoistedDependencies
if (opts.hoistPattern != null || opts.publicHoistPattern != null) {
newHoistedDependencies = await hoist({
lockfile: filteredLockfile,
lockfileDir,
projectDir: rootDir,
projects: opts.projects,
registries: opts.registries,
rootDependencies: directDependenciesByImporterId[id],
privateHoistedModulesDir: hoistedModulesDir,
privateHoistPattern: opts.hoistPattern ?? [],
publicHoistedModulesDir,
publicHoistPattern: opts.publicHoistPattern ?? [],
virtualStoreDir,
})
} else {
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 &&
(manifest.scripts.preinstall ?? manifest.scripts.prepublish ??
manifest.scripts.install ??
manifest.scripts.postinstall ??
manifest.scripts.prepare)
) {
opts.pendingBuilds.push(id)
}
}
// we can use concat here because we always only append new packages, which are guaranteed to not be there by definition
opts.pendingBuilds = opts.pendingBuilds
.concat(
depNodes
.filter(({ requiresBuild }) => requiresBuild)
.map(({ depPath }) => depPath)
)
} else {
const directNodes = new Set<string>()
for (const { id } of opts.projects) {
R
.values(directDependenciesByImporterId[id])
.filter((loc) => graph[loc])
.forEach((loc) => {
directNodes.add(loc)
})
}
const extraBinPaths = [...opts.extraBinPaths ?? []]
if (opts.hoistPattern) {
extraBinPaths.unshift(path.join(virtualStoreDir, 'node_modules/.bin'))
}
let extraEnv: Record<string, string> | undefined
if (opts.enablePnp) {
extraEnv = makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.js'))
}
await buildModules(graph, Array.from(directNodes), {
childConcurrency: opts.childConcurrency,
extraBinPaths,
extraEnv,
lockfileDir,
optional: opts.include.optionalDependencies,
rawConfig: opts.rawConfig,
rootModulesDir: virtualStoreDir,
scriptShell: opts.scriptShell,
shellEmulator: opts.shellEmulator,
sideEffectsCacheWrite: opts.sideEffectsCacheWrite,
storeController: opts.storeController,
unsafePerm: opts.unsafePerm,
userAgent: opts.userAgent,
})
}
// 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 &&
(manifest.scripts.preinstall ?? manifest.scripts.prepublish ??
manifest.scripts.install ??
manifest.scripts.postinstall ??
manifest.scripts.prepare)
) {
opts.pendingBuilds.push(id)
}
}
// we can use concat here because we always only append new packages, which are guaranteed to not be there by definition
opts.pendingBuilds = opts.pendingBuilds
.concat(
depNodes
.filter(({ requiresBuild }) => requiresBuild)
.map(({ depPath }) => depPath)
)
} else {
const directNodes = new Set<string>()
for (const { id } of opts.projects) {
R
.values(directDependenciesByImporterId[id])
.filter((loc) => graph[loc])
.forEach((loc) => {
directNodes.add(loc)
})
}
const extraBinPaths = [...opts.extraBinPaths ?? []]
if (opts.hoistPattern) {
extraBinPaths.unshift(path.join(virtualStoreDir, 'node_modules/.bin'))
}
let extraEnv: Record<string, string> | undefined
if (opts.enablePnp) {
extraEnv = makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.js'))
}
await buildModules(graph, Array.from(directNodes), {
childConcurrency: opts.childConcurrency,
extraBinPaths,
extraEnv,
lockfileDir,
optional: opts.include.optionalDependencies,
rawConfig: opts.rawConfig,
rootModulesDir: virtualStoreDir,
scriptShell: opts.scriptShell,
shellEmulator: opts.shellEmulator,
sideEffectsCacheWrite: opts.sideEffectsCacheWrite,
storeController: opts.storeController,
unsafePerm: opts.unsafePerm,
userAgent: opts.userAgent,
})
}
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),
}))
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 }) }
)
.filter(({ manifest }) => manifest != null) as Array<{ location: string, manifest: DependencyManifest }>,
project.binsDir,
{ warn: (message: string) => logger.info({ message, prefix: project.rootDir }) }
)
}
}))
}
}))
if (currentLockfile && !R.equals(opts.projects.map(({ id }) => id).sort(), Object.keys(filteredLockfile.importers).sort())) {
Object.assign(filteredLockfile.packages, currentLockfile.packages)
if (currentLockfile && !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,
included: opts.include,
layoutVersion: LAYOUT_VERSION,
packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`,
pendingBuilds: opts.pendingBuilds,
publicHoistPattern: opts.publicHoistPattern,
registries: opts.registries,
skipped: Array.from(skipped),
storeDir: opts.storeDir,
virtualStoreDir,
})
}
await writeCurrentLockfile(virtualStoreDir, filteredLockfile)
await writeModulesYaml(rootModulesDir, {
hoistedDependencies: newHoistedDependencies,
hoistPattern: opts.hoistPattern,
included: opts.include,
layoutVersion: LAYOUT_VERSION,
packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`,
pendingBuilds: opts.pendingBuilds,
publicHoistPattern: opts.publicHoistPattern,
registries: opts.registries,
skipped: Array.from(skipped),
storeDir: opts.storeDir,
virtualStoreDir,
})
// waiting till package requests are finished
await Promise.all(depNodes.map(({ finishing }) => finishing))

View File

@@ -721,3 +721,16 @@ test('installing with no symlinks but with PnP', async () => {
expect(await project.readModulesManifest()).toBeTruthy()
expect(await exists(path.join(prefix, '.pnp.js'))).toBeTruthy()
})
test('installing with no modules directory', async () => {
const prefix = path.join(fixtures, 'simple')
await rimraf(path.join(prefix, 'node_modules'))
await rimraf(path.join(prefix, '.pnp.js'))
await headless(await testDefaults({
enableModulesDir: false,
lockfileDir: prefix,
}))
expect(await exists(path.join(prefix, 'node_modules'))).toBeFalsy()
})

View File

@@ -68,6 +68,7 @@ export interface StrictInstallOptions {
virtualStoreDir?: string
dir: string
symlink: boolean
enableModulesDir: boolean
hoistPattern: string[] | undefined
forceHoistPattern: boolean
@@ -142,6 +143,7 @@ const defaults = async (opts: InstallOptions) => {
userAgent: `${packageManager.name}/${packageManager.version} npm/? node/${process.version} ${process.platform} ${process.arch}`,
verifyStoreIntegrity: true,
workspacePackages: {},
enableModulesDir: true,
} as StrictInstallOptions
}

View File

@@ -699,7 +699,7 @@ async function installInContext (
}
const lockfileOpts = { forceSharedFormat: opts.forceSharedLockfile }
if (!opts.lockfileOnly) {
if (!opts.lockfileOnly && opts.enableModulesDir) {
const result = await linkPackages(
projectsToResolve,
dependenciesGraph,

View File

@@ -1219,3 +1219,22 @@ test('installing with no symlinks with PnP', async () => {
expect(await project.readModulesManifest()).toBeTruthy()
expect(await exists(path.resolve('.pnp.js'))).toBeTruthy()
})
test('installing with no modules directory', async () => {
const project = prepareEmpty()
await addDependenciesToPackage(
{
name: 'project',
version: '0.0.0',
},
['rimraf@2.7.1'],
await testDefaults({
enableModulesDir: false,
fastUnpack: false,
})
)
expect(await project.readLockfile()).toBeTruthy()
expect(await exists(path.resolve('node_modules'))).toBeFalsy()
})