diff --git a/.changeset/happy-fireants-approve.md b/.changeset/happy-fireants-approve.md new file mode 100644 index 0000000000..b6d40b0fb1 --- /dev/null +++ b/.changeset/happy-fireants-approve.md @@ -0,0 +1,5 @@ +--- +"@pnpm/assert-project": minor +--- + +expose dir of project diff --git a/.changeset/itchy-ladybugs-breathe.md b/.changeset/itchy-ladybugs-breathe.md new file mode 100644 index 0000000000..f0b73c6cb3 --- /dev/null +++ b/.changeset/itchy-ladybugs-breathe.md @@ -0,0 +1,6 @@ +--- +"pnpm": minor +"@pnpm/plugin-commands-installation": minor +--- + +add new command `pnpm fetch` diff --git a/.changeset/ten-sheep-wonder.md b/.changeset/ten-sheep-wonder.md new file mode 100644 index 0000000000..1109997913 --- /dev/null +++ b/.changeset/ten-sheep-wonder.md @@ -0,0 +1,6 @@ +--- +"@pnpm/headless": minor +"supi": minor +--- + +support fetch package without package manifest diff --git a/packages/headless/src/index.ts b/packages/headless/src/index.ts index 20f041fe7f..f5fa3c4427 100644 --- a/packages/headless/src/index.ts +++ b/packages/headless/src/index.ts @@ -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() - 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() - 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, diff --git a/packages/headless/test/fixtures/ignore-package-manifest/.npmrc b/packages/headless/test/fixtures/ignore-package-manifest/.npmrc new file mode 100644 index 0000000000..32b7ad3cf0 --- /dev/null +++ b/packages/headless/test/fixtures/ignore-package-manifest/.npmrc @@ -0,0 +1,2 @@ +link-workspace-packages=true +shared-workspace-lockfile=true diff --git a/packages/headless/test/fixtures/ignore-package-manifest/pnpm-lock.yaml b/packages/headless/test/fixtures/ignore-package-manifest/pnpm-lock.yaml new file mode 100644 index 0000000000..50329d18ba --- /dev/null +++ b/packages/headless/test/fixtures/ignore-package-manifest/pnpm-lock.yaml @@ -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 diff --git a/packages/headless/test/index.ts b/packages/headless/test/index.ts index 2df35b0947..bf7407ac03 100644 --- a/packages/headless/test/index.ts +++ b/packages/headless/test/index.ts @@ -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') diff --git a/packages/headless/test/utils/testDefaults.ts b/packages/headless/test/utils/testDefaults.ts index ead874b8ff..f2d3648511 100644 --- a/packages/headless/test/utils/testDefaults.ts +++ b/packages/headless/test/utils/testDefaults.ts @@ -56,6 +56,8 @@ export default async function testDefaults ( }, engineStrict: false, force: false, + hoistedDependencies: {}, + hoistPattern: ['*'], include, lockfileDir, packageManager: { diff --git a/packages/plugin-commands-installation/src/fetch.ts b/packages/plugin-commands-installation/src/fetch.ts new file mode 100644 index 0000000000..3b114d0631 --- /dev/null +++ b/packages/plugin-commands-installation/src/fetch.ts @@ -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 & 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) +} diff --git a/packages/plugin-commands-installation/src/index.ts b/packages/plugin-commands-installation/src/index.ts index a34692fc02..c6b5a96d07 100644 --- a/packages/plugin-commands-installation/src/index.ts +++ b/packages/plugin-commands-installation/src/index.ts @@ -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 } diff --git a/packages/plugin-commands-installation/src/install.ts b/packages/plugin-commands-installation/src/install.ts index 3319e73dd6..bf3cc61246 100644 --- a/packages/plugin-commands-installation/src/install.ts +++ b/packages/plugin-commands-installation/src/install.ts @@ -263,7 +263,6 @@ export type InstallCommandOptions = Pick { + 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') +}) diff --git a/packages/pnpm/src/cmd/index.ts b/packages/pnpm/src/cmd/index.ts index 7d3cadfa39..20839145d2 100644 --- a/packages/pnpm/src/cmd/index.ts +++ b/packages/pnpm/src/cmd/index.ts @@ -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, diff --git a/packages/supi/src/install/extendInstallOptions.ts b/packages/supi/src/install/extendInstallOptions.ts index 945bdcf4f0..c90a5722db 100644 --- a/packages/supi/src/install/extendInstallOptions.ts +++ b/packages/supi/src/install/extendInstallOptions.ts @@ -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, diff --git a/packages/supi/src/install/index.ts b/packages/supi/src/install/index.ts index 6c6415fb94..745474d8ad 100644 --- a/packages/supi/src/install/index.ts +++ b/packages/supi/src/install/index.ts @@ -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) { diff --git a/privatePackages/assert-project/src/index.ts b/privatePackages/assert-project/src/index.ts index 56e6ccf96a..d191699d23 100644 --- a/privatePackages/assert-project/src/index.ts +++ b/privatePackages/assert-project/src/index.ts @@ -16,6 +16,7 @@ export type RawLockfile = Lockfile & Partial export interface Project { // eslint-disable-next-line requireModule: (moduleName: string) => any + dir: () => string has: (pkgName: string, modulesDir?: string) => Promise hasNot: (pkgName: string, modulesDir?: string) => Promise getStorePath: () => Promise @@ -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))