mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-29 04:21:39 -04:00
feat: new command: pnpm fetch (#3294)
Co-authored-by: Zoltan Kochan <zkochan@users.noreply.github.com> close #3207
This commit is contained in:
5
.changeset/happy-fireants-approve.md
Normal file
5
.changeset/happy-fireants-approve.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/assert-project": minor
|
||||
---
|
||||
|
||||
expose dir of project
|
||||
6
.changeset/itchy-ladybugs-breathe.md
Normal file
6
.changeset/itchy-ladybugs-breathe.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"pnpm": minor
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
---
|
||||
|
||||
add new command `pnpm fetch`
|
||||
6
.changeset/ten-sheep-wonder.md
Normal file
6
.changeset/ten-sheep-wonder.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/headless": minor
|
||||
"supi": minor
|
||||
---
|
||||
|
||||
support fetch package without package manifest
|
||||
@@ -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,
|
||||
|
||||
2
packages/headless/test/fixtures/ignore-package-manifest/.npmrc
vendored
Normal file
2
packages/headless/test/fixtures/ignore-package-manifest/.npmrc
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
link-workspace-packages=true
|
||||
shared-workspace-lockfile=true
|
||||
103
packages/headless/test/fixtures/ignore-package-manifest/pnpm-lock.yaml
generated
vendored
Normal file
103
packages/headless/test/fixtures/ignore-package-manifest/pnpm-lock.yaml
generated
vendored
Normal 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
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ export default async function testDefaults (
|
||||
},
|
||||
engineStrict: false,
|
||||
force: false,
|
||||
hoistedDependencies: {},
|
||||
hoistPattern: ['*'],
|
||||
include,
|
||||
lockfileDir,
|
||||
packageManager: {
|
||||
|
||||
72
packages/plugin-commands-installation/src/fetch.ts
Normal file
72
packages/plugin-commands-installation/src/fetch.ts
Normal 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)
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -263,7 +263,6 @@ export type InstallCommandOptions = Pick<Config,
|
||||
| 'lockfileOnly'
|
||||
| 'pnpmfile'
|
||||
| 'production'
|
||||
| 'rawLocalConfig'
|
||||
| 'registries'
|
||||
| 'save'
|
||||
| 'saveDev'
|
||||
|
||||
121
packages/plugin-commands-installation/test/fetch.ts
Normal file
121
packages/plugin-commands-installation/test/fetch.ts
Normal 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')
|
||||
})
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user