mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
5
.changeset/afraid-bobcats-carry.md
Normal file
5
.changeset/afraid-bobcats-carry.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/types": minor
|
||||
---
|
||||
|
||||
New optional field added to `dependenciesMeta`: `injected`.
|
||||
5
.changeset/breezy-trees-share.md
Normal file
5
.changeset/breezy-trees-share.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/lifecycle": minor
|
||||
---
|
||||
|
||||
`runLifecycleHooksConcurrently` will relink projects after rebuilding them if they are injected to other projects.
|
||||
5
.changeset/calm-spies-destroy.md
Normal file
5
.changeset/calm-spies-destroy.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/fetcher-base": minor
|
||||
---
|
||||
|
||||
The files response can point to files that are not in the global content-addressable store. In this case, the response will contain a `local: true` property, and the structure of `filesIndex` will be just a `Record<string, string>`.
|
||||
6
.changeset/dirty-beds-draw.md
Normal file
6
.changeset/dirty-beds-draw.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/local-resolver": minor
|
||||
"@pnpm/npm-resolver": minor
|
||||
---
|
||||
|
||||
Support the resolution of injected local dependencies.
|
||||
5
.changeset/dull-pots-end.md
Normal file
5
.changeset/dull-pots-end.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/lifecycle": major
|
||||
---
|
||||
|
||||
`storeController` is a required new option of `runLifecycleHooksConcurrently()`.
|
||||
5
.changeset/eleven-pens-rush.md
Normal file
5
.changeset/eleven-pens-rush.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/resolver-base": minor
|
||||
---
|
||||
|
||||
New optional property added to `WantedDependency`: `injected`.
|
||||
7
.changeset/fair-panthers-grab.md
Normal file
7
.changeset/fair-panthers-grab.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/package-requester": minor
|
||||
"@pnpm/package-store": minor
|
||||
"@pnpm/resolve-dependencies": minor
|
||||
---
|
||||
|
||||
Added support for "injected" dependencies.
|
||||
5
.changeset/many-spoons-eat.md
Normal file
5
.changeset/many-spoons-eat.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/fetcher-base": minor
|
||||
---
|
||||
|
||||
New optional property is added to `PackageFilesResponse` for specifying how the package needs to be imported to the modules directory. Should it be hard linked, copied, or cloned.
|
||||
5
.changeset/moody-poems-tell.md
Normal file
5
.changeset/moody-poems-tell.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/directory-fetcher": major
|
||||
---
|
||||
|
||||
Initial release.
|
||||
5
.changeset/real-cups-leave.md
Normal file
5
.changeset/real-cups-leave.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/client": minor
|
||||
---
|
||||
|
||||
New fetcher added for fetching local directory dependencies.
|
||||
5
.changeset/six-tools-tell.md
Normal file
5
.changeset/six-tools-tell.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-rebuild": minor
|
||||
---
|
||||
|
||||
Injected dependencies should be relinked after they are rebuilt.
|
||||
6
.changeset/slow-kangaroos-boil.md
Normal file
6
.changeset/slow-kangaroos-boil.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/lockfile-file": minor
|
||||
"@pnpm/lockfile-types": minor
|
||||
---
|
||||
|
||||
New optional property added to project snapshots: `dependenciesMeta`.
|
||||
7
.changeset/thin-dolphins-deny.md
Normal file
7
.changeset/thin-dolphins-deny.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/client": major
|
||||
"@pnpm/local-resolver": major
|
||||
"@pnpm/default-resolver": major
|
||||
---
|
||||
|
||||
Local directory dependencies are resolved to absolute path.
|
||||
5
.changeset/weak-buckets-shave.md
Normal file
5
.changeset/weak-buckets-shave.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/lockfile-utils": minor
|
||||
---
|
||||
|
||||
New utility function added: `extendProjectsWithTargetDirs()`.
|
||||
59
.changeset/wild-bags-refuse.md
Normal file
59
.changeset/wild-bags-refuse.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
"@pnpm/core": minor
|
||||
"@pnpm/headless": minor
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
New property supported via the `dependenciesMeta` field of `package.json`: `injected`. When `injected` is set to `true`, the package will be hard linked to `node_modules`, not symlinked [#3915](https://github.com/pnpm/pnpm/pull/3915).
|
||||
|
||||
For instance, the following `package.json` in a workspace will create a symlink to `bar` in the `node_modules` directory of `foo`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"dependencies": {
|
||||
"bar": "workspace:1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
But what if `bar` has `react` in its peer dependencies? If all projects in the monorepo use the same version of `react`, then no problem. But what if `bar` is required by `foo` that uses `react` 16 and `qar` with `react` 17? In the past, you'd have to choose a single version of react and install it as dev dependency of `bar`. But now with the `injected` field you can inject `bar` to a package, and `bar` will be installed with the `react` version of that package.
|
||||
|
||||
So this will be the `package.json` of `foo`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"dependencies": {
|
||||
"bar": "workspace:1.0.0",
|
||||
"react": "16"
|
||||
},
|
||||
"dependenciesMeta": {
|
||||
"bar": {
|
||||
"injected": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`bar` will be hard linked into the dependencies of `foo`, and `react` 16 will be linked to the dependencies of `foo/node_modules/bar`.
|
||||
|
||||
And this will be the `package.json` of `qar`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "qar",
|
||||
"dependencies": {
|
||||
"bar": "workspace:1.0.0",
|
||||
"react": "17"
|
||||
},
|
||||
"dependenciesMeta": {
|
||||
"bar": {
|
||||
"injected": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`bar` will be hard linked into the dependencies of `qar`, and `react` 17 will be linked to the dependencies of `qar/node_modules/bar`.
|
||||
@@ -32,6 +32,7 @@
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/master/packages/client#readme",
|
||||
"dependencies": {
|
||||
"@pnpm/default-resolver": "workspace:13.0.9",
|
||||
"@pnpm/directory-fetcher": "workspace:0.0.0",
|
||||
"@pnpm/fetch": "workspace:4.1.3",
|
||||
"@pnpm/fetching-types": "workspace:2.2.1",
|
||||
"@pnpm/git-fetcher": "workspace:4.1.6",
|
||||
|
||||
@@ -4,6 +4,7 @@ import createResolve, {
|
||||
} from '@pnpm/default-resolver'
|
||||
import { AgentOptions, createFetchFromRegistry } from '@pnpm/fetch'
|
||||
import { FetchFromRegistry, GetCredentials, RetryTimeoutOptions } from '@pnpm/fetching-types'
|
||||
import createDirectoryFetcher from '@pnpm/directory-fetcher'
|
||||
import fetchFromGit from '@pnpm/git-fetcher'
|
||||
import createTarballFetcher from '@pnpm/tarball-fetcher'
|
||||
import getCredentialsByURI from 'credentials-by-uri'
|
||||
@@ -43,5 +44,6 @@ function createFetchers (
|
||||
return {
|
||||
...createTarballFetcher(fetchFromRegistry, getCredentials, opts),
|
||||
...fetchFromGit(),
|
||||
...createDirectoryFetcher(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
{
|
||||
"path": "../default-resolver"
|
||||
},
|
||||
{
|
||||
"path": "../directory-fetcher"
|
||||
},
|
||||
{
|
||||
"path": "../fetch"
|
||||
},
|
||||
|
||||
@@ -68,6 +68,7 @@ function getWantedDependenciesFromGivenSet (
|
||||
return {
|
||||
alias,
|
||||
dev: depType === 'dev',
|
||||
injected: opts.dependenciesMeta[alias]?.injected,
|
||||
optional: depType === 'optional',
|
||||
nodeExecPath: opts.nodeExecPath ?? opts.dependenciesMeta[alias]?.node,
|
||||
pinnedVersion: guessPinnedVersionFromExistingSpec(deps[alias]),
|
||||
|
||||
@@ -21,11 +21,13 @@ import {
|
||||
import linkBins, { linkBinsOfPackages } from '@pnpm/link-bins'
|
||||
import {
|
||||
ProjectSnapshot,
|
||||
Lockfile,
|
||||
writeCurrentLockfile,
|
||||
writeLockfiles,
|
||||
writeWantedLockfile,
|
||||
} from '@pnpm/lockfile-file'
|
||||
import { writePnpFile } from '@pnpm/lockfile-to-pnp'
|
||||
import { extendProjectsWithTargetDirs } from '@pnpm/lockfile-utils'
|
||||
import logger, { streamParser } from '@pnpm/logger'
|
||||
import { getAllDependenciesFromManifest } from '@pnpm/manifest-utils'
|
||||
import { write as writeModulesYaml } from '@pnpm/modules-yaml'
|
||||
@@ -129,6 +131,14 @@ export async function install (
|
||||
return projects[0].manifest
|
||||
}
|
||||
|
||||
interface ProjectToBeInstalled {
|
||||
id: string
|
||||
buildIndex: number
|
||||
manifest: ProjectManifest
|
||||
modulesDir: string
|
||||
rootDir: string
|
||||
}
|
||||
|
||||
export type MutatedProject = ProjectOptions & DependenciesMutation
|
||||
|
||||
export async function mutateModules (
|
||||
@@ -310,13 +320,14 @@ export async function mutateModules (
|
||||
|
||||
const projectsToInstall = [] as ImporterToUpdate[]
|
||||
|
||||
const projectsToBeInstalled = ctx.projects.filter(({ mutation }) => mutation === 'install') as Array<{ buildIndex: number, rootDir: string, manifest: ProjectManifest, modulesDir: string }>
|
||||
const projectsToBeInstalled = ctx.projects.filter(({ mutation }) => mutation === 'install') as ProjectToBeInstalled[]
|
||||
const scriptsOpts: RunLifecycleHooksConcurrentlyOptions = {
|
||||
extraBinPaths: opts.extraBinPaths,
|
||||
rawConfig: opts.rawConfig,
|
||||
scriptShell: opts.scriptShell,
|
||||
shellEmulator: opts.shellEmulator,
|
||||
stdio: opts.ownLifecycleHooksStdio,
|
||||
storeController: opts.storeController,
|
||||
unsafePerm: opts.unsafePerm || false,
|
||||
}
|
||||
|
||||
@@ -480,14 +491,15 @@ export async function mutateModules (
|
||||
if (opts.enablePnp) {
|
||||
scriptsOpts.extraEnv = makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs'))
|
||||
}
|
||||
const projectsToBeBuilt = extendProjectsWithTargetDirs(projectsToBeInstalled, result.newLockfile, ctx)
|
||||
await runLifecycleHooksConcurrently(['preinstall', 'install', 'postinstall', 'prepare'],
|
||||
projectsToBeInstalled,
|
||||
projectsToBeBuilt,
|
||||
opts.childConcurrency,
|
||||
scriptsOpts
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
return result.projects
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +675,7 @@ type InstallFunction = (
|
||||
pruneVirtualStore: boolean
|
||||
currentLockfileIsUpToDate: boolean
|
||||
}
|
||||
) => Promise<Array<{ rootDir: string, manifest: ProjectManifest }>>
|
||||
) => Promise<{ projects: Array<{ rootDir: string, manifest: ProjectManifest }>, newLockfile: Lockfile }>
|
||||
|
||||
const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
if (opts.lockfileOnly && ctx.existsCurrentLockfile) {
|
||||
@@ -998,7 +1010,10 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
|
||||
await opts.storeController.close()
|
||||
|
||||
return projectsToResolve.map(({ manifest, rootDir }) => ({ rootDir, manifest }))
|
||||
return {
|
||||
newLockfile,
|
||||
projects: projectsToResolve.map(({ manifest, rootDir }) => ({ rootDir, manifest })),
|
||||
}
|
||||
}
|
||||
|
||||
const installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
|
||||
@@ -224,3 +224,94 @@ test('allProjectsAreUpToDate(): use link and registry version if linkWorkspacePa
|
||||
)
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test('allProjectsAreUpToDate(): returns false if dependenciesMeta differs', async () => {
|
||||
expect(await allProjectsAreUpToDate([
|
||||
{
|
||||
id: 'bar',
|
||||
manifest: {
|
||||
dependencies: {
|
||||
foo: 'workspace:../foo',
|
||||
},
|
||||
dependenciesMeta: {
|
||||
foo: {
|
||||
injected: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rootDir: 'bar',
|
||||
},
|
||||
{
|
||||
id: 'foo',
|
||||
manifest: fooManifest,
|
||||
rootDir: 'foo',
|
||||
},
|
||||
], {
|
||||
linkWorkspacePackages: true,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
bar: {
|
||||
dependencies: {
|
||||
foo: 'link:../foo',
|
||||
},
|
||||
specifiers: {
|
||||
foo: 'workspace:../foo',
|
||||
},
|
||||
},
|
||||
foo: {
|
||||
specifiers: {},
|
||||
},
|
||||
},
|
||||
lockfileVersion: 5,
|
||||
},
|
||||
workspacePackages,
|
||||
})).toBeFalsy()
|
||||
})
|
||||
|
||||
test('allProjectsAreUpToDate(): returns true if dependenciesMeta matches', async () => {
|
||||
expect(await allProjectsAreUpToDate([
|
||||
{
|
||||
id: 'bar',
|
||||
manifest: {
|
||||
dependencies: {
|
||||
foo: 'workspace:../foo',
|
||||
},
|
||||
dependenciesMeta: {
|
||||
foo: {
|
||||
injected: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rootDir: 'bar',
|
||||
},
|
||||
{
|
||||
id: 'foo',
|
||||
manifest: fooManifest,
|
||||
rootDir: 'foo',
|
||||
},
|
||||
], {
|
||||
linkWorkspacePackages: true,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
bar: {
|
||||
dependencies: {
|
||||
foo: 'link:../foo',
|
||||
},
|
||||
dependenciesMeta: {
|
||||
foo: {
|
||||
injected: true,
|
||||
},
|
||||
},
|
||||
specifiers: {
|
||||
foo: 'workspace:../foo',
|
||||
},
|
||||
},
|
||||
foo: {
|
||||
specifiers: {},
|
||||
},
|
||||
},
|
||||
lockfileVersion: 5,
|
||||
},
|
||||
workspacePackages,
|
||||
})).toBeTruthy()
|
||||
})
|
||||
|
||||
284
packages/core/test/install/injectLocalPackages.ts
Normal file
284
packages/core/test/install/injectLocalPackages.ts
Normal file
@@ -0,0 +1,284 @@
|
||||
import path from 'path'
|
||||
import assertProject from '@pnpm/assert-project'
|
||||
import { MutatedProject, mutateModules } from '@pnpm/core'
|
||||
import { preparePackages } from '@pnpm/prepare'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import pathExists from 'path-exists'
|
||||
import { testDefaults } from '../utils'
|
||||
|
||||
test('inject local packages', async () => {
|
||||
const project1Manifest = {
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
'dep-of-pkg-with-1-dep': '100.0.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
}
|
||||
const project2Manifest = {
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
'project-1': 'workspace:1.0.0',
|
||||
},
|
||||
dependenciesMeta: {
|
||||
'project-1': {
|
||||
injected: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
const projects = preparePackages([
|
||||
{
|
||||
location: 'project-1',
|
||||
package: project1Manifest,
|
||||
},
|
||||
{
|
||||
location: 'project-2',
|
||||
package: project2Manifest,
|
||||
},
|
||||
])
|
||||
|
||||
const importers: MutatedProject[] = [
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: project1Manifest,
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('project-1'),
|
||||
},
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: project2Manifest,
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('project-2'),
|
||||
},
|
||||
]
|
||||
const workspacePackages = {
|
||||
'project-1': {
|
||||
'1.0.0': {
|
||||
dir: path.resolve('project-1'),
|
||||
manifest: project1Manifest,
|
||||
},
|
||||
},
|
||||
'project-2': {
|
||||
'1.0.0': {
|
||||
dir: path.resolve('project-2'),
|
||||
manifest: project2Manifest,
|
||||
},
|
||||
},
|
||||
}
|
||||
await mutateModules(importers, await testDefaults({
|
||||
workspacePackages,
|
||||
}))
|
||||
|
||||
await projects['project-1'].has('is-negative')
|
||||
await projects['project-1'].has('dep-of-pkg-with-1-dep')
|
||||
await projects['project-1'].hasNot('is-positive')
|
||||
|
||||
await projects['project-2'].has('is-positive')
|
||||
await projects['project-2'].has('project-1')
|
||||
|
||||
const rootModules = assertProject(process.cwd())
|
||||
{
|
||||
const lockfile = await rootModules.readLockfile()
|
||||
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
|
||||
'project-1': {
|
||||
injected: true,
|
||||
},
|
||||
})
|
||||
expect(lockfile.packages['file:project-1_is-positive@1.0.0']).toEqual({
|
||||
resolution: {
|
||||
directory: 'project-1',
|
||||
type: 'directory',
|
||||
},
|
||||
id: 'file:project-1',
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
peerDependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
dev: false,
|
||||
})
|
||||
}
|
||||
|
||||
await rimraf('node_modules')
|
||||
await rimraf('project-1/node_modules')
|
||||
await rimraf('project-2/node_modules')
|
||||
|
||||
await mutateModules(importers, await testDefaults({
|
||||
frozenLockfile: true,
|
||||
workspacePackages,
|
||||
}))
|
||||
|
||||
await projects['project-1'].has('is-negative')
|
||||
await projects['project-1'].has('dep-of-pkg-with-1-dep')
|
||||
await projects['project-1'].hasNot('is-positive')
|
||||
|
||||
await projects['project-2'].has('is-positive')
|
||||
await projects['project-2'].has('project-1')
|
||||
|
||||
// The injected project is updated when on of its dependencies needs to be updated
|
||||
importers[0].manifest.dependencies!['is-negative'] = '2.0.0'
|
||||
await mutateModules(importers, await testDefaults({ workspacePackages }))
|
||||
{
|
||||
const lockfile = await rootModules.readLockfile()
|
||||
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
|
||||
'project-1': {
|
||||
injected: true,
|
||||
},
|
||||
})
|
||||
expect(lockfile.packages['file:project-1_is-positive@1.0.0']).toEqual({
|
||||
resolution: {
|
||||
directory: 'project-1',
|
||||
type: 'directory',
|
||||
},
|
||||
id: 'file:project-1',
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
peerDependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
dependencies: {
|
||||
'is-negative': '2.0.0',
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
dev: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test('inject local packages and relink them after build', async () => {
|
||||
const project1Manifest = {
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
'dep-of-pkg-with-1-dep': '100.0.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
scripts: {
|
||||
prepublishOnly: 'touch main.js',
|
||||
},
|
||||
}
|
||||
const project2Manifest = {
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
'project-1': 'workspace:1.0.0',
|
||||
},
|
||||
dependenciesMeta: {
|
||||
'project-1': {
|
||||
injected: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
const projects = preparePackages([
|
||||
{
|
||||
location: 'project-1',
|
||||
package: project1Manifest,
|
||||
},
|
||||
{
|
||||
location: 'project-2',
|
||||
package: project2Manifest,
|
||||
},
|
||||
])
|
||||
|
||||
const importers: MutatedProject[] = [
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: project1Manifest,
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('project-1'),
|
||||
},
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: project2Manifest,
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('project-2'),
|
||||
},
|
||||
]
|
||||
const workspacePackages = {
|
||||
'project-1': {
|
||||
'1.0.0': {
|
||||
dir: path.resolve('project-1'),
|
||||
manifest: project1Manifest,
|
||||
},
|
||||
},
|
||||
'project-2': {
|
||||
'1.0.0': {
|
||||
dir: path.resolve('project-2'),
|
||||
manifest: project2Manifest,
|
||||
},
|
||||
},
|
||||
}
|
||||
await mutateModules(importers, await testDefaults({
|
||||
workspacePackages,
|
||||
}))
|
||||
|
||||
await projects['project-1'].has('is-negative')
|
||||
await projects['project-1'].has('dep-of-pkg-with-1-dep')
|
||||
await projects['project-1'].hasNot('is-positive')
|
||||
|
||||
await projects['project-2'].has('is-positive')
|
||||
await projects['project-2'].has('project-1')
|
||||
|
||||
expect(await pathExists(path.resolve('project-2/node_modules/project-1/main.js'))).toBeTruthy()
|
||||
|
||||
const rootModules = assertProject(process.cwd())
|
||||
const lockfile = await rootModules.readLockfile()
|
||||
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
|
||||
'project-1': {
|
||||
injected: true,
|
||||
},
|
||||
})
|
||||
expect(lockfile.packages['file:project-1_is-positive@1.0.0']).toEqual({
|
||||
resolution: {
|
||||
directory: 'project-1',
|
||||
type: 'directory',
|
||||
},
|
||||
id: 'file:project-1',
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
peerDependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
dev: false,
|
||||
})
|
||||
|
||||
await rimraf('node_modules')
|
||||
await rimraf('project-1/main.js')
|
||||
await rimraf('project-1/node_modules')
|
||||
await rimraf('project-2/node_modules')
|
||||
|
||||
await mutateModules(importers, await testDefaults({
|
||||
frozenLockfile: true,
|
||||
workspacePackages,
|
||||
}))
|
||||
|
||||
await projects['project-1'].has('is-negative')
|
||||
await projects['project-1'].has('dep-of-pkg-with-1-dep')
|
||||
await projects['project-1'].hasNot('is-positive')
|
||||
|
||||
await projects['project-2'].has('is-positive')
|
||||
await projects['project-2'].has('project-1')
|
||||
|
||||
expect(await pathExists(path.resolve('project-2/node_modules/project-1/main.js'))).toBeTruthy()
|
||||
})
|
||||
15
packages/directory-fetcher/README.md
Normal file
15
packages/directory-fetcher/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# @pnpm/directory-fetcher
|
||||
|
||||
> Fetcher for local directory packages
|
||||
|
||||
[](https://www.npmjs.com/package/@pnpm/directory-fetcher)
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
<pnpm|npm|yarn> add @pnpm/directory-fetcher
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
1
packages/directory-fetcher/jest.config.js
Normal file
1
packages/directory-fetcher/jest.config.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('../../jest.config.js')
|
||||
40
packages/directory-fetcher/package.json
Normal file
40
packages/directory-fetcher/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@pnpm/directory-fetcher",
|
||||
"version": "0.0.0",
|
||||
"description": "A fetcher for local directory packages",
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"scripts": {
|
||||
"_test": "jest",
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"lint": "eslint src/**/*.ts test/**/*.ts",
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"compile": "rimraf lib tsconfig.tsbuildinfo && tsc --build && pnpm run lint -- --fix"
|
||||
},
|
||||
"repository": "https://github.com/pnpm/pnpm/blob/master/packages/directory-fetcher",
|
||||
"engines": {
|
||||
"node": ">=12.17"
|
||||
},
|
||||
"keywords": [
|
||||
"pnpm6",
|
||||
"pnpm",
|
||||
"fetcher"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/master/packages/directory-fetcher#readme",
|
||||
"dependencies": {
|
||||
"@pnpm/fetcher-base": "workspace:11.0.3",
|
||||
"@pnpm/resolver-base": "workspace:8.0.4",
|
||||
"load-json-file": "^6.2.0",
|
||||
"npm-packlist": "^2.2.2",
|
||||
"ramda": "^0.27.1"
|
||||
}
|
||||
}
|
||||
36
packages/directory-fetcher/src/index.ts
Normal file
36
packages/directory-fetcher/src/index.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import path from 'path'
|
||||
import { Cafs, DeferredManifestPromise } from '@pnpm/fetcher-base'
|
||||
import { DirectoryResolution } from '@pnpm/resolver-base'
|
||||
import fromPairs from 'ramda/src/fromPairs'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import packlist from 'npm-packlist'
|
||||
|
||||
export interface DirectoryFetcherOptions {
|
||||
manifest?: DeferredManifestPromise
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return {
|
||||
directory: (
|
||||
cafs: Cafs,
|
||||
resolution: DirectoryResolution,
|
||||
opts: DirectoryFetcherOptions
|
||||
) => fetchFromDir(resolution.directory, opts),
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchFromDir (
|
||||
dir: string,
|
||||
opts: DirectoryFetcherOptions
|
||||
) {
|
||||
const files = await packlist({ path: dir })
|
||||
const filesIndex: Record<string, string> = fromPairs(files.map((file) => [file, path.join(dir, file)]))
|
||||
if (opts.manifest) {
|
||||
opts.manifest.resolve(await loadJsonFile(path.join(dir, 'package.json')))
|
||||
}
|
||||
return {
|
||||
local: true as const,
|
||||
filesIndex,
|
||||
packageImportMethod: 'hardlink' as const,
|
||||
}
|
||||
}
|
||||
23
packages/directory-fetcher/test/index.ts
Normal file
23
packages/directory-fetcher/test/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/// <reference path="../../../typings/index.d.ts"/>
|
||||
import path from 'path'
|
||||
import createFetcher from '@pnpm/directory-fetcher'
|
||||
|
||||
test('fetch', async () => {
|
||||
const fetcher = createFetcher()
|
||||
|
||||
// eslint-disable-next-line
|
||||
const fetchResult = await fetcher.directory({} as any, { directory: path.join(__dirname, '..'), type: 'directory' }, {})
|
||||
|
||||
expect(fetchResult.local).toBe(true)
|
||||
expect(fetchResult.packageImportMethod).toBe('hardlink')
|
||||
expect(fetchResult.filesIndex['package.json']).toBe(path.join(__dirname, '../package.json'))
|
||||
|
||||
// Only those files are included which would get published
|
||||
expect(Object.keys(fetchResult.filesIndex).sort()).toStrictEqual([
|
||||
'README.md',
|
||||
'lib/index.d.ts',
|
||||
'lib/index.js',
|
||||
'lib/index.js.map',
|
||||
'package.json',
|
||||
])
|
||||
})
|
||||
19
packages/directory-fetcher/tsconfig.json
Normal file
19
packages/directory-fetcher/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../typings/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../fetcher-base"
|
||||
},
|
||||
{
|
||||
"path": "../resolver-base"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
packages/directory-fetcher/tsconfig.lint.json
Normal file
8
packages/directory-fetcher/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../typings/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -9,11 +9,17 @@ export interface PackageFileInfo {
|
||||
size: number
|
||||
}
|
||||
|
||||
export interface PackageFilesResponse {
|
||||
export type PackageFilesResponse = {
|
||||
fromStore: boolean
|
||||
filesIndex: Record<string, PackageFileInfo>
|
||||
packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone'
|
||||
sideEffects?: Record<string, Record<string, PackageFileInfo>>
|
||||
}
|
||||
} & ({
|
||||
local: true
|
||||
filesIndex: Record<string, string>
|
||||
} | {
|
||||
local?: false
|
||||
filesIndex: Record<string, PackageFileInfo>
|
||||
})
|
||||
|
||||
export type ImportPackageFunction = (
|
||||
to: string,
|
||||
@@ -49,8 +55,12 @@ export type FetchFunction = (
|
||||
opts: FetchOptions
|
||||
) => Promise<FetchResult>
|
||||
|
||||
export interface FetchResult {
|
||||
export type FetchResult = {
|
||||
local?: false
|
||||
filesIndex: FilesIndex
|
||||
} | {
|
||||
local: true
|
||||
filesIndex: Record<string, string>
|
||||
}
|
||||
|
||||
export interface FileWriteResult {
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
} from '@pnpm/lockfile-file'
|
||||
import { writePnpFile } from '@pnpm/lockfile-to-pnp'
|
||||
import {
|
||||
extendProjectsWithTargetDirs,
|
||||
nameVerFromPkgSnapshot,
|
||||
packageIdFromSnapshot,
|
||||
pkgSnapshotToResolution,
|
||||
@@ -165,6 +166,7 @@ export default async (opts: HeadlessOptions) => {
|
||||
scriptShell: opts.scriptShell,
|
||||
shellEmulator: opts.shellEmulator,
|
||||
stdio: opts.ownLifecycleHooksStdio ?? 'inherit',
|
||||
storeController: opts.storeController,
|
||||
unsafePerm: opts.unsafePerm || false,
|
||||
}
|
||||
|
||||
@@ -431,9 +433,13 @@ export default async (opts: HeadlessOptions) => {
|
||||
await opts.storeController.close()
|
||||
|
||||
if (!opts.ignoreScripts && !opts.ignorePackageManifest) {
|
||||
const projectsToBeBuilt = extendProjectsWithTargetDirs(opts.projects, wantedLockfile, {
|
||||
lockfileDir: opts.lockfileDir,
|
||||
virtualStoreDir,
|
||||
})
|
||||
await runLifecycleHooksConcurrently(
|
||||
['preinstall', 'install', 'postinstall', 'prepublish', 'prepare'],
|
||||
opts.projects,
|
||||
['preinstall', 'install', 'postinstall', 'prepare'],
|
||||
projectsToBeBuilt,
|
||||
opts.childConcurrency ?? 5,
|
||||
scriptsOpts
|
||||
)
|
||||
|
||||
@@ -36,8 +36,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/core-loggers": "workspace:6.0.4",
|
||||
"@pnpm/directory-fetcher": "workspace:0.0.0",
|
||||
"@pnpm/npm-lifecycle": "^1.0.0",
|
||||
"@pnpm/read-package-json": "workspace:5.0.4",
|
||||
"@pnpm/store-controller-types": "workspace:11.0.5",
|
||||
"@pnpm/types": "workspace:7.4.0",
|
||||
"path-exists": "^4.0.0",
|
||||
"run-groups": "^3.0.1"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { fetchFromDir } from '@pnpm/directory-fetcher'
|
||||
import { StoreController } from '@pnpm/store-controller-types'
|
||||
import { ProjectManifest } from '@pnpm/types'
|
||||
import runGroups from 'run-groups'
|
||||
import runLifecycleHook, { RunLifecycleHookOptions } from './runLifecycleHook'
|
||||
@@ -6,15 +8,26 @@ export type RunLifecycleHooksConcurrentlyOptions = Omit<RunLifecycleHookOptions,
|
||||
| 'depPath'
|
||||
| 'pkgRoot'
|
||||
| 'rootModulesDir'
|
||||
>
|
||||
> & {
|
||||
storeController: StoreController
|
||||
}
|
||||
|
||||
export interface Importer {
|
||||
buildIndex: number
|
||||
manifest: ProjectManifest
|
||||
rootDir: string
|
||||
modulesDir: string
|
||||
stages?: string[]
|
||||
targetDirs?: string[]
|
||||
}
|
||||
|
||||
export default async function runLifecycleHooksConcurrently (
|
||||
stages: string[],
|
||||
importers: Array<{ buildIndex: number, manifest: ProjectManifest, rootDir: string, modulesDir: string }>,
|
||||
importers: Importer[],
|
||||
childConcurrency: number,
|
||||
opts: RunLifecycleHooksConcurrentlyOptions
|
||||
) {
|
||||
const importersByBuildIndex = new Map<number, Array<{ rootDir: string, manifest: ProjectManifest, modulesDir: string }>>()
|
||||
const importersByBuildIndex = new Map<number, Importer[]>()
|
||||
for (const importer of importers) {
|
||||
if (!importersByBuildIndex.has(importer.buildIndex)) {
|
||||
importersByBuildIndex.set(importer.buildIndex, [importer])
|
||||
@@ -24,8 +37,8 @@ export default async function runLifecycleHooksConcurrently (
|
||||
}
|
||||
const sortedBuildIndexes = Array.from(importersByBuildIndex.keys()).sort()
|
||||
const groups = sortedBuildIndexes.map((buildIndex) => {
|
||||
const importers = importersByBuildIndex.get(buildIndex) as Array<{ rootDir: string, manifest: ProjectManifest, modulesDir: string }>
|
||||
return importers.map(({ manifest, modulesDir, rootDir }) =>
|
||||
const importers = importersByBuildIndex.get(buildIndex)!
|
||||
return importers.map(({ manifest, modulesDir, rootDir, stages: importerStages, targetDirs }) =>
|
||||
async () => {
|
||||
const runLifecycleHookOpts = {
|
||||
...opts,
|
||||
@@ -33,10 +46,21 @@ export default async function runLifecycleHooksConcurrently (
|
||||
pkgRoot: rootDir,
|
||||
rootModulesDir: modulesDir,
|
||||
}
|
||||
for (const stage of stages) {
|
||||
for (const stage of (importerStages ?? stages)) {
|
||||
if ((manifest.scripts == null) || !manifest.scripts[stage]) continue
|
||||
await runLifecycleHook(stage, manifest, runLifecycleHookOpts)
|
||||
}
|
||||
if (targetDirs == null) return
|
||||
const filesResponse = await fetchFromDir(rootDir, {})
|
||||
await Promise.all(
|
||||
targetDirs.map((targetDir) => opts.storeController.importPackage(targetDir, {
|
||||
filesResponse: {
|
||||
fromStore: false,
|
||||
...filesResponse,
|
||||
},
|
||||
force: false,
|
||||
}))
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -12,9 +12,15 @@
|
||||
{
|
||||
"path": "../core-loggers"
|
||||
},
|
||||
{
|
||||
"path": "../directory-fetcher"
|
||||
},
|
||||
{
|
||||
"path": "../read-package-json"
|
||||
},
|
||||
{
|
||||
"path": "../store-controller-types"
|
||||
},
|
||||
{
|
||||
"path": "../types"
|
||||
}
|
||||
|
||||
@@ -10,13 +10,15 @@ import {
|
||||
} from '@pnpm/resolver-base'
|
||||
import { DependencyManifest } from '@pnpm/types'
|
||||
import ssri from 'ssri'
|
||||
import parsePref from './parsePref'
|
||||
import parsePref, { WantedLocalDependency } from './parsePref'
|
||||
|
||||
export { WantedLocalDependency }
|
||||
|
||||
/**
|
||||
* Resolves a package hosted on the local filesystem
|
||||
*/
|
||||
export default async function resolveLocal (
|
||||
wantedDependency: {pref: string},
|
||||
wantedDependency: WantedLocalDependency,
|
||||
opts: {
|
||||
lockfileDir?: string
|
||||
projectDir: string
|
||||
@@ -34,7 +36,7 @@ export default async function resolveLocal (
|
||||
)
|
||||
) | null
|
||||
> {
|
||||
const spec = parsePref(wantedDependency.pref, opts.projectDir, opts.lockfileDir ?? opts.projectDir)
|
||||
const spec = parsePref(wantedDependency, opts.projectDir, opts.lockfileDir ?? opts.projectDir)
|
||||
if (spec == null) return null
|
||||
if (spec.type === 'file') {
|
||||
return {
|
||||
|
||||
@@ -17,29 +17,34 @@ export interface LocalPackageSpec {
|
||||
normalizedPref: string
|
||||
}
|
||||
|
||||
export interface WantedLocalDependency {
|
||||
pref: string
|
||||
injected?: boolean
|
||||
}
|
||||
|
||||
export default function parsePref (
|
||||
pref: string,
|
||||
wd: WantedLocalDependency,
|
||||
projectDir: string,
|
||||
lockfileDir: string
|
||||
): LocalPackageSpec | null {
|
||||
if (pref.startsWith('link:') || pref.startsWith('workspace:')) {
|
||||
return fromLocal(pref, projectDir, lockfileDir, 'directory')
|
||||
if (wd.pref.startsWith('link:') || wd.pref.startsWith('workspace:')) {
|
||||
return fromLocal(wd, projectDir, lockfileDir, 'directory')
|
||||
}
|
||||
if (pref.endsWith('.tgz') ||
|
||||
pref.endsWith('.tar.gz') ||
|
||||
pref.endsWith('.tar') ||
|
||||
pref.includes(path.sep) ||
|
||||
pref.startsWith('file:') ||
|
||||
isFilespec.test(pref)
|
||||
if (wd.pref.endsWith('.tgz') ||
|
||||
wd.pref.endsWith('.tar.gz') ||
|
||||
wd.pref.endsWith('.tar') ||
|
||||
wd.pref.includes(path.sep) ||
|
||||
wd.pref.startsWith('file:') ||
|
||||
isFilespec.test(wd.pref)
|
||||
) {
|
||||
const type = isFilename.test(pref) ? 'file' : 'directory'
|
||||
return fromLocal(pref, projectDir, lockfileDir, type)
|
||||
const type = isFilename.test(wd.pref) ? 'file' : 'directory'
|
||||
return fromLocal(wd, projectDir, lockfileDir, type)
|
||||
}
|
||||
if (pref.startsWith('path:')) {
|
||||
if (wd.pref.startsWith('path:')) {
|
||||
const err = new PnpmError('PATH_IS_UNSUPPORTED_PROTOCOL', 'Local dependencies via `path:` protocol are not supported. ' +
|
||||
'Use the `link:` protocol for folder dependencies and `file:` for local tarballs')
|
||||
/* eslint-disable @typescript-eslint/dot-notation */
|
||||
err['pref'] = pref
|
||||
err['pref'] = wd.pref
|
||||
err['protocol'] = 'path:'
|
||||
/* eslint-enable @typescript-eslint/dot-notation */
|
||||
throw err
|
||||
@@ -48,7 +53,7 @@ export default function parsePref (
|
||||
}
|
||||
|
||||
function fromLocal (
|
||||
pref: string,
|
||||
{ pref, injected }: WantedLocalDependency,
|
||||
projectDir: string,
|
||||
lockfileDir: string,
|
||||
type: 'file' | 'directory'
|
||||
@@ -57,7 +62,7 @@ function fromLocal (
|
||||
.replace(/^(file|link|workspace):[/]*([A-Za-z]:)/, '$2') // drive name paths on windows
|
||||
.replace(/^(file|link|workspace):(?:[/]*([~./]))?/, '$2')
|
||||
|
||||
const protocol = type === 'directory' ? 'link:' : 'file:'
|
||||
const protocol = type === 'directory' && !injected ? 'link:' : 'file:'
|
||||
let fetchSpec!: string
|
||||
let normalizedPref!: string
|
||||
if (/^~[/]/.test(spec)) {
|
||||
@@ -73,9 +78,9 @@ function fromLocal (
|
||||
}
|
||||
}
|
||||
|
||||
const dependencyPath = normalize(path.relative(projectDir, fetchSpec))
|
||||
const id = type === 'directory' || projectDir === lockfileDir
|
||||
? `${protocol}${dependencyPath}`
|
||||
const dependencyPath = normalize(path.resolve(fetchSpec))
|
||||
const id = !injected && (type === 'directory' || projectDir === lockfileDir)
|
||||
? `${protocol}${normalize(path.relative(projectDir, fetchSpec))}`
|
||||
: `${protocol}${normalize(path.relative(lockfileDir, fetchSpec))}`
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
/// <reference path="../../../typings/index.d.ts"/>
|
||||
import path from 'path'
|
||||
import resolveFromLocal from '@pnpm/local-resolver'
|
||||
import normalize from 'normalize-path'
|
||||
|
||||
test('resolve directory', async () => {
|
||||
const resolveResult = await resolveFromLocal({ pref: '..' }, { projectDir: __dirname })
|
||||
expect(resolveResult!.id).toEqual('link:..')
|
||||
expect(resolveResult!.normalizedPref).toEqual('link:..')
|
||||
expect(resolveResult!['manifest']!.name).toEqual('@pnpm/local-resolver')
|
||||
expect(resolveResult!.resolution['directory']).toEqual('..')
|
||||
expect(resolveResult!.resolution['directory']).toEqual(normalize(path.join(__dirname, '..')))
|
||||
expect(resolveResult!.resolution['type']).toEqual('directory')
|
||||
})
|
||||
|
||||
test('resolve injected directory', async () => {
|
||||
const resolveResult = await resolveFromLocal({ injected: true, pref: '..' }, { projectDir: __dirname })
|
||||
expect(resolveResult!.id).toEqual('file:..')
|
||||
expect(resolveResult!.normalizedPref).toEqual('file:..')
|
||||
expect(resolveResult!['manifest']!.name).toEqual('@pnpm/local-resolver')
|
||||
expect(resolveResult!.resolution['directory']).toEqual(normalize(path.join(__dirname, '..')))
|
||||
expect(resolveResult!.resolution['type']).toEqual('directory')
|
||||
})
|
||||
|
||||
@@ -16,7 +26,7 @@ test('resolve workspace directory', async () => {
|
||||
expect(resolveResult!.id).toEqual('link:..')
|
||||
expect(resolveResult!.normalizedPref).toEqual('link:..')
|
||||
expect(resolveResult!['manifest']!.name).toEqual('@pnpm/local-resolver')
|
||||
expect(resolveResult!.resolution['directory']).toEqual('..')
|
||||
expect(resolveResult!.resolution['directory']).toEqual(normalize(path.join(__dirname, '..')))
|
||||
expect(resolveResult!.resolution['type']).toEqual('directory')
|
||||
})
|
||||
|
||||
@@ -25,7 +35,7 @@ test('resolve directory specified using the file: protocol', async () => {
|
||||
expect(resolveResult!.id).toEqual('link:..')
|
||||
expect(resolveResult!.normalizedPref).toEqual('link:..')
|
||||
expect(resolveResult!['manifest']!.name).toEqual('@pnpm/local-resolver')
|
||||
expect(resolveResult!.resolution['directory']).toEqual('..')
|
||||
expect(resolveResult!.resolution['directory']).toEqual(normalize(path.join(__dirname, '..')))
|
||||
expect(resolveResult!.resolution['type']).toEqual('directory')
|
||||
})
|
||||
|
||||
@@ -34,7 +44,7 @@ test('resolve directoty specified using the link: protocol', async () => {
|
||||
expect(resolveResult!.id).toEqual('link:..')
|
||||
expect(resolveResult!.normalizedPref).toEqual('link:..')
|
||||
expect(resolveResult!['manifest']!.name).toEqual('@pnpm/local-resolver')
|
||||
expect(resolveResult!.resolution['directory']).toEqual('..')
|
||||
expect(resolveResult!.resolution['directory']).toEqual(normalize(path.join(__dirname, '..')))
|
||||
expect(resolveResult!.resolution['type']).toEqual('directory')
|
||||
})
|
||||
|
||||
|
||||
@@ -100,6 +100,9 @@ export function normalizeLockfile (lockfile: Lockfile, forceSharedFormat: boolea
|
||||
const normalizedImporter = {
|
||||
specifiers: importer.specifiers ?? {},
|
||||
}
|
||||
if (importer.dependenciesMeta != null && !isEmpty(importer.dependenciesMeta)) {
|
||||
normalizedImporter['dependenciesMeta'] = importer.dependenciesMeta
|
||||
}
|
||||
for (const depType of DEPENDENCIES_FIELDS) {
|
||||
if (!isEmpty(importer[depType] ?? {})) {
|
||||
normalizedImporter[depType] = importer[depType]
|
||||
|
||||
@@ -27,5 +27,8 @@
|
||||
"compile": "rimraf lib tsconfig.tsbuildinfo && tsc --build && pnpm run lint -- --fix",
|
||||
"prepublishOnly": "pnpm run compile"
|
||||
},
|
||||
"funding": "https://opencollective.com/pnpm"
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"dependencies": {
|
||||
"@pnpm/types": "workspace:7.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DependenciesMeta } from '@pnpm/types'
|
||||
|
||||
export interface Lockfile {
|
||||
importers: Record<string, ProjectSnapshot>
|
||||
lockfileVersion: number
|
||||
@@ -12,6 +14,7 @@ export interface ProjectSnapshot {
|
||||
dependencies?: ResolvedDependencies
|
||||
optionalDependencies?: ResolvedDependencies
|
||||
devDependencies?: ResolvedDependencies
|
||||
dependenciesMeta?: DependenciesMeta
|
||||
}
|
||||
|
||||
export interface PackageSnapshots {
|
||||
|
||||
@@ -8,5 +8,9 @@
|
||||
"src/**/*.ts",
|
||||
"../../typings/**/*.d.ts"
|
||||
],
|
||||
"references": []
|
||||
"references": [
|
||||
{
|
||||
"path": "../types"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
27
packages/lockfile-utils/src/extendProjectsWithTargetDirs.ts
Normal file
27
packages/lockfile-utils/src/extendProjectsWithTargetDirs.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import path from 'path'
|
||||
import { Lockfile } from '@pnpm/lockfile-types'
|
||||
import { depPathToFilename } from 'dependency-path'
|
||||
import fromPairs from 'ramda/src/fromPairs'
|
||||
|
||||
export default function extendProjectsWithTargetDirs<T> (
|
||||
projects: Array<T & { id: string }>,
|
||||
lockfile: Lockfile,
|
||||
ctx: {
|
||||
lockfileDir: string
|
||||
virtualStoreDir: string
|
||||
}
|
||||
) {
|
||||
const projectsById: Record<string, T & { targetDirs: string[], stages?: string[] }> =
|
||||
fromPairs(projects.map((project) => [project.id, { ...project, targetDirs: [] as string[] }]))
|
||||
Object.entries(lockfile.packages ?? {})
|
||||
.forEach(([depPath, pkg]) => {
|
||||
if (pkg.resolution?.['type'] !== 'directory') return
|
||||
const pkgId = pkg.id ?? depPath
|
||||
const importerId = pkgId.replace(/^file:/, '')
|
||||
if (projectsById[importerId] == null) return
|
||||
const localLocation = path.join(ctx.virtualStoreDir, depPathToFilename(depPath, ctx.lockfileDir), 'node_modules', pkg.name!)
|
||||
projectsById[importerId].targetDirs.push(localLocation)
|
||||
projectsById[importerId].stages = ['preinstall', 'install', 'postinstall', 'prepare', 'prepublishOnly']
|
||||
})
|
||||
return Object.values(projectsById)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { refToRelative } from 'dependency-path'
|
||||
import extendProjectsWithTargetDirs from './extendProjectsWithTargetDirs'
|
||||
import nameVerFromPkgSnapshot from './nameVerFromPkgSnapshot'
|
||||
import packageIdFromSnapshot from './packageIdFromSnapshot'
|
||||
import packageIsIndependent from './packageIsIndependent'
|
||||
@@ -8,6 +9,7 @@ import satisfiesPackageManifest from './satisfiesPackageManifest'
|
||||
export * from '@pnpm/lockfile-types'
|
||||
|
||||
export {
|
||||
extendProjectsWithTargetDirs,
|
||||
nameVerFromPkgSnapshot,
|
||||
packageIdFromSnapshot,
|
||||
packageIsIndependent,
|
||||
|
||||
@@ -11,6 +11,7 @@ export default (lockfile: Lockfile, pkg: ProjectManifest, importerId: string) =>
|
||||
if (!equals({ ...pkg.devDependencies, ...pkg.dependencies, ...pkg.optionalDependencies }, importer.specifiers)) {
|
||||
return false
|
||||
}
|
||||
if (!equals(pkg.dependenciesMeta, importer.dependenciesMeta)) return false
|
||||
for (const depField of DEPENDENCIES_FIELDS) {
|
||||
const importerDeps = importer[depField] ?? {}
|
||||
const pkgDeps = pkg[depField] ?? {}
|
||||
|
||||
@@ -101,6 +101,7 @@ export type ResolveFromNpmOptions = {
|
||||
alwaysTryWorkspacePackages?: boolean
|
||||
defaultTag?: string
|
||||
dryRun?: boolean
|
||||
lockfileDir?: string
|
||||
registry: string
|
||||
preferredVersions?: PreferredVersions
|
||||
preferWorkspacePackages?: boolean
|
||||
@@ -125,6 +126,7 @@ async function resolveNpm (
|
||||
if (wantedDependency.pref.startsWith('workspace:.')) return null
|
||||
const resolvedFromWorkspace = tryResolveFromWorkspace(wantedDependency, {
|
||||
defaultTag,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
projectDir: opts.projectDir,
|
||||
registry: opts.registry,
|
||||
workspacePackages: opts.workspacePackages,
|
||||
@@ -150,7 +152,11 @@ async function resolveNpm (
|
||||
})
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
if ((workspacePackages != null) && opts.projectDir) {
|
||||
const resolvedFromLocal = tryResolveFromWorkspacePackages(workspacePackages, spec, opts.projectDir)
|
||||
const resolvedFromLocal = tryResolveFromWorkspacePackages(workspacePackages, spec, {
|
||||
projectDir: opts.projectDir,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
hardLinkLocalPackages: wantedDependency.injected,
|
||||
})
|
||||
if (resolvedFromLocal != null) return resolvedFromLocal
|
||||
}
|
||||
throw err
|
||||
@@ -159,7 +165,11 @@ async function resolveNpm (
|
||||
const meta = pickResult.meta
|
||||
if (pickedPackage == null) {
|
||||
if ((workspacePackages != null) && opts.projectDir) {
|
||||
const resolvedFromLocal = tryResolveFromWorkspacePackages(workspacePackages, spec, opts.projectDir)
|
||||
const resolvedFromLocal = tryResolveFromWorkspacePackages(workspacePackages, spec, {
|
||||
projectDir: opts.projectDir,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
hardLinkLocalPackages: wantedDependency.injected,
|
||||
})
|
||||
if (resolvedFromLocal != null) return resolvedFromLocal
|
||||
}
|
||||
throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta })
|
||||
@@ -168,14 +178,22 @@ async function resolveNpm (
|
||||
if (((workspacePackages?.[pickedPackage.name]) != null) && opts.projectDir) {
|
||||
if (workspacePackages[pickedPackage.name][pickedPackage.version]) {
|
||||
return {
|
||||
...resolveFromLocalPackage(workspacePackages[pickedPackage.name][pickedPackage.version], spec.normalizedPref, opts.projectDir),
|
||||
...resolveFromLocalPackage(workspacePackages[pickedPackage.name][pickedPackage.version], spec.normalizedPref, {
|
||||
projectDir: opts.projectDir,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
hardLinkLocalPackages: wantedDependency.injected,
|
||||
}),
|
||||
latest: meta['dist-tags'].latest,
|
||||
}
|
||||
}
|
||||
const localVersion = pickMatchingLocalVersionOrNull(workspacePackages[pickedPackage.name], spec)
|
||||
if (localVersion && (semver.gt(localVersion, pickedPackage.version) || opts.preferWorkspacePackages)) {
|
||||
return {
|
||||
...resolveFromLocalPackage(workspacePackages[pickedPackage.name][localVersion], spec.normalizedPref, opts.projectDir),
|
||||
...resolveFromLocalPackage(workspacePackages[pickedPackage.name][localVersion], spec.normalizedPref, {
|
||||
projectDir: opts.projectDir,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
hardLinkLocalPackages: wantedDependency.injected,
|
||||
}),
|
||||
latest: meta['dist-tags'].latest,
|
||||
}
|
||||
}
|
||||
@@ -201,6 +219,7 @@ function tryResolveFromWorkspace (
|
||||
wantedDependency: WantedDependency,
|
||||
opts: {
|
||||
defaultTag: string
|
||||
lockfileDir?: string
|
||||
projectDir?: string
|
||||
registry: string
|
||||
workspacePackages?: WorkspacePackages
|
||||
@@ -219,7 +238,11 @@ function tryResolveFromWorkspace (
|
||||
if (!opts.projectDir) {
|
||||
throw new Error('Cannot resolve package from workspace because opts.projectDir is not defined')
|
||||
}
|
||||
const resolvedFromLocal = tryResolveFromWorkspacePackages(opts.workspacePackages, spec, opts.projectDir)
|
||||
const resolvedFromLocal = tryResolveFromWorkspacePackages(opts.workspacePackages, spec, {
|
||||
projectDir: opts.projectDir,
|
||||
hardLinkLocalPackages: wantedDependency.injected,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
})
|
||||
if (resolvedFromLocal == null) {
|
||||
throw new PnpmError(
|
||||
'NO_MATCHING_VERSION_INSIDE_WORKSPACE',
|
||||
@@ -232,12 +255,16 @@ function tryResolveFromWorkspace (
|
||||
function tryResolveFromWorkspacePackages (
|
||||
workspacePackages: WorkspacePackages,
|
||||
spec: RegistryPackageSpec,
|
||||
projectDir: string
|
||||
opts: {
|
||||
hardLinkLocalPackages?: boolean
|
||||
projectDir: string
|
||||
lockfileDir?: string
|
||||
}
|
||||
) {
|
||||
if (!workspacePackages[spec.name]) return null
|
||||
const localVersion = pickMatchingLocalVersionOrNull(workspacePackages[spec.name], spec)
|
||||
if (!localVersion) return null
|
||||
return resolveFromLocalPackage(workspacePackages[spec.name][localVersion], spec.normalizedPref, projectDir)
|
||||
return resolveFromLocalPackage(workspacePackages[spec.name][localVersion], spec.normalizedPref, opts)
|
||||
}
|
||||
|
||||
function pickMatchingLocalVersionOrNull (
|
||||
@@ -268,10 +295,16 @@ function resolveFromLocalPackage (
|
||||
manifest: DependencyManifest
|
||||
},
|
||||
normalizedPref: string | undefined,
|
||||
projectDir: string
|
||||
opts: {
|
||||
hardLinkLocalPackages?: boolean
|
||||
projectDir: string
|
||||
lockfileDir?: string
|
||||
}
|
||||
) {
|
||||
return {
|
||||
id: `link:${normalize(path.relative(projectDir, localPackage.dir))}`,
|
||||
id: opts.hardLinkLocalPackages
|
||||
? `file:${normalize(path.relative(opts.lockfileDir!, localPackage.dir))}`
|
||||
: `link:${normalize(path.relative(opts.projectDir, localPackage.dir))}`,
|
||||
manifest: localPackage.manifest,
|
||||
normalizedPref,
|
||||
resolution: {
|
||||
|
||||
@@ -944,6 +944,44 @@ test('resolve from local directory when it matches the latest version of the pac
|
||||
expect(resolveResult!.manifest!.version).toBe('1.0.0')
|
||||
})
|
||||
|
||||
test('resolve injected dependency from local directory when it matches the latest version of the package', async () => {
|
||||
nock(registry)
|
||||
.get('/is-positive')
|
||||
.reply(200, isPositiveMeta)
|
||||
|
||||
const cacheDir = tempy.directory()
|
||||
const resolve = createResolveFromNpm({
|
||||
cacheDir,
|
||||
})
|
||||
const resolveResult = await resolve({ alias: 'is-positive', injected: true, pref: '1.0.0' }, {
|
||||
projectDir: '/home/istvan/src',
|
||||
lockfileDir: '/home/istvan/src',
|
||||
registry,
|
||||
workspacePackages: {
|
||||
'is-positive': {
|
||||
'1.0.0': {
|
||||
dir: '/home/istvan/src/is-positive',
|
||||
manifest: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(resolveResult!.resolvedVia).toBe('local-filesystem')
|
||||
expect(resolveResult!.id).toBe('file:is-positive')
|
||||
expect(resolveResult!.latest!.split('.').length).toBe(3)
|
||||
expect(resolveResult!.resolution).toStrictEqual({
|
||||
directory: '/home/istvan/src/is-positive',
|
||||
type: 'directory',
|
||||
})
|
||||
expect(resolveResult!.manifest).toBeTruthy()
|
||||
expect(resolveResult!.manifest!.name).toBe('is-positive')
|
||||
expect(resolveResult!.manifest!.version).toBe('1.0.0')
|
||||
})
|
||||
|
||||
test('do not resolve from local directory when alwaysTryWorkspacePackages is false', async () => {
|
||||
nock(registry)
|
||||
.get('/is-positive')
|
||||
|
||||
@@ -184,7 +184,7 @@ async function resolveAndFetch (
|
||||
|
||||
const id = pkgId as string
|
||||
|
||||
if (resolution.type === 'directory') {
|
||||
if (resolution.type === 'directory' && !wantedDependency.injected) {
|
||||
if (manifest == null) {
|
||||
throw new Error(`Couldn't read package.json of local dependency ${wantedDependency.alias ? wantedDependency.alias + '@' : ''}${wantedDependency.pref ?? ''}`)
|
||||
}
|
||||
@@ -368,10 +368,13 @@ function fetchToStore (
|
||||
|
||||
if (opts.fetchRawManifest && (result.bundledManifest == null)) {
|
||||
result.bundledManifest = removeKeyOnFail(
|
||||
result.files.then(async ({ filesIndex }) => {
|
||||
const { integrity, mode } = filesIndex['package.json']
|
||||
const manifestPath = ctx.getFilePathByModeInCafs(integrity, mode)
|
||||
return readBundledManifest(manifestPath)
|
||||
result.files.then(async (filesResult) => {
|
||||
if (!filesResult.local) {
|
||||
const { integrity, mode } = filesResult.filesIndex['package.json']
|
||||
const manifestPath = ctx.getFilePathByModeInCafs(integrity, mode)
|
||||
return readBundledManifest(manifestPath)
|
||||
}
|
||||
return readBundledManifest(filesResult.filesIndex['package.json'])
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -400,13 +403,15 @@ function fetchToStore (
|
||||
) {
|
||||
try {
|
||||
const isLocalTarballDep = opts.pkg.id.startsWith('file:')
|
||||
const isLocalPkg = opts.pkg.resolution.type === 'directory'
|
||||
|
||||
if (
|
||||
!opts.force &&
|
||||
(
|
||||
!isLocalTarballDep ||
|
||||
await tarballIsUpToDate(opts.pkg.resolution as any, target, opts.lockfileDir) // eslint-disable-line
|
||||
)
|
||||
) &&
|
||||
!isLocalPkg
|
||||
) {
|
||||
let pkgFilesIndex
|
||||
try {
|
||||
@@ -494,42 +499,52 @@ Actual package in the store by the given integrity: ${pkgFilesIndex.name}@${pkgF
|
||||
}
|
||||
), { priority })
|
||||
|
||||
const filesIndex = fetchedPackage.filesIndex
|
||||
|
||||
// Ideally, files wouldn't care about when integrity is calculated.
|
||||
// However, we can only rename the temp folder once we know the package name.
|
||||
// And we cannot rename the temp folder till we're calculating integrities.
|
||||
const integrity: Record<string, PackageFileInfo> = {}
|
||||
await Promise.all(
|
||||
Object.keys(filesIndex)
|
||||
.map(async (filename) => {
|
||||
const {
|
||||
checkedAt,
|
||||
integrity: fileIntegrity,
|
||||
} = await filesIndex[filename].writeResult
|
||||
integrity[filename] = {
|
||||
checkedAt,
|
||||
integrity: fileIntegrity.toString(), // TODO: use the raw Integrity object
|
||||
mode: filesIndex[filename].mode,
|
||||
size: filesIndex[filename].size,
|
||||
}
|
||||
})
|
||||
)
|
||||
await writeJsonFile(filesIndexFile, {
|
||||
name: opts.pkg.name,
|
||||
version: opts.pkg.version,
|
||||
files: integrity,
|
||||
})
|
||||
let filesResult!: PackageFilesResponse
|
||||
if (!fetchedPackage.local) {
|
||||
const filesIndex = fetchedPackage.filesIndex
|
||||
// Ideally, files wouldn't care about when integrity is calculated.
|
||||
// However, we can only rename the temp folder once we know the package name.
|
||||
// And we cannot rename the temp folder till we're calculating integrities.
|
||||
const integrity: Record<string, PackageFileInfo> = {}
|
||||
await Promise.all(
|
||||
Object.keys(filesIndex)
|
||||
.map(async (filename) => {
|
||||
const {
|
||||
checkedAt,
|
||||
integrity: fileIntegrity,
|
||||
} = await filesIndex[filename].writeResult
|
||||
integrity[filename] = {
|
||||
checkedAt,
|
||||
integrity: fileIntegrity.toString(), // TODO: use the raw Integrity object
|
||||
mode: filesIndex[filename].mode,
|
||||
size: filesIndex[filename].size,
|
||||
}
|
||||
})
|
||||
)
|
||||
await writeJsonFile(filesIndexFile, {
|
||||
name: opts.pkg.name,
|
||||
version: opts.pkg.version,
|
||||
files: integrity,
|
||||
})
|
||||
filesResult = {
|
||||
fromStore: false,
|
||||
filesIndex: integrity,
|
||||
}
|
||||
} else {
|
||||
filesResult = {
|
||||
local: true,
|
||||
fromStore: false,
|
||||
filesIndex: fetchedPackage.filesIndex,
|
||||
packageImportMethod: fetchedPackage['packageImportMethod'],
|
||||
}
|
||||
}
|
||||
|
||||
if (isLocalTarballDep && opts.pkg.resolution['integrity']) { // eslint-disable-line @typescript-eslint/dot-notation
|
||||
await fs.mkdir(target, { recursive: true })
|
||||
await gfs.writeFile(path.join(target, TARBALL_INTEGRITY_FILENAME), opts.pkg.resolution['integrity'], 'utf8') // eslint-disable-line @typescript-eslint/dot-notation
|
||||
}
|
||||
|
||||
files.resolve({
|
||||
filesIndex: integrity,
|
||||
fromStore: false,
|
||||
})
|
||||
files.resolve(filesResult)
|
||||
finishing.resolve(undefined)
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
files.reject(err)
|
||||
|
||||
@@ -496,7 +496,7 @@ test('fetchPackageToStore() concurrency check', async () => {
|
||||
const fetchResult = fetchResults[0]
|
||||
const files = await fetchResult.files()
|
||||
|
||||
ino1 = statSync(getFilePathInCafs(cafsDir, files.filesIndex['package.json'].integrity, 'nonexec')).ino
|
||||
ino1 = statSync(getFilePathInCafs(cafsDir, files.filesIndex['package.json']['integrity'], 'nonexec')).ino
|
||||
|
||||
expect(Object.keys(files.filesIndex).sort()).toStrictEqual(['package.json', 'index.js', 'license', 'readme.md'].sort())
|
||||
expect(files.fromStore).toBeFalsy()
|
||||
@@ -508,7 +508,7 @@ test('fetchPackageToStore() concurrency check', async () => {
|
||||
const fetchResult = fetchResults[1]
|
||||
const files = await fetchResult.files()
|
||||
|
||||
ino2 = statSync(getFilePathInCafs(cafsDir, files.filesIndex['package.json'].integrity, 'nonexec')).ino
|
||||
ino2 = statSync(getFilePathInCafs(cafsDir, files.filesIndex['package.json']['integrity'], 'nonexec')).ino
|
||||
|
||||
expect(Object.keys(files.filesIndex).sort()).toStrictEqual(['package.json', 'index.js', 'license', 'readme.md'].sort())
|
||||
expect(files.fromStore).toBeFalsy()
|
||||
@@ -734,7 +734,7 @@ test('refetch package to store if it has been modified', async () => {
|
||||
})
|
||||
|
||||
const { filesIndex } = await fetchResult.files()
|
||||
indexJsFile = getFilePathInCafs(cafsDir, filesIndex['index.js'].integrity, 'nonexec')
|
||||
indexJsFile = getFilePathInCafs(cafsDir, filesIndex['index.js']['integrity'], 'nonexec')
|
||||
}
|
||||
|
||||
await delay(200)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@zkochan/rimraf": "^2.1.1",
|
||||
"load-json-file": "^6.2.0",
|
||||
"make-empty-dir": "^2.0.0",
|
||||
"mem": "^8.0.0",
|
||||
"p-limit": "^3.1.0",
|
||||
"path-exists": "^4.0.0",
|
||||
"path-temp": "^2.0.0",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import createCafs, {
|
||||
getFilePathByModeInCafs as _getFilePathByModeInCafs,
|
||||
getFilePathByModeInCafs,
|
||||
PackageFilesIndex,
|
||||
} from '@pnpm/cafs'
|
||||
import { FetchFunction } from '@pnpm/fetcher-base'
|
||||
import { FetchFunction, PackageFilesResponse } from '@pnpm/fetcher-base'
|
||||
import createPackageRequester from '@pnpm/package-requester'
|
||||
import { ResolveFunction } from '@pnpm/resolver-base'
|
||||
import {
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
StoreController,
|
||||
} from '@pnpm/store-controller-types'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import memoize from 'mem'
|
||||
import pathTemp from 'path-temp'
|
||||
import writeJsonFile from 'write-json-file'
|
||||
import createImportPackage from './createImportPackage'
|
||||
@@ -24,27 +25,44 @@ function createPackageImporter (
|
||||
cafsDir: string
|
||||
}
|
||||
): ImportPackageFunction {
|
||||
const impPkg = createImportPackage(opts.packageImportMethod)
|
||||
const getFilePathByModeInCafs = _getFilePathByModeInCafs.bind(null, opts.cafsDir)
|
||||
const cachedImporterCreator = memoize(createImportPackage)
|
||||
const packageImportMethod = opts.packageImportMethod
|
||||
const gfm = getFlatMap.bind(null, opts.cafsDir)
|
||||
return async (to, opts) => {
|
||||
const filesMap = {} as Record<string, string>
|
||||
let isBuilt!: boolean
|
||||
let filesIndex!: Record<string, PackageFileInfo>
|
||||
if (opts.targetEngine && ((opts.filesResponse.sideEffects?.[opts.targetEngine]) != null)) {
|
||||
filesIndex = opts.filesResponse.sideEffects?.[opts.targetEngine]
|
||||
isBuilt = true
|
||||
} else {
|
||||
filesIndex = opts.filesResponse.filesIndex
|
||||
isBuilt = false
|
||||
}
|
||||
for (const [fileName, fileMeta] of Object.entries(filesIndex)) {
|
||||
filesMap[fileName] = getFilePathByModeInCafs(fileMeta.integrity, fileMeta.mode)
|
||||
}
|
||||
const { filesMap, isBuilt } = gfm(opts.filesResponse, opts.targetEngine)
|
||||
const impPkg = cachedImporterCreator(opts.filesResponse.packageImportMethod ?? packageImportMethod)
|
||||
const importMethod = await impPkg(to, { filesMap, fromStore: opts.filesResponse.fromStore, force: opts.force })
|
||||
return { importMethod, isBuilt }
|
||||
}
|
||||
}
|
||||
|
||||
function getFlatMap (
|
||||
cafsDir: string,
|
||||
filesResponse: PackageFilesResponse,
|
||||
targetEngine?: string
|
||||
): { filesMap: Record<string, string>, isBuilt: boolean } {
|
||||
if (filesResponse.local) {
|
||||
return {
|
||||
filesMap: filesResponse.filesIndex,
|
||||
isBuilt: false,
|
||||
}
|
||||
}
|
||||
let isBuilt!: boolean
|
||||
let filesIndex!: Record<string, PackageFileInfo>
|
||||
if (targetEngine && ((filesResponse.sideEffects?.[targetEngine]) != null)) {
|
||||
filesIndex = filesResponse.sideEffects?.[targetEngine]
|
||||
isBuilt = true
|
||||
} else {
|
||||
filesIndex = filesResponse.filesIndex
|
||||
isBuilt = false
|
||||
}
|
||||
const filesMap = {}
|
||||
for (const [fileName, fileMeta] of Object.entries(filesIndex)) {
|
||||
filesMap[fileName] = getFilePathByModeInCafs(cafsDir, fileMeta.integrity, fileMeta.mode)
|
||||
}
|
||||
return { filesMap, isBuilt }
|
||||
}
|
||||
|
||||
export function createCafsStore (
|
||||
storeDir: string,
|
||||
opts?: {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@pnpm/config": "workspace:13.4.0",
|
||||
"@pnpm/error": "workspace:2.0.0",
|
||||
"@pnpm/fetch": "workspace:4.1.3",
|
||||
"@pnpm/fetcher-base": "workspace:11.0.3",
|
||||
"@pnpm/package-store": "workspace:12.0.15",
|
||||
"@pnpm/store-path": "^5.0.0",
|
||||
"@pnpm/tarball-fetcher": "workspace:9.3.7",
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { Config } from '@pnpm/config'
|
||||
import fetch, { createFetchFromRegistry, FetchFromRegistry } from '@pnpm/fetch'
|
||||
import { FilesIndex } from '@pnpm/fetcher-base'
|
||||
import { createCafsStore } from '@pnpm/package-store'
|
||||
import storePath from '@pnpm/store-path'
|
||||
import createFetcher, { waitForFilesIndex } from '@pnpm/tarball-fetcher'
|
||||
@@ -87,7 +88,7 @@ async function installNode (wantedNodeVersion: string, versionDir: string, opts:
|
||||
})
|
||||
await cafs.importPackage(versionDir, {
|
||||
filesResponse: {
|
||||
filesIndex: await waitForFilesIndex(filesIndex),
|
||||
filesIndex: await waitForFilesIndex(filesIndex as FilesIndex),
|
||||
fromStore: false,
|
||||
},
|
||||
force: true,
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
{
|
||||
"path": "../fetch"
|
||||
},
|
||||
{
|
||||
"path": "../fetcher-base"
|
||||
},
|
||||
{
|
||||
"path": "../package-store"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Registries } from '@pnpm/types'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
|
||||
export interface StrictRebuildOptions {
|
||||
cacheDir: string
|
||||
childConcurrency: number
|
||||
extraBinPaths: string[]
|
||||
lockfileDir: string
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import lockfileWalker, { LockfileWalkerStep } from '@pnpm/lockfile-walker'
|
||||
import logger, { streamParser } from '@pnpm/logger'
|
||||
import { write as writeModulesYaml } from '@pnpm/modules-yaml'
|
||||
import { createOrConnectStoreController } from '@pnpm/store-connection-manager'
|
||||
import { ProjectManifest } from '@pnpm/types'
|
||||
import * as dp from 'dependency-path'
|
||||
import runGroups from 'run-groups'
|
||||
@@ -150,11 +151,13 @@ export async function rebuild (
|
||||
|
||||
ctx.pendingBuilds = ctx.pendingBuilds.filter((depPath) => !pkgsThatWereRebuilt.has(depPath))
|
||||
|
||||
const store = await createOrConnectStoreController(opts)
|
||||
const scriptsOpts = {
|
||||
extraBinPaths: ctx.extraBinPaths,
|
||||
rawConfig: opts.rawConfig,
|
||||
scriptShell: opts.scriptShell,
|
||||
shellEmulator: opts.shellEmulator,
|
||||
storeController: store.ctrl,
|
||||
unsafePerm: opts.unsafePerm || false,
|
||||
}
|
||||
await runLifecycleHooksConcurrently(
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface WantedDependency {
|
||||
pref: string // package reference
|
||||
dev: boolean
|
||||
optional: boolean
|
||||
injected?: boolean
|
||||
}
|
||||
|
||||
export default function getNonDevWantedDependencies (pkg: DependencyManifest) {
|
||||
|
||||
@@ -153,12 +153,15 @@ export default async function (
|
||||
virtualStoreDir: opts.virtualStoreDir,
|
||||
})
|
||||
|
||||
for (const { id } of projectsToLink) {
|
||||
for (const { id, manifest } of projectsToLink) {
|
||||
for (const [alias, depPath] of Object.entries(dependenciesByProjectId[id])) {
|
||||
const depNode = dependenciesGraph[depPath]
|
||||
if (depNode.isPure) continue
|
||||
|
||||
const projectSnapshot = opts.wantedLockfile.importers[id]
|
||||
if (manifest.dependenciesMeta != null) {
|
||||
projectSnapshot.dependenciesMeta = manifest.dependenciesMeta
|
||||
}
|
||||
const ref = depPathToRef(depPath, {
|
||||
alias,
|
||||
realName: depNode.name,
|
||||
|
||||
@@ -67,7 +67,7 @@ function toLockfileDependency (
|
||||
}
|
||||
): PackageSnapshot {
|
||||
const lockfileResolution = toLockfileResolution(
|
||||
{ name: pkg.name, version: pkg.version },
|
||||
{ id: pkg.id, name: pkg.name, version: pkg.version },
|
||||
opts.depPath,
|
||||
pkg.resolution,
|
||||
opts.registry
|
||||
@@ -210,6 +210,7 @@ function updateResolvedDeps (
|
||||
|
||||
function toLockfileResolution (
|
||||
pkg: {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
},
|
||||
@@ -219,6 +220,12 @@ function toLockfileResolution (
|
||||
): LockfileResolution {
|
||||
/* eslint-disable @typescript-eslint/dot-notation */
|
||||
if (dp.isAbsolute(depPath) || resolution.type !== undefined || !resolution['integrity']) {
|
||||
if (resolution.type === 'directory') {
|
||||
return {
|
||||
type: 'directory',
|
||||
directory: pkg.id.replace(/^file:/, ''),
|
||||
}
|
||||
}
|
||||
return resolution as LockfileResolution
|
||||
}
|
||||
const base = registry !== resolution['registry'] ? { registry: resolution['registry'] } : {}
|
||||
|
||||
@@ -64,11 +64,13 @@ export interface ResolveOptions {
|
||||
}
|
||||
|
||||
export type WantedDependency = {
|
||||
injected?: boolean
|
||||
} & ({
|
||||
alias?: string
|
||||
pref: string
|
||||
} | {
|
||||
alias: string
|
||||
pref?: string
|
||||
}
|
||||
})
|
||||
|
||||
export type ResolveFunction = (wantedDependency: WantedDependency, opts: ResolveOptions) => Promise<ResolveResult>
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface PeerDependenciesMeta {
|
||||
|
||||
export interface DependenciesMeta {
|
||||
[dependencyName: string]: {
|
||||
injected?: boolean
|
||||
node?: string
|
||||
}
|
||||
}
|
||||
|
||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -285,6 +285,7 @@ importers:
|
||||
specifiers:
|
||||
'@pnpm/client': 'link:'
|
||||
'@pnpm/default-resolver': workspace:13.0.9
|
||||
'@pnpm/directory-fetcher': workspace:0.0.0
|
||||
'@pnpm/fetch': workspace:4.1.3
|
||||
'@pnpm/fetching-types': workspace:2.2.1
|
||||
'@pnpm/git-fetcher': workspace:4.1.6
|
||||
@@ -295,6 +296,7 @@ importers:
|
||||
mem: ^8.0.0
|
||||
dependencies:
|
||||
'@pnpm/default-resolver': link:../default-resolver
|
||||
'@pnpm/directory-fetcher': link:../directory-fetcher
|
||||
'@pnpm/fetch': link:../fetch
|
||||
'@pnpm/fetching-types': link:../fetching-types
|
||||
'@pnpm/git-fetcher': link:../git-fetcher
|
||||
@@ -673,6 +675,23 @@ importers:
|
||||
'@types/semver': 7.3.9
|
||||
dependency-path: 'link:'
|
||||
|
||||
packages/directory-fetcher:
|
||||
specifiers:
|
||||
'@pnpm/directory-fetcher': 'link:'
|
||||
'@pnpm/fetcher-base': workspace:11.0.3
|
||||
'@pnpm/resolver-base': workspace:8.0.4
|
||||
load-json-file: ^6.2.0
|
||||
npm-packlist: ^2.2.2
|
||||
ramda: ^0.27.1
|
||||
dependencies:
|
||||
'@pnpm/fetcher-base': link:../fetcher-base
|
||||
'@pnpm/resolver-base': link:../resolver-base
|
||||
load-json-file: 6.2.0
|
||||
npm-packlist: 2.2.2
|
||||
ramda: 0.27.1
|
||||
devDependencies:
|
||||
'@pnpm/directory-fetcher': 'link:'
|
||||
|
||||
packages/error:
|
||||
specifiers:
|
||||
'@pnpm/error': 'link:'
|
||||
@@ -1142,10 +1161,12 @@ importers:
|
||||
packages/lifecycle:
|
||||
specifiers:
|
||||
'@pnpm/core-loggers': workspace:6.0.4
|
||||
'@pnpm/directory-fetcher': workspace:0.0.0
|
||||
'@pnpm/lifecycle': 'link:'
|
||||
'@pnpm/logger': ^4.0.0
|
||||
'@pnpm/npm-lifecycle': ^1.0.0
|
||||
'@pnpm/read-package-json': workspace:5.0.4
|
||||
'@pnpm/store-controller-types': workspace:11.0.5
|
||||
'@pnpm/types': workspace:7.4.0
|
||||
'@types/rimraf': ^3.0.0
|
||||
json-append: 1.1.1
|
||||
@@ -1154,8 +1175,10 @@ importers:
|
||||
run-groups: ^3.0.1
|
||||
dependencies:
|
||||
'@pnpm/core-loggers': link:../core-loggers
|
||||
'@pnpm/directory-fetcher': link:../directory-fetcher
|
||||
'@pnpm/npm-lifecycle': 1.0.0
|
||||
'@pnpm/read-package-json': link:../read-package-json
|
||||
'@pnpm/store-controller-types': link:../store-controller-types
|
||||
'@pnpm/types': link:../types
|
||||
path-exists: 4.0.0
|
||||
run-groups: 3.0.1
|
||||
@@ -1372,6 +1395,9 @@ importers:
|
||||
packages/lockfile-types:
|
||||
specifiers:
|
||||
'@pnpm/lockfile-types': 'link:'
|
||||
'@pnpm/types': workspace:7.4.0
|
||||
dependencies:
|
||||
'@pnpm/types': link:../types
|
||||
devDependencies:
|
||||
'@pnpm/lockfile-types': 'link:'
|
||||
|
||||
@@ -1846,6 +1872,7 @@ importers:
|
||||
'@zkochan/rimraf': ^2.1.1
|
||||
load-json-file: ^6.2.0
|
||||
make-empty-dir: ^2.0.0
|
||||
mem: ^8.0.0
|
||||
p-limit: ^3.1.0
|
||||
path-exists: ^4.0.0
|
||||
path-temp: ^2.0.0
|
||||
@@ -1866,6 +1893,7 @@ importers:
|
||||
'@zkochan/rimraf': 2.1.1
|
||||
load-json-file: 6.2.0
|
||||
make-empty-dir: 2.0.0
|
||||
mem: 8.1.1
|
||||
p-limit: 3.1.0
|
||||
path-exists: 4.0.0
|
||||
path-temp: 2.0.0
|
||||
@@ -1988,6 +2016,7 @@ importers:
|
||||
'@pnpm/config': workspace:13.4.0
|
||||
'@pnpm/error': workspace:2.0.0
|
||||
'@pnpm/fetch': workspace:4.1.3
|
||||
'@pnpm/fetcher-base': workspace:11.0.3
|
||||
'@pnpm/package-store': workspace:12.0.15
|
||||
'@pnpm/plugin-commands-env': 'link:'
|
||||
'@pnpm/prepare': workspace:0.0.26
|
||||
@@ -2010,6 +2039,7 @@ importers:
|
||||
'@pnpm/config': link:../config
|
||||
'@pnpm/error': link:../error
|
||||
'@pnpm/fetch': link:../fetch
|
||||
'@pnpm/fetcher-base': link:../fetcher-base
|
||||
'@pnpm/package-store': link:../package-store
|
||||
'@pnpm/store-path': 5.0.0
|
||||
'@pnpm/tarball-fetcher': link:../tarball-fetcher
|
||||
|
||||
Reference in New Issue
Block a user