From 59759f7c6fb2b77fe5aabef669cbcce0ac9125a9 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sat, 5 Oct 2019 20:54:07 +0300 Subject: [PATCH] feat: new matching pattern BREAKING CHANGE: new wildcard matcher used close #2048 PR #2052 --- packages/config/src/Config.ts | 2 +- packages/config/src/index.ts | 8 +- packages/config/test/index.ts | 2 +- packages/dependencies-hierarchy/package.json | 6 +- packages/dependencies-hierarchy/src/index.ts | 77 +---- packages/dependencies-hierarchy/test/index.ts | 291 ++---------------- packages/headless/package.json | 1 + packages/headless/src/index.ts | 5 +- packages/hoist/package.json | 2 - packages/hoist/src/index.ts | 10 +- packages/list/package.json | 5 +- packages/list/src/createPackagesSearcher.ts | 43 +++ packages/list/src/index.ts | 24 +- .../list/test/createPackagesSearcher.spec.ts | 27 ++ packages/list/test/index.ts | 1 + packages/matcher/README.md | 23 ++ packages/matcher/package.json | 44 +++ packages/matcher/src/index.ts | 22 ++ packages/matcher/test/index.ts | 35 +++ packages/matcher/tsconfig.json | 10 + packages/matcher/tslint.json | 3 + packages/modules-yaml/src/index.ts | 2 +- packages/outdated/package.json | 4 +- packages/outdated/src/index.ts | 36 +-- packages/outdated/test/index.ts | 123 +------- packages/pnpm/package.json | 3 +- packages/pnpm/src/cmd/outdated.ts | 26 +- packages/pnpm/src/cmd/recursive/filter.ts | 2 +- packages/pnpm/test/link.ts | 2 +- packages/pnpm/test/recursive/misc.ts | 4 +- packages/read-importers-context/src/index.ts | 2 +- packages/supi/package.json | 1 + packages/supi/src/getContext/index.ts | 15 +- .../supi/src/install/extendInstallOptions.ts | 2 +- packages/supi/src/install/link.ts | 5 +- packages/supi/src/link/options.ts | 2 +- .../supi/src/rebuild/extendRebuildOptions.ts | 2 - packages/supi/src/rebuild/index.ts | 2 +- pnpm-lock.yaml | 60 ++-- 39 files changed, 345 insertions(+), 589 deletions(-) create mode 100644 packages/list/src/createPackagesSearcher.ts create mode 100644 packages/list/test/createPackagesSearcher.spec.ts create mode 100644 packages/matcher/README.md create mode 100644 packages/matcher/package.json create mode 100644 packages/matcher/src/index.ts create mode 100644 packages/matcher/test/index.ts create mode 100644 packages/matcher/tsconfig.json create mode 100644 packages/matcher/tslint.json diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 05c59d7bc2..2a3f5d49ae 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -86,7 +86,7 @@ export interface Config { pnpmfile: string, independentLeaves?: boolean, packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone', - hoistPattern?: string, + hoistPattern?: string[], useStoreServer?: boolean, useRunningStoreServer?: boolean, workspaceConcurrency: number, diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index db2f90e5a6..e9ffd24d1d 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -32,7 +32,7 @@ export const types = Object.assign({ 'global-path': path, 'global-pnpmfile': String, 'hoist': Boolean, - 'hoist-pattern': String, + 'hoist-pattern': Array, 'ignore-pnpmfile': Boolean, 'ignore-stop-requests': Boolean, 'ignore-upload-requests': Boolean, @@ -148,7 +148,7 @@ export default async ( 'fetch-retry-mintimeout': 10000, 'globalconfig': npmDefaults.globalconfig, 'hoist': true, - 'hoist-pattern': '*', + 'hoist-pattern': ['*'], 'ignore-workspace-root-check': false, 'latest': false, 'link-workspace-packages': true, @@ -247,7 +247,7 @@ export default async ( } pnpmConfig.independentLeaves = false } - if (pnpmConfig.hoistPattern !== '*') { + if (pnpmConfig.hoistPattern && (pnpmConfig.hoistPattern.length > 1 || pnpmConfig.hoistPattern[0] !== '*')) { if (opts.cliArgs['hoist-pattern']) { throw new PnpmError('CONFIG_CONFLICT_HOIST_PATTERN_WITH_GLOBAL', 'Configuration conflict. "hoist-pattern" may not be used with "global"') @@ -326,7 +326,7 @@ export default async ( } if (pnpmConfig['shamefullyFlatten']) { warnings.push('The "shamefully-flatten" setting is deprecated. Use "shamefully-hoist", "hoist" or "hoist-pattern" instead. Since v4, hoisting is on by default for all dependencies.') - pnpmConfig.hoistPattern = '*' + pnpmConfig.hoistPattern = ['*'] pnpmConfig.shamefullyHoist = true } if (pnpmConfig['hoist'] === false) { diff --git a/packages/config/test/index.ts b/packages/config/test/index.ts index 570b165cf6..6d46802845 100644 --- a/packages/config/test/index.ts +++ b/packages/config/test/index.ts @@ -514,7 +514,7 @@ test('convert shamefully-flatten to hoist-pattern=* and warn', async (t) => { }, }) - t.equal(config.hoistPattern, '*') + t.deepEqual(config.hoistPattern, ['*']) t.equal(config.shamefullyHoist, true) t.deepEqual(warnings, ['The "shamefully-flatten" setting is deprecated. ' + 'Use "shamefully-hoist", "hoist" or "hoist-pattern" instead. ' + diff --git a/packages/dependencies-hierarchy/package.json b/packages/dependencies-hierarchy/package.json index cc6b400b94..13cfc3ae78 100644 --- a/packages/dependencies-hierarchy/package.json +++ b/packages/dependencies-hierarchy/package.json @@ -43,16 +43,12 @@ "@pnpm/types": "workspace:3.2.0", "@pnpm/utils": "workspace:0.10.6", "dependency-path": "workspace:3.0.8", - "micromatch": "4.0.2", "normalize-path": "3.0.0", - "resolve-link-target": "1.0.1", - "semver": "6.3.0" + "resolve-link-target": "1.0.1" }, "devDependencies": { "@pnpm/constants": "link:../constants", "@pnpm/logger": "2.1.1", - "@types/micromatch": "3.1.0", - "@types/semver": "6", "dependencies-hierarchy": "link:", "tape": "4.11.0" } diff --git a/packages/dependencies-hierarchy/src/index.ts b/packages/dependencies-hierarchy/src/index.ts index 9d2f40acce..f62036c04b 100644 --- a/packages/dependencies-hierarchy/src/index.ts +++ b/packages/dependencies-hierarchy/src/index.ts @@ -19,18 +19,12 @@ import { realNodeModulesDir, safeReadPackageFromDir, } from '@pnpm/utils' -import assert = require('assert') import { refToAbsolute, refToRelative } from 'dependency-path' -import { isMatch } from 'micromatch' import normalizePath = require('normalize-path') import path = require('path') import resolveLinkTarget = require('resolve-link-target') -import semver = require('semver') -export type PackageSelector = string | { - name: string, - range: string, -} +export type SearchFunction = (pkg: { name: string, version: string }) => boolean export interface PackageNode { alias: string, @@ -48,34 +42,6 @@ export interface PackageNode { version: string, } -export function forPackages ( - packages: PackageSelector[], - projectPaths: string[], - opts: { - depth: number, - include?: { [dependenciesField in DependenciesField]: boolean }, - registries?: Registries, - lockfileDirectory: string, - }, -) { - assert(packages, 'packages should be defined') - if (!packages.length) return {} - - return dependenciesHierarchy(projectPaths, packages, opts) -} - -export default function ( - projectPaths: string[], - opts: { - depth: number, - include?: { [dependenciesField in DependenciesField]: boolean }, - registries?: Registries, - lockfileDirectory: string, - }, -) { - return dependenciesHierarchy(projectPaths, [], opts) -} - export type DependenciesHierarchy = { dependencies?: PackageNode[], devDependencies?: PackageNode[], @@ -83,13 +49,13 @@ export type DependenciesHierarchy = { unsavedDependencies?: PackageNode[], } -async function dependenciesHierarchy ( +export default async function dependenciesHierarchy ( projectPaths: string[], - searched: PackageSelector[], maybeOpts: { depth: number, include?: { [dependenciesField in DependenciesField]: boolean }, registries?: Registries, + search?: SearchFunction, lockfileDirectory: string, }, ): Promise<{ [prefix: string]: DependenciesHierarchy }> { @@ -122,13 +88,14 @@ async function dependenciesHierarchy ( }, lockfileDirectory: maybeOpts.lockfileDirectory, registries, + search: maybeOpts.search, skipped: new Set(modules && modules.skipped || []), } ; ( await Promise.all(projectPaths.map(async (projectPath) => { return [ projectPath, - await dependenciesHierarchyForPackage(projectPath, currentLockfile, searched, opts), + await dependenciesHierarchyForPackage(projectPath, currentLockfile, opts), ] as [string, DependenciesHierarchy] })) ).forEach(([projectPath, dependenciesHierarchy]) => { @@ -140,11 +107,11 @@ async function dependenciesHierarchy ( async function dependenciesHierarchyForPackage ( projectPath: string, currentLockfile: Lockfile, - searched: PackageSelector[], opts: { depth: number, include: { [dependenciesField in DependenciesField]: boolean }, registries: Registries, + search?: SearchFunction, skipped: Set, lockfileDirectory: string, }, @@ -167,7 +134,7 @@ async function dependenciesHierarchyForPackage ( maxDepth: opts.depth, modulesDir, registries: opts.registries, - searched, + search: opts.search, skipped: opts.skipped, wantedPackages: wantedLockfile.packages || {}, }) @@ -186,9 +153,9 @@ async function dependenciesHierarchyForPackage ( wantedPackages: wantedLockfile.packages || {}, }) let newEntry: PackageNode | null = null - const matchedSearched = searched.length && matches(searched, packageInfo) + const matchedSearched = opts.search && opts.search(packageInfo) if (packageAbsolutePath === null) { - if (searched.length && !matchedSearched) return + if (opts.search && !matchedSearched) return newEntry = packageInfo } else { const relativeId = refToRelative(topDeps[alias], alias) @@ -199,7 +166,7 @@ async function dependenciesHierarchyForPackage ( ...packageInfo, dependencies, } - } else if (!searched.length || matches(searched, packageInfo)) { + } else if (!opts.search || matchedSearched) { newEntry = packageInfo } } @@ -234,8 +201,8 @@ async function dependenciesHierarchyForPackage ( path: pkgPath, version, } - const matchedSearched = searched.length && matches(searched, pkg) - if (searched.length && !matchedSearched) return + const matchedSearched = opts.search && opts.search(pkg) + if (opts.search && !matchedSearched) return const newEntry: PackageNode = pkg if (matchedSearched) { newEntry.searched = true @@ -262,7 +229,7 @@ function getTree ( maxDepth: number, modulesDir: string, includeOptionalDependencies: boolean, - searched: PackageSelector[], + search?: SearchFunction, skipped: Set, registries: Registries, currentPackages: PackageSnapshots, @@ -301,7 +268,7 @@ function getTree ( wantedPackages: opts.wantedPackages, }) let circular: boolean - const matchedSearched = opts.searched.length && matches(opts.searched, packageInfo) + const matchedSearched = opts.search && opts.search(packageInfo) let newEntry: PackageNode | null = null if (packageAbsolutePath === null) { circular = false @@ -316,7 +283,7 @@ function getTree ( ...packageInfo, dependencies, } - } else if (!opts.searched.length || matchedSearched) { + } else if (!opts.search || matchedSearched) { newEntry = packageInfo } } @@ -399,17 +366,3 @@ function getPkgInfo ( packageInfo, } } - -function matches ( - searched: PackageSelector[], - pkg: {name: string, version: string}, -) { - return searched.some((searchedPkg) => { - if (typeof searchedPkg === 'string') { - return isMatch(pkg.name, searchedPkg) - } - return isMatch(pkg.name, searchedPkg.name) && - !pkg.version.startsWith('link:') && - semver.satisfies(pkg.version, searchedPkg.range) - }) -} diff --git a/packages/dependencies-hierarchy/test/index.ts b/packages/dependencies-hierarchy/test/index.ts index aca12cf445..bd80670f1a 100644 --- a/packages/dependencies-hierarchy/test/index.ts +++ b/packages/dependencies-hierarchy/test/index.ts @@ -1,6 +1,6 @@ /// import { WANTED_LOCKFILE } from '@pnpm/constants' -import dh, { forPackages as dhForPackages, PackageNode } from 'dependencies-hierarchy' +import dh, { PackageNode } from 'dependencies-hierarchy' import path = require('path') import test = require('tape') @@ -252,18 +252,31 @@ test('only dev depth 0', async t => { }) test('hierarchy for no packages', async t => { - const tree = await dhForPackages([], [generalFixture], { depth: 100, lockfileDirectory: generalFixture }) + const tree = await dh([generalFixture], { + depth: 100, + lockfileDirectory: generalFixture, + search: () => false, + }) - t.deepEqual(tree, []) + t.deepEqual(tree, { + [generalFixture]: { + dependencies: [], + devDependencies: [], + optionalDependencies: [], + }, + }) t.end() }) test('filter 1 package with depth 0', async t => { - const tree = await dhForPackages( - [{ name: 'rimraf', range: '*' }], + const tree = await dh( [generalFixture], - { depth: 0, lockfileDirectory: generalFixture }, + { + depth: 0, + lockfileDirectory: generalFixture, + search: ({ name }) => name === 'rimraf', + }, ) const modulesDir = path.join(generalFixture, 'node_modules') @@ -291,214 +304,6 @@ test('filter 1 package with depth 0', async t => { t.end() }) -test('filter by pattern', async t => { - const modulesDir = path.join(generalFixture, 'node_modules') - - t.deepEqual( - await dhForPackages(['rim*'], [generalFixture], { depth: 0, lockfileDirectory: generalFixture }), - { - [generalFixture]: { - dependencies: [ - { - alias: 'rimraf', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'rimraf', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/rimraf/2.5.1'), - resolved: 'https://registry.npmjs.org/rimraf/-/rimraf-2.5.1.tgz', - searched: true, - version: '2.5.1', - }, - ], - devDependencies: [], - optionalDependencies: [], - }, - }, - 'matched by pattern', - ) - - t.deepEqual( - await dhForPackages(['rim1*'], [generalFixture], { depth: 0, lockfileDirectory: generalFixture }), - { - [generalFixture]: { - dependencies: [], - devDependencies: [], - optionalDependencies: [], - } - }, - 'not matched by pattern', - ) - - t.deepEqual( - await dhForPackages([{ name: 'rim*', range: '2' }], [generalFixture], { depth: 0, lockfileDirectory: generalFixture }), - { - [generalFixture]: { - dependencies: [ - { - alias: 'rimraf', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'rimraf', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/rimraf/2.5.1'), - resolved: 'https://registry.npmjs.org/rimraf/-/rimraf-2.5.1.tgz', - searched: true, - version: '2.5.1', - }, - ], - devDependencies: [], - optionalDependencies: [], - }, - }, - 'matched by pattern and range', - ) - - t.deepEqual( - await dhForPackages([{ name: 'rim*', range: '3' }], [generalFixture], { depth: 0, lockfileDirectory: generalFixture }), - { - [generalFixture]: { - dependencies: [], - devDependencies: [], - optionalDependencies: [], - }, - }, - 'not matched by pattern and range', - ) - - t.end() -}) - -test('filter 2 packages with depth 100', async t => { - const searched = [ - 'minimatch', - { name: 'once', range: '1.4' }, - ] - const tree = await dhForPackages(searched, [generalFixture], { depth: 100, lockfileDirectory: generalFixture }) - const modulesDir = path.join(generalFixture, 'node_modules') - - t.deepEqual(tree, { - [generalFixture]: { - dependencies: [ - { - alias: 'minimatch', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'minimatch', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/minimatch/3.0.4'), - resolved: 'https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz', - searched: true, - version: '3.0.4', - }, - { - alias: 'rimraf', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'rimraf', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/rimraf/2.5.1'), - resolved: 'https://registry.npmjs.org/rimraf/-/rimraf-2.5.1.tgz', - version: '2.5.1', - - dependencies: [ - { - alias: 'glob', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'glob', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/glob/6.0.4'), - resolved: 'https://registry.npmjs.org/glob/-/glob-6.0.4.tgz', - version: '6.0.4', - - dependencies: [ - { - alias: 'inflight', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'inflight', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/inflight/1.0.6'), - resolved: 'https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz', - version: '1.0.6', - - dependencies: [ - { - alias: 'once', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'once', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/once/1.4.0'), - resolved: 'https://registry.npmjs.org/once/-/once-1.4.0.tgz', - searched: true, - version: '1.4.0', - }, - ], - }, - { - alias: 'minimatch', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'minimatch', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/minimatch/3.0.4'), - resolved: 'https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz', - searched: true, - version: '3.0.4', - }, - { - alias: 'once', - dev: false, - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'once', - path: path.join(modulesDir, '.pnpm/registry.npmjs.org/once/1.4.0'), - resolved: 'https://registry.npmjs.org/once/-/once-1.4.0.tgz', - searched: true, - version: '1.4.0', - }, - ], - }, - ], - }, - ], - devDependencies: [], - optionalDependencies: [], - }, - }) - - t.end() -}) - -test('filter 2 packages with ranges that are not satisfied', async t => { - const searched = [ - { name: 'minimatch', range: '100' }, - { name: 'once', range: '100' }, - ] - const tree = await dhForPackages(searched, [generalFixture], { depth: 100, lockfileDirectory: generalFixture }) - - t.deepEqual(tree, { - [generalFixture]: { - dependencies: [], - devDependencies: [], - optionalDependencies: [], - }, - }) - - t.end() -}) - test('circular dependency', async t => { const tree = await dh([circularFixture], { depth: 1000, lockfileDirectory: circularFixture }) const modulesDir = path.join(circularFixture, 'node_modules') @@ -593,55 +398,6 @@ test('on a package that has only links', async t => { t.end() }) -test('filter on a package that has only links', async t => { - t.deepEqual( - await dhForPackages(['rimraf'], [withLinksOnlyFixture], { depth: 1000, lockfileDirectory: withLinksOnlyFixture }), - { - [withLinksOnlyFixture]: { - dependencies: [], - devDependencies: [], - optionalDependencies: [], - }, - }, - 'not found', - ) - t.deepEqual( - await dhForPackages([{ name: 'general', range: '2' }], [withLinksOnlyFixture], { depth: 1000, lockfileDirectory: withLinksOnlyFixture }), - { - [withLinksOnlyFixture]: { - dependencies: [], - devDependencies: [], - optionalDependencies: [], - }, - }, - 'not found', - ) - t.deepEqual( - await dhForPackages(['general'], [withLinksOnlyFixture], { depth: 1000, lockfileDirectory: withLinksOnlyFixture }), - { - [withLinksOnlyFixture]: { - dependencies: [ - { - alias: 'general', - isMissing: false, - isPeer: false, - isSkipped: false, - name: 'general', - path: path.join(__dirname, '..', 'fixtureWithLinks', 'general'), - searched: true, - version: 'link:../general', - }, - ], - devDependencies: [], - optionalDependencies: [], - }, - }, - 'found', - ) - - t.end() -}) - test('unsaved dependencies are listed', async t => { const modulesDir = path.join(withUnsavedDepsFixture, 'node_modules') t.deepEqual( @@ -692,10 +448,13 @@ test('unsaved dependencies are listed', async t => { test('unsaved dependencies are listed and filtered', async t => { const modulesDir = path.join(withUnsavedDepsFixture, 'node_modules') t.deepEqual( - await dhForPackages( - [{ name: 'symlink-dir', range: '*' }], + await dh( [withUnsavedDepsFixture], - { depth: 0, lockfileDirectory: withUnsavedDepsFixture }, + { + depth: 0, + lockfileDirectory: withUnsavedDepsFixture, + search: ({ name }) => name === 'symlink-dir', + }, ), { [withUnsavedDepsFixture]: { diff --git a/packages/headless/package.json b/packages/headless/package.json index e7be61f9f7..c7ea339dd2 100644 --- a/packages/headless/package.json +++ b/packages/headless/package.json @@ -87,6 +87,7 @@ "@pnpm/link-bins": "4.4.0", "@pnpm/lockfile-file": "workspace:2.0.0-0", "@pnpm/lockfile-utils": "workspace:1.0.10", + "@pnpm/matcher": "workspace:0.0.0", "@pnpm/modules-cleaner": "workspace:5.0.0-2", "@pnpm/modules-yaml": "workspace:4.0.0-1", "@pnpm/package-requester": "workspace:8.0.0-0", diff --git a/packages/headless/src/index.ts b/packages/headless/src/index.ts index 234c8a444f..cebd31620a 100644 --- a/packages/headless/src/index.ts +++ b/packages/headless/src/index.ts @@ -38,6 +38,7 @@ import logger, { LogBase, streamParser, } from '@pnpm/logger' +import matcher from '@pnpm/matcher' import { prune } from '@pnpm/modules-cleaner' import { IncludedDependencies, @@ -85,7 +86,7 @@ export interface HeadlessOptions { pruneDirectDependencies?: boolean, }>, hoistedAliases: {[depPath: string]: string[]} - hoistPattern?: string, + hoistPattern?: string[], lockfileDirectory: string, shamefullyHoist: boolean, storeController: StoreController, @@ -237,7 +238,7 @@ export default async (opts: HeadlessOptions) => { const rootImporterWithFlatModules = opts.hoistPattern && opts.importers.find((importer) => importer.id === '.') let newHoistedAliases!: {[depPath: string]: string[]} if (rootImporterWithFlatModules) { - newHoistedAliases = await hoist(opts.hoistPattern!, { + newHoistedAliases = await hoist(matcher(opts.hoistPattern!), { getIndependentPackageLocation: opts.independentLeaves ? async (packageId: string, packageName: string) => { const { directory } = await opts.storeController.getPackageLocation(packageId, packageName, { diff --git a/packages/hoist/package.json b/packages/hoist/package.json index 61ab9c6cc5..5157848cfa 100644 --- a/packages/hoist/package.json +++ b/packages/hoist/package.json @@ -20,7 +20,6 @@ }, "devDependencies": { "@pnpm/logger": "2.1.1", - "@types/micromatch": "3.1.0", "@types/ramda": "0.26.21", "rimraf": "3.0.0" }, @@ -52,7 +51,6 @@ "@pnpm/symlink-dependency": "workspace:2.0.6", "@pnpm/types": "workspace:3.2.0", "dependency-path": "workspace:3.0.8", - "micromatch": "4.0.2", "ramda": "0.26.1" } } diff --git a/packages/hoist/src/index.ts b/packages/hoist/src/index.ts index 9de463dc1e..3566b984d1 100644 --- a/packages/hoist/src/index.ts +++ b/packages/hoist/src/index.ts @@ -11,12 +11,11 @@ import pkgIdToFilename from '@pnpm/pkgid-to-filename' import symlinkDependency from '@pnpm/symlink-dependency' import { Registries } from '@pnpm/types' import * as dp from 'dependency-path' -import { matcher } from 'micromatch' import path = require('path') import R = require('ramda') export default async function hoistByLockfile ( - hoistPattern: string, + match: (dependencyName: string) => boolean, opts: { getIndependentPackageLocation?: (packageId: string, packageName: string) => Promise, lockfile: Lockfile, @@ -51,8 +50,8 @@ export default async function hoistByLockfile ( const aliasesByDependencyPath = await hoistGraph(deps, opts.lockfile.importers['.'].specifiers, { dryRun: false, + match, modulesDir: opts.modulesDir, - pattern: hoistPattern, }) const bin = path.join(opts.modulesDir, '.bin') @@ -142,14 +141,13 @@ async function hoistGraph ( depNodes: Dependency[], currentSpecifiers: {[alias: string]: string}, opts: { + match: (dependencyName: string) => boolean, modulesDir: string, dryRun: boolean, - pattern: string, }, ): Promise<{[alias: string]: string[]}> { const hoistedAliases = new Set(R.keys(currentSpecifiers)) const aliasesByDependencyPath: {[depPath: string]: string[]} = {} - const match = opts.pattern === '*' ? () => true : matcher(opts.pattern) await Promise.all(depNodes // sort by depth and then alphabetically @@ -160,7 +158,7 @@ async function hoistGraph ( // build the alias map and the id map .map((depNode) => { for (const childAlias of Object.keys(depNode.children)) { - if (!match(childAlias)) continue + if (!opts.match(childAlias)) continue // if this alias has already been taken, skip it if (hoistedAliases.has(childAlias)) { continue diff --git a/packages/list/package.json b/packages/list/package.json index 2183fd900e..2601e507bb 100644 --- a/packages/list/package.json +++ b/packages/list/package.json @@ -36,6 +36,7 @@ }, "homepage": "https://github.com/pnpm/pnpm/blob/master/packages/list#readme", "dependencies": { + "@pnpm/matcher": "workspace:0.0.0", "@pnpm/read-importer-manifest": "workspace:1.0.9", "@pnpm/read-package-json": "workspace:2.0.3", "@pnpm/types": "workspace:3.2.0", @@ -45,7 +46,8 @@ "cli-columns": "3.1.2", "dependencies-hierarchy": "workspace:7.0.0-3", "p-limit": "2.2.1", - "ramda": "0.26.1" + "ramda": "0.26.1", + "semver": "6.3.0" }, "devDependencies": { "@pnpm/list": "link:", @@ -53,6 +55,7 @@ "@types/archy": "0.0.31", "@types/common-tags": "1.8.0", "@types/ramda": "0.26.21", + "@types/semver": "6", "common-tags": "1.8.0", "tape": "4.11.0" } diff --git a/packages/list/src/createPackagesSearcher.ts b/packages/list/src/createPackagesSearcher.ts new file mode 100644 index 0000000000..81ed555109 --- /dev/null +++ b/packages/list/src/createPackagesSearcher.ts @@ -0,0 +1,43 @@ +import matcher from '@pnpm/matcher' +import npa = require('@zkochan/npm-package-arg') +import { SearchFunction } from 'dependencies-hierarchy' +import semver = require('semver') + +export default function createPatternSearcher (queries: string[]) { + const searchers: SearchFunction[] = queries + .map(parseSearchQuery) + .map((packageSelector) => search.bind(null, packageSelector)) + return (pkg: { name: string, version: string }) => searchers.some((search) => search(pkg)) +} + +type MatchFunction = (entry: string) => boolean + +function search ( + packageSelector: { + matchName: MatchFunction, + matchVersion?: MatchFunction, + }, + pkg: { name: string, version: string }, +) { + if (!packageSelector.matchName(pkg.name)) { + return false + } + if (!packageSelector.matchVersion) { + return true + } + return !pkg.version.startsWith('link:') && packageSelector.matchVersion(pkg.version) +} + +function parseSearchQuery (query: string) { + const parsed = npa(query) + if (parsed.raw === parsed.name) { + return { matchName: matcher(parsed.name) } + } + if (parsed.type !== 'version' && parsed.type !== 'range') { + throw new Error(`Invalid queryument - ${query}. List can search only by version or range`) + } + return { + matchName: matcher(parsed.name), + matchVersion: (version: string) => semver.satisfies(version, parsed.fetchSpec), + } +} diff --git a/packages/list/src/index.ts b/packages/list/src/index.ts index 39ad52ba9f..86330fcc97 100644 --- a/packages/list/src/index.ts +++ b/packages/list/src/index.ts @@ -1,11 +1,8 @@ import { readImporterManifestOnly } from '@pnpm/read-importer-manifest' import { DependenciesField, Registries } from '@pnpm/types' -import npa = require('@zkochan/npm-package-arg') -import dh, { - forPackages as dhForPackages, - PackageSelector, -} from 'dependencies-hierarchy' +import dh from 'dependencies-hierarchy' import R = require('ramda') +import createPackagesSearcher from './createPackagesSearcher' import renderJson from './renderJson' import renderParseable from './renderParseable' import renderTree from './renderTree' @@ -34,26 +31,15 @@ export async function forPackages ( ) { const opts = { ...DEFAULTS, ...maybeOpts } - const searched: PackageSelector[] = packages.map((arg) => { - const parsed = npa(arg) - if (parsed.raw === parsed.name) { - return parsed.name - } - if (parsed.type !== 'version' && parsed.type !== 'range') { - throw new Error(`Invalid argument - ${arg}. List can search only by version or range`) - } - return { - name: parsed.name, - range: parsed.fetchSpec, - } - }) + const search = createPackagesSearcher(packages) const pkgs = await Promise.all( - R.toPairs(await dhForPackages(searched, projectPaths, { + R.toPairs(await dh(projectPaths, { depth: opts.depth, include: maybeOpts && maybeOpts.include, lockfileDirectory: maybeOpts && maybeOpts.lockfileDirectory, registries: opts.registries, + search, })) .map(async ([projectPath, dependenciesHierarchy]) => { const entryPkg = await readImporterManifestOnly(projectPath) diff --git a/packages/list/test/createPackagesSearcher.spec.ts b/packages/list/test/createPackagesSearcher.spec.ts new file mode 100644 index 0000000000..3669e8584c --- /dev/null +++ b/packages/list/test/createPackagesSearcher.spec.ts @@ -0,0 +1,27 @@ +import createPackagesSearcher from '@pnpm/list/lib/createPackagesSearcher' +import test = require('tape') + +test('packages searcher', (t) => { + { + const search = createPackagesSearcher(['rimraf@*']) + t.ok(search({ name: 'rimraf', version: '1.0.0' })) + t.notOk(search({ name: 'express', version: '1.0.0' })) + } + { + const search = createPackagesSearcher(['rim*']) + t.ok(search({ name: 'rimraf', version: '1.0.0' })) + t.notOk(search({ name: 'express', version: '1.0.0' })) + } + { + const search = createPackagesSearcher(['rim*@2']) + t.ok(search({ name: 'rimraf', version: '2.0.0' })) + t.notOk(search({ name: 'rimraf', version: '1.0.0' })) + } + { + const search = createPackagesSearcher(['minimatch', 'once@1.4']) + t.ok(search({ name: 'minimatch', version: '2.0.0' })) + t.ok(search({ name: 'once', version: '1.4.1' })) + t.notOk(search({ name: 'rimraf', version: '1.0.0' })) + } + t.end() +}) diff --git a/packages/list/test/index.ts b/packages/list/test/index.ts index 8b730e8eaf..078b7667f6 100644 --- a/packages/list/test/index.ts +++ b/packages/list/test/index.ts @@ -6,6 +6,7 @@ import cliColumns = require('cli-columns') import { stripIndent } from 'common-tags' import path = require('path') import test = require('tape') +import './createPackagesSearcher.spec.ts' const DEV_DEP_ONLY_CLR = chalk.yellow const PROD_DEP_CLR = (s: string) => s // just use the default color diff --git a/packages/matcher/README.md b/packages/matcher/README.md new file mode 100644 index 0000000000..215177d177 --- /dev/null +++ b/packages/matcher/README.md @@ -0,0 +1,23 @@ +# @pnpm/matcher + +> A simple pattern matcher for pnpm + +## Install + +``` + add @pnpm/matcher +``` + +## Usage + +```ts +import matcher from '@pnpm/matcher' + +const match = matcher(['eslint-*']) +match('eslint-plugin-foo') +//> true +``` + +## License + +[MIT](LICENSE) diff --git a/packages/matcher/package.json b/packages/matcher/package.json new file mode 100644 index 0000000000..0717b15c3b --- /dev/null +++ b/packages/matcher/package.json @@ -0,0 +1,44 @@ +{ + "name": "@pnpm/matcher", + "version": "0.0.0", + "description": "A simple pattern matcher for pnpm", + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "engines": { + "node": ">=10" + }, + "files": [ + "lib/" + ], + "scripts": { + "lint": "tslint -c tslint.json src/**/*.ts test/**/*.ts", + "test": "pnpm run tsc && pnpm run lint && ts-node test --type-check", + "tsc": "rimraf lib && tsc", + "prepublishOnly": "pnpm run tsc" + }, + "repository": "https://github.com/pnpm/pnpm/blob/master/packages/matcher", + "keywords": [ + "pnpm", + "match", + "wildcard", + "pattern" + ], + "author": { + "name": "Zoltan Kochan", + "email": "z@kochan.io", + "url": "https://www.kochan.io" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/pnpm/pnpm/issues" + }, + "homepage": "https://github.com/pnpm/pnpm/blob/master/packages/matcher#readme", + "devDependencies": { + "@pnpm/matcher": "link:", + "rimraf": "3.0.0", + "tape": "4.11.0" + }, + "dependencies": { + "escape-string-regexp": "2.0.0" + } +} diff --git a/packages/matcher/src/index.ts b/packages/matcher/src/index.ts new file mode 100644 index 0000000000..ed122dde82 --- /dev/null +++ b/packages/matcher/src/index.ts @@ -0,0 +1,22 @@ +import escapeStringRegexp = require('escape-string-regexp') + +export default function matcher (patterns: string[] | string) { + if (typeof patterns === 'string') return matcherFromPattern(patterns) + if (patterns.length === 0) return matcherFromPattern(patterns[0]) + const matchArr = patterns.map(matcherFromPattern) + return (input: string) => matchArr.some((match) => match(input)) +} + +function matcherFromPattern (pattern: string) { + if (pattern === '*') { + return () => true + } + + const escapedPattern = escapeStringRegexp(pattern).replace(/\\\*/g, '.*') + if (escapedPattern === pattern) { + return (input: string) => input === pattern + } + + const regexp = new RegExp(`^${escapedPattern}$`) + return (input: string) => regexp.test(input) +} diff --git a/packages/matcher/test/index.ts b/packages/matcher/test/index.ts new file mode 100644 index 0000000000..06c36348d3 --- /dev/null +++ b/packages/matcher/test/index.ts @@ -0,0 +1,35 @@ +import matcher from '@pnpm/matcher' +import test = require('tape') + +test('matcher()', (t) => { + { + const match = matcher('*') + t.ok(match('@eslint/plugin-foo')) + t.ok(match('express')) + } + { + const match = matcher(['eslint-*']) + t.ok(match('eslint-plugin-foo')) + t.notOk(match('express')) + } + { + const match = matcher(['*plugin*']) + t.ok(match('@eslint/plugin-foo')) + t.notOk(match('express')) + } + { + const match = matcher(['a*c']) + t.ok(match('abc')) + } + { + const match = matcher(['*-positive']) + t.ok(match('is-positive')) + } + { + const match = matcher(['foo', 'bar']) + t.ok(match('foo')) + t.ok(match('bar')) + t.notOk(match('express')) + } + t.end() +}) diff --git a/packages/matcher/tsconfig.json b/packages/matcher/tsconfig.json new file mode 100644 index 0000000000..44610611db --- /dev/null +++ b/packages/matcher/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@pnpm/tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": [ + "src/**/*.ts", + "../../typings/**/*.d.ts" + ] +} diff --git a/packages/matcher/tslint.json b/packages/matcher/tslint.json new file mode 100644 index 0000000000..568acfabec --- /dev/null +++ b/packages/matcher/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "@pnpm/tslint-config" +} diff --git a/packages/modules-yaml/src/index.ts b/packages/modules-yaml/src/index.ts index b5cf812884..e8bd61a1b3 100644 --- a/packages/modules-yaml/src/index.ts +++ b/packages/modules-yaml/src/index.ts @@ -13,7 +13,7 @@ export type IncludedDependencies = { export interface Modules { hoistedAliases: {[depPath: string]: string[]} - hoistPattern?: string + hoistPattern?: string[] included: IncludedDependencies, independentLeaves: boolean, layoutVersion: number, diff --git a/packages/outdated/package.json b/packages/outdated/package.json index 7eb41f27d2..5b099795b3 100644 --- a/packages/outdated/package.json +++ b/packages/outdated/package.json @@ -43,14 +43,12 @@ "@pnpm/lockfile-file": "workspace:2.0.0-0", "@pnpm/lockfile-utils": "workspace:1.0.10", "@pnpm/types": "workspace:3.2.0", - "dependency-path": "workspace:3.0.8", - "micromatch": "4.0.2" + "dependency-path": "workspace:3.0.8" }, "devDependencies": { "@pnpm/logger": "2.1.1", "@pnpm/outdated": "link:", "@pnpm/registry-mock": "1.5.0", - "@types/micromatch": "3.1.0", "npm-run-all": "4.1.5", "tape": "4.11.0" } diff --git a/packages/outdated/src/index.ts b/packages/outdated/src/index.ts index 214b730926..afbee19e63 100644 --- a/packages/outdated/src/index.ts +++ b/packages/outdated/src/index.ts @@ -11,7 +11,6 @@ import { PackageManifest, } from '@pnpm/types' import * as dp from 'dependency-path' -import { isMatch } from 'micromatch' export type GetLatestManifestFunction = (packageName: string) => Promise @@ -24,43 +23,16 @@ export interface OutdatedPackage { wanted: string, } -export default async function ( +export default async function outdated ( opts: { currentLockfile: Lockfile | null, + match?: (dependencyName: string) => boolean, manifest: ImporterManifest, prefix: string, getLatestManifest: GetLatestManifestFunction, lockfileDirectory: string, wantedLockfile: Lockfile, }, -) { - return _outdated([], opts) -} - -export async function forPackages ( - packages: string[], - opts: { - currentLockfile: Lockfile | null, - manifest: ImporterManifest, - prefix: string, - getLatestManifest: GetLatestManifestFunction, - lockfileDirectory: string, - wantedLockfile: Lockfile, - }, -) { - return _outdated(packages, opts) -} - -async function _outdated ( - forPkgs: string[], - opts: { - manifest: ImporterManifest, - prefix: string, - currentLockfile: Lockfile | null, - getLatestManifest: GetLatestManifestFunction, - lockfileDirectory: string, - wantedLockfile: Lockfile, - }, ): Promise { if (packageHasNoDeps(opts.manifest)) return [] const importerId = getLockfileImporterId(opts.lockfileDirectory, opts.prefix) @@ -74,8 +46,8 @@ async function _outdated ( let pkgs = Object.keys(opts.wantedLockfile.importers[importerId][depType]!) - if (forPkgs.length) { - pkgs = pkgs.filter((pkgName) => forPkgs.some((forPkg) => isMatch(pkgName, forPkg))) + if (opts.match) { + pkgs = pkgs.filter((pkgName) => opts.match!(pkgName)) } await Promise.all( diff --git a/packages/outdated/test/index.ts b/packages/outdated/test/index.ts index 98b0688a97..78547f002f 100644 --- a/packages/outdated/test/index.ts +++ b/packages/outdated/test/index.ts @@ -1,5 +1,5 @@ /// -import outdated, { forPackages as outdatedForPackages } from '@pnpm/outdated' +import outdated from '@pnpm/outdated' import test = require('tape') async function getLatestManifest (packageName: string) { @@ -232,124 +232,8 @@ test('outdated() should return deprecated package even if its current version is t.end() }) -test('forPackages()', async (t) => { - const outdatedPkgs = await outdatedForPackages(['is-negative'], { - currentLockfile: { - importers: { - '.': { - dependencies: { - 'from-github': 'github.com/blabla/from-github/d5f8d5500f7faf593d32e134c1b0043ff69151b4', - 'is-negative': '1.0.0', - 'is-positive': '1.0.0', - 'linked-1': 'link:../linked-1', - 'linked-2': 'file:../linked-2', - }, - specifiers: { - 'is-negative': '^2.1.0', - 'is-positive': '^1.0.0', - }, - }, - }, - lockfileVersion: 5, - packages: { - '/is-negative/2.1.0': { - resolution: { - integrity: 'sha1-8Nhjd6oVpkw0lh84rCqb4rQKEYc=', - }, - }, - '/is-positive/1.0.0': { - resolution: { - integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=', - }, - }, - 'github.com/blabla/from-github/d5f8d5500f7faf593d32e134c1b0043ff69151b4': { - name: 'from-github', - version: '1.1.0', - - resolution: { - tarball: 'https://codeload.github.com/blabla/from-github/tar.gz/d5f8d5500f7faf593d32e134c1b0043ff69151b3', - }, - }, - }, - }, - getLatestManifest, - lockfileDirectory: 'wanted-shrinkwrap', - manifest: { - name: 'wanted-shrinkwrap', - version: '1.0.0', - - dependencies: { - 'is-negative': '^2.1.0', - 'is-positive': '^3.1.0', - }, - }, - prefix: 'wanted-shrinkwrap', - wantedLockfile: { - importers: { - '.': { - dependencies: { - 'from-github': 'github.com/blabla/from-github/d5f8d5500f7faf593d32e134c1b0043ff69151b3', - 'from-github-2': 'github.com/blabla/from-github-2/d5f8d5500f7faf593d32e134c1b0043ff69151b3', - 'is-negative': '1.1.0', - 'is-positive': '3.1.0', - 'linked-1': 'link:../linked-1', - 'linked-2': 'file:../linked-2', - }, - specifiers: { - 'is-negative': '^2.1.0', - 'is-positive': '^3.1.0', - }, - }, - }, - lockfileVersion: 5, - packages: { - '/is-negative/1.1.0': { - resolution: { - integrity: 'sha1-8Nhjd6oVpkw0lh84rCqb4rQKEYc=', - }, - }, - '/is-positive/3.1.0': { - resolution: { - integrity: 'sha1-hX21hKG6XRyymAUn/DtsQ103sP0=', - }, - }, - 'github.com/blabla/from-github-2/d5f8d5500f7faf593d32e134c1b0043ff69151b3': { - name: 'from-github-2', - version: '1.0.0', - - resolution: { - tarball: 'https://codeload.github.com/blabla/from-github-2/tar.gz/d5f8d5500f7faf593d32e134c1b0043ff69151b3', - }, - }, - 'github.com/blabla/from-github/d5f8d5500f7faf593d32e134c1b0043ff69151b3': { - name: 'from-github', - version: '1.0.0', - - resolution: { - tarball: 'https://codeload.github.com/blabla/from-github/tar.gz/d5f8d5500f7faf593d32e134c1b0043ff69151b3', - }, - }, - }, - }, - }) - t.deepEqual(outdatedPkgs, [ - { - alias: 'is-negative', - belongsTo: 'dependencies', - current: '1.0.0', - latestManifest: { - name: 'is-negative', - version: '2.1.0', - }, - packageName: 'is-negative', - wanted: '1.1.0', - }, - ]) - t.end() -}) - -test('forPackages() by pattern', async (t) => { - const outdatedPkgs = await outdatedForPackages(['*-negative'], { +test('using a matcher', async (t) => { + const outdatedPkgs = await outdated({ currentLockfile: { importers: { '.': { @@ -399,6 +283,7 @@ test('forPackages() by pattern', async (t) => { 'is-positive': '^3.1.0', }, }, + match: (dependencyName) => dependencyName === 'is-negative', prefix: 'wanted-shrinkwrap', wantedLockfile: { importers: { diff --git a/packages/pnpm/package.json b/packages/pnpm/package.json index c2cb57801f..437e4ea985 100644 --- a/packages/pnpm/package.json +++ b/packages/pnpm/package.json @@ -33,6 +33,7 @@ "@pnpm/list": "workspace:3.0.0-3", "@pnpm/lockfile-file": "workspace:2.0.0-0", "@pnpm/logger": "2.1.1", + "@pnpm/matcher": "workspace:0.0.0", "@pnpm/outdated": "workspace:5.0.0-0", "@pnpm/package-is-installable": "workspace:2.2.1", "@pnpm/package-store": "workspace:5.0.0-1", @@ -73,7 +74,6 @@ "lru-cache": "5.1.1", "make-dir": "3.0.0", "mem": "5.1.1", - "micromatch": "4.0.2", "mz": "2.7.0", "nopt": "4.0.1", "p-filter": "2.1.0", @@ -106,7 +106,6 @@ "@pnpm/write-importer-manifest": "link:../write-importer-manifest", "@types/byline": "4.2.31", "@types/common-tags": "1.8.0", - "@types/micromatch": "3.1.0", "@types/mkdirp": "0.5.2", "@types/nopt": "3.0.29", "@types/ramda": "0.26.21", diff --git a/packages/pnpm/src/cmd/outdated.ts b/packages/pnpm/src/cmd/outdated.ts index 7a527ad089..991da0d63b 100644 --- a/packages/pnpm/src/cmd/outdated.ts +++ b/packages/pnpm/src/cmd/outdated.ts @@ -4,9 +4,8 @@ import { readCurrentLockfile, readWantedLockfile, } from '@pnpm/lockfile-file' -import outdated, { - forPackages as outdatedForPackages, OutdatedPackage, -} from '@pnpm/outdated' +import matcher from '@pnpm/matcher' +import outdated, { OutdatedPackage } from '@pnpm/outdated' import semverDiff, { SEMVER_CHANGE } from '@pnpm/semver-diff' import storePath from '@pnpm/store-path' import { PackageJson, Registries } from '@pnpm/types' @@ -283,19 +282,18 @@ export async function outdatedDependenciesOfWorkspacePackages ( store, }) return Promise.all(pkgs.map(async ({ manifest, path }) => { - const optsForOutdated = { - currentLockfile, - getLatestManifest, - lockfileDirectory, - manifest, - prefix: path, - wantedLockfile, - } + let match = args.length && matcher(args) || undefined return { manifest, - outdatedPackages: args.length - ? await outdatedForPackages(args, optsForOutdated) - : await outdated(optsForOutdated), + outdatedPackages: await outdated({ + currentLockfile, + getLatestManifest, + lockfileDirectory, + manifest, + match, + prefix: path, + wantedLockfile, + }), prefix: getLockfileImporterId(lockfileDirectory, path), } })) diff --git a/packages/pnpm/src/cmd/recursive/filter.ts b/packages/pnpm/src/cmd/recursive/filter.ts index f8a6f843d1..f2074b5be0 100644 --- a/packages/pnpm/src/cmd/recursive/filter.ts +++ b/packages/pnpm/src/cmd/recursive/filter.ts @@ -1,5 +1,5 @@ +import matcher from '@pnpm/matcher' import isSubdir = require('is-subdir') -import { matcher } from 'micromatch' import { PackageNode } from 'pkgs-graph' import R = require('ramda') import { PackageSelector } from '../../parsePackageSelectors' diff --git a/packages/pnpm/test/link.ts b/packages/pnpm/test/link.ts index 90a9234699..e5fa345fbe 100644 --- a/packages/pnpm/test/link.ts +++ b/packages/pnpm/test/link.ts @@ -43,7 +43,7 @@ test('linking multiple packages', async (t: tape.Test) => { project.has('linked-bar') const modules = await readYamlFile('../linked-bar/node_modules/.modules.yaml') - t.equal(modules['hoistPattern'], '*', 'the linked package used its own configs during installation') // tslint:disable-line:no-string-literal + t.deepEqual(modules['hoistPattern'], ['*'], 'the linked package used its own configs during installation') // tslint:disable-line:no-string-literal }) test('link global bin', async function (t: tape.Test) { diff --git a/packages/pnpm/test/recursive/misc.ts b/packages/pnpm/test/recursive/misc.ts index 3ea421d388..f3b3e742c6 100644 --- a/packages/pnpm/test/recursive/misc.ts +++ b/packages/pnpm/test/recursive/misc.ts @@ -253,7 +253,7 @@ test('recursive installation with package-specific .npmrc', async t => { t.ok(projects['project-2'].requireModule('is-negative')) const modulesYaml1 = await readYamlFile<{ hoistPattern: string }>(path.resolve('project-1', 'node_modules', '.modules.yaml')) - t.equal(modulesYaml1 && modulesYaml1.hoistPattern, '*') + t.deepEqual(modulesYaml1 && modulesYaml1.hoistPattern, ['*']) const modulesYaml2 = await readYamlFile<{ hoistPattern: string }>(path.resolve('project-2', 'node_modules', '.modules.yaml')) t.notOk(modulesYaml2 && modulesYaml2.hoistPattern) @@ -296,7 +296,7 @@ test('workspace .npmrc is always read', async (t: tape.Test) => { t.ok(projects['project-1'].requireModule('is-positive')) const modulesYaml1 = await readYamlFile<{ hoistPattern: string }>(path.resolve('node_modules', '.modules.yaml')) - t.equal(modulesYaml1 && modulesYaml1.hoistPattern, '*') + t.deepEqual(modulesYaml1 && modulesYaml1.hoistPattern, ['*']) process.chdir('..') process.chdir('project-2') diff --git a/packages/read-importers-context/src/index.ts b/packages/read-importers-context/src/index.ts index 4296d151dc..7e138cb24f 100644 --- a/packages/read-importers-context/src/index.ts +++ b/packages/read-importers-context/src/index.ts @@ -16,7 +16,7 @@ export default async function ( importers: (ImporterOptions & T)[], lockfileDirectory: string, ): Promise<{ - currentHoistPattern?: string, + currentHoistPattern?: string[], hoist?: boolean, hoistedAliases: { [depPath: string]: string[] }, importers: Array<{ diff --git a/packages/supi/package.json b/packages/supi/package.json index 12d8062029..cffcd0c770 100644 --- a/packages/supi/package.json +++ b/packages/supi/package.json @@ -32,6 +32,7 @@ "@pnpm/link-bins": "4.4.0", "@pnpm/lockfile-file": "workspace:2.0.0-0", "@pnpm/lockfile-utils": "workspace:1.0.10", + "@pnpm/matcher": "workspace:0.0.0", "@pnpm/modules-cleaner": "workspace:5.0.0-2", "@pnpm/modules-yaml": "workspace:4.0.0-1", "@pnpm/package-requester": "workspace:8.0.0-0", diff --git a/packages/supi/src/getContext/index.ts b/packages/supi/src/getContext/index.ts index 72dd488d78..07181a7c0b 100644 --- a/packages/supi/src/getContext/index.ts +++ b/packages/supi/src/getContext/index.ts @@ -16,6 +16,7 @@ import { import rimraf = require('@zkochan/rimraf') import makeDir = require('make-dir') import path = require('path') +import R = require('ramda') import checkCompatibility from './checkCompatibility' import readLockfileFile from './readLockfiles' @@ -34,7 +35,7 @@ export interface PnpmContext { modulesFile: Modules | null, pendingBuilds: string[], rootModulesDir: string, - hoistPattern: string | undefined, + hoistPattern: string[] | undefined, hoistedModulesDir: string, lockfileDirectory: string, virtualStoreDir: string, @@ -69,7 +70,7 @@ export default async function getContext ( independentLeaves?: boolean, forceIndependentLeaves?: boolean, - hoistPattern?: string | undefined, + hoistPattern?: string[] | undefined, forceHoistPattern?: boolean, shamefullyHoist?: boolean, @@ -164,7 +165,7 @@ async function validateNodeModules ( prefix: string, }>, opts: { - currentHoistPattern?: string, + currentHoistPattern?: string[], force: boolean, include?: IncludedDependencies, lockfileDirectory: string, @@ -173,7 +174,7 @@ async function validateNodeModules ( independentLeaves?: boolean, forceIndependentLeaves?: boolean, - hoistPattern?: string | undefined, + hoistPattern?: string[] | undefined, forceHoistPattern?: boolean, shamefullyHoist?: boolean | undefined, @@ -220,7 +221,7 @@ async function validateNodeModules ( } if (opts.forceHoistPattern && rootImporter) { try { - if (opts.currentHoistPattern !== (opts.hoistPattern || undefined)) { + if (!R.equals(opts.currentHoistPattern, (opts.hoistPattern || undefined))) { if (opts.currentHoistPattern) { throw new PnpmError( 'HOISTING_WANTED', @@ -287,7 +288,7 @@ export interface PnpmSingleContext { extraBinPaths: string[], hoistedAliases: {[depPath: string]: string[]}, hoistedModulesDir: string, - hoistPattern: string | undefined, + hoistPattern: string[] | undefined, manifest: ImporterManifest, modulesDir: string, importerId: string, @@ -322,7 +323,7 @@ export async function getContextForSingleImporter ( store: string, useLockfile: boolean, - hoistPattern?: string | undefined, + hoistPattern?: string[] | undefined, forceHoistPattern?: boolean, shamefullyHoist?: boolean, diff --git a/packages/supi/src/install/extendInstallOptions.ts b/packages/supi/src/install/extendInstallOptions.ts index b7f8ab02f6..b61b4417a6 100644 --- a/packages/supi/src/install/extendInstallOptions.ts +++ b/packages/supi/src/install/extendInstallOptions.ts @@ -62,7 +62,7 @@ export interface StrictInstallOptions { bin: string, prefix: string, - hoistPattern: string | undefined, + hoistPattern: string[] | undefined, forceHoistPattern: boolean, shamefullyHoist: boolean, diff --git a/packages/supi/src/install/link.ts b/packages/supi/src/install/link.ts index 70770a80ca..53a99c22aa 100644 --- a/packages/supi/src/install/link.ts +++ b/packages/supi/src/install/link.ts @@ -14,6 +14,7 @@ import { import hoist from '@pnpm/hoist' import { Lockfile } from '@pnpm/lockfile-file' import logger from '@pnpm/logger' +import matcher from '@pnpm/matcher' import { prune } from '@pnpm/modules-cleaner' import { IncludedDependencies } from '@pnpm/modules-yaml' import { DependenciesTree, LinkedDependency } from '@pnpm/resolve-dependencies' @@ -62,7 +63,7 @@ export default async function linkPackages ( force: boolean, hoistedAliases: {[depPath: string]: string[]}, hoistedModulesDir: string, - hoistPattern?: string, + hoistPattern?: string[], include: IncludedDependencies, independentLeaves: boolean, lockfileDirectory: string, @@ -309,7 +310,7 @@ export default async function linkPackages ( if (newDepPaths.length > 0 || removedDepPaths.size > 0) { const rootImporterWithFlatModules = opts.hoistPattern && importers.find(({ id }) => id === '.') if (rootImporterWithFlatModules) { - newHoistedAliases = await hoist(opts.hoistPattern!, { + newHoistedAliases = await hoist(matcher(opts.hoistPattern!), { getIndependentPackageLocation: opts.independentLeaves ? async (packageId: string, packageName: string) => { const { directory } = await opts.storeController.getPackageLocation(packageId, packageName, { diff --git a/packages/supi/src/link/options.ts b/packages/supi/src/link/options.ts index 78c8e59410..c4d3016dde 100644 --- a/packages/supi/src/link/options.ts +++ b/packages/supi/src/link/options.ts @@ -21,7 +21,7 @@ interface StrictLinkOptions { store: string, reporter: ReporterFunction, - hoistPattern: string | undefined, + hoistPattern: string[] | undefined, forceHoistPattern: boolean, shamefullyHoist: boolean, diff --git a/packages/supi/src/rebuild/extendRebuildOptions.ts b/packages/supi/src/rebuild/extendRebuildOptions.ts index 1a2d4ec5a8..d03b7614c0 100644 --- a/packages/supi/src/rebuild/extendRebuildOptions.ts +++ b/packages/supi/src/rebuild/extendRebuildOptions.ts @@ -31,7 +31,6 @@ export interface StrictRebuildOptions { }, unsafePerm: boolean, pending: boolean, - hoistPattern: string | undefined, shamefullyHoist: boolean, } @@ -51,7 +50,6 @@ const defaults = async (opts: RebuildOptions) => { development: true, force: false, forceSharedLockfile: false, - hoistPattern: undefined, lockfileDirectory, optional: true, packageManager, diff --git a/packages/supi/src/rebuild/index.ts b/packages/supi/src/rebuild/index.ts index 241145927c..80ce36580e 100644 --- a/packages/supi/src/rebuild/index.ts +++ b/packages/supi/src/rebuild/index.ts @@ -170,7 +170,7 @@ export async function rebuild ( await writeModulesYaml(ctx.rootModulesDir, { ...ctx.modulesFile, hoistedAliases: ctx.hoistedAliases, - hoistPattern: opts.hoistPattern, + hoistPattern: ctx.hoistPattern, included: ctx.include, independentLeaves: ctx.independentLeaves, layoutVersion: LAYOUT_VERSION, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 694668ba7d..d02c0bb4ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,15 +208,11 @@ importers: '@pnpm/types': 'link:../types' '@pnpm/utils': 'link:../utils' dependency-path: 'link:../dependency-path' - micromatch: 4.0.2 normalize-path: 3.0.0 resolve-link-target: 1.0.1 - semver: 6.3.0 devDependencies: '@pnpm/constants': 'link:../constants' '@pnpm/logger': 2.1.1 - '@types/micromatch': 3.1.0 - '@types/semver': 6.0.2 dependencies-hierarchy: 'link:' tape: 4.11.0 specifiers: @@ -228,14 +224,10 @@ importers: '@pnpm/read-modules-dir': 'workspace:2.0.1' '@pnpm/types': 'workspace:3.2.0' '@pnpm/utils': 'workspace:0.10.6' - '@types/micromatch': 3.1.0 - '@types/semver': '6' dependencies-hierarchy: 'link:' dependency-path: 'workspace:3.0.8' - micromatch: 4.0.2 normalize-path: 3.0.0 resolve-link-target: 1.0.1 - semver: 6.3.0 tape: 4.11.0 packages/dependency-path: dependencies: @@ -408,6 +400,7 @@ importers: '@pnpm/link-bins': 4.4.0 '@pnpm/lockfile-file': 'link:../lockfile-file' '@pnpm/lockfile-utils': 'link:../lockfile-utils' + '@pnpm/matcher': 'link:../matcher' '@pnpm/modules-cleaner': 'link:../modules-cleaner' '@pnpm/modules-yaml': 'link:../modules-yaml' '@pnpm/package-requester': 'link:../package-requester' @@ -465,6 +458,7 @@ importers: '@pnpm/lockfile-file': 'workspace:2.0.0-0' '@pnpm/lockfile-utils': 'workspace:1.0.10' '@pnpm/logger': 2.1.1 + '@pnpm/matcher': 'workspace:0.0.0' '@pnpm/modules-cleaner': 'workspace:5.0.0-2' '@pnpm/modules-yaml': 'workspace:4.0.0-1' '@pnpm/package-requester': 'workspace:8.0.0-0' @@ -510,11 +504,9 @@ importers: '@pnpm/symlink-dependency': 'link:../symlink-dependency' '@pnpm/types': 'link:../types' dependency-path: 'link:../dependency-path' - micromatch: 4.0.2 ramda: 0.26.1 devDependencies: '@pnpm/logger': 2.1.1 - '@types/micromatch': 3.1.0 '@types/ramda': 0.26.21 rimraf: 3.0.0 specifiers: @@ -526,10 +518,8 @@ importers: '@pnpm/pkgid-to-filename': 2.0.0 '@pnpm/symlink-dependency': 'workspace:2.0.6' '@pnpm/types': 'workspace:3.2.0' - '@types/micromatch': 3.1.0 '@types/ramda': 0.26.21 dependency-path: 'workspace:3.0.8' - micromatch: 4.0.2 ramda: 0.26.1 rimraf: 3.0.0 packages/lifecycle: @@ -564,6 +554,7 @@ importers: tape: 4.11.0 packages/list: dependencies: + '@pnpm/matcher': 'link:../matcher' '@pnpm/read-importer-manifest': 'link:../read-importer-manifest' '@pnpm/read-package-json': 'link:../read-package-json' '@pnpm/types': 'link:../types' @@ -574,23 +565,27 @@ importers: dependencies-hierarchy: 'link:../dependencies-hierarchy' p-limit: 2.2.1 ramda: 0.26.1 + semver: 6.3.0 devDependencies: '@pnpm/list': 'link:' '@pnpm/logger': 2.1.1 '@types/archy': 0.0.31 '@types/common-tags': 1.8.0 '@types/ramda': 0.26.21 + '@types/semver': 6.0.2 common-tags: 1.8.0 tape: 4.11.0 specifiers: '@pnpm/list': 'link:' '@pnpm/logger': 2.1.1 + '@pnpm/matcher': 'workspace:0.0.0' '@pnpm/read-importer-manifest': 'workspace:1.0.9' '@pnpm/read-package-json': 'workspace:2.0.3' '@pnpm/types': 'workspace:3.2.0' '@types/archy': 0.0.31 '@types/common-tags': 1.8.0 '@types/ramda': 0.26.21 + '@types/semver': '6' '@zkochan/npm-package-arg': 1.0.2 archy: 1.0.0 chalk: 2.4.2 @@ -599,6 +594,7 @@ importers: dependencies-hierarchy: 'workspace:7.0.0-3' p-limit: 2.2.1 ramda: 0.26.1 + semver: 6.3.0 tape: 4.11.0 packages/local-resolver: dependencies: @@ -704,6 +700,18 @@ importers: tempy: 0.3.0 write-yaml-file: 3.0.1 yaml-tag: 1.1.0 + packages/matcher: + dependencies: + escape-string-regexp: 2.0.0 + devDependencies: + '@pnpm/matcher': 'link:' + rimraf: 3.0.0 + tape: 4.11.0 + specifiers: + '@pnpm/matcher': 'link:' + escape-string-regexp: 2.0.0 + rimraf: 3.0.0 + tape: 4.11.0 packages/modules-cleaner: dependencies: '@pnpm/core-loggers': 'link:../core-loggers' @@ -847,12 +855,10 @@ importers: '@pnpm/lockfile-utils': 'link:../lockfile-utils' '@pnpm/types': 'link:../types' dependency-path: 'link:../dependency-path' - micromatch: 4.0.2 devDependencies: '@pnpm/logger': 2.1.1 '@pnpm/outdated': 'link:' '@pnpm/registry-mock': 1.5.0 - '@types/micromatch': 3.1.0 npm-run-all: 4.1.5 tape: 4.11.0 specifiers: @@ -863,9 +869,7 @@ importers: '@pnpm/outdated': 'link:' '@pnpm/registry-mock': 1.5.0 '@pnpm/types': 'workspace:3.2.0' - '@types/micromatch': 3.1.0 dependency-path: 'workspace:3.0.8' - micromatch: 4.0.2 npm-run-all: 4.1.5 tape: 4.11.0 packages/package-is-installable: @@ -1076,6 +1080,7 @@ importers: '@pnpm/list': 'link:../list' '@pnpm/lockfile-file': 'link:../lockfile-file' '@pnpm/logger': 2.1.1 + '@pnpm/matcher': 'link:../matcher' '@pnpm/outdated': 'link:../outdated' '@pnpm/package-is-installable': 'link:../package-is-installable' '@pnpm/package-store': 'link:../package-store' @@ -1116,7 +1121,6 @@ importers: lru-cache: 5.1.1 make-dir: 3.0.0 mem: 5.1.1 - micromatch: 4.0.2 mz: 2.7.0 nopt: 4.0.1 p-filter: 2.1.0 @@ -1148,7 +1152,6 @@ importers: '@pnpm/write-importer-manifest': 'link:../write-importer-manifest' '@types/byline': 4.2.31 '@types/common-tags': 1.8.0 - '@types/micromatch': 3.1.0 '@types/mkdirp': 0.5.2 '@types/nopt': 3.0.29 '@types/ramda': 0.26.21 @@ -1189,6 +1192,7 @@ importers: '@pnpm/lockfile-file': 'workspace:2.0.0-0' '@pnpm/lockfile-types': 1.1.0 '@pnpm/logger': 2.1.1 + '@pnpm/matcher': 'workspace:0.0.0' '@pnpm/modules-yaml': 'link:../modules-yaml' '@pnpm/outdated': 'workspace:5.0.0-0' '@pnpm/package-is-installable': 'workspace:2.2.1' @@ -1208,7 +1212,6 @@ importers: '@types/byline': 4.2.31 '@types/common-tags': 1.8.0 '@types/lru-cache': ^5.1.0 - '@types/micromatch': 3.1.0 '@types/mkdirp': 0.5.2 '@types/mz': ^0.0.32 '@types/nopt': 3.0.29 @@ -1248,7 +1251,6 @@ importers: lru-cache: 5.1.1 make-dir: 3.0.0 mem: 5.1.1 - micromatch: 4.0.2 mz: 2.7.0 ncp: 2.0.0 nopt: 4.0.1 @@ -1511,6 +1513,7 @@ importers: '@pnpm/link-bins': 4.4.0 '@pnpm/lockfile-file': 'link:../lockfile-file' '@pnpm/lockfile-utils': 'link:../lockfile-utils' + '@pnpm/matcher': 'link:../matcher' '@pnpm/modules-cleaner': 'link:../modules-cleaner' '@pnpm/modules-yaml': 'link:../modules-yaml' '@pnpm/package-requester': 'link:../package-requester' @@ -1610,6 +1613,7 @@ importers: '@pnpm/lockfile-file': 'workspace:2.0.0-0' '@pnpm/lockfile-utils': 'workspace:1.0.10' '@pnpm/logger': 2.1.1 + '@pnpm/matcher': 'workspace:0.0.0' '@pnpm/modules-cleaner': 'workspace:5.0.0-2' '@pnpm/modules-yaml': 'workspace:4.0.0-1' '@pnpm/package-requester': 'workspace:8.0.0-0' @@ -2340,10 +2344,6 @@ packages: /@types/archy/0.0.31: resolution: integrity: sha512-v+dxizsFVyXgD3EpFuqT9YjdEjbJmPxNf1QIX9ohZOhxh1ZF2yhqv3vYaeum9lg3VghhxS5S0a6yldN9J9lPEQ== - /@types/braces/3.0.0: - dev: true - resolution: - integrity: sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw== /@types/byline/4.2.31: dependencies: '@types/node': 12.7.11 @@ -2397,12 +2397,6 @@ packages: /@types/lru-cache/5.1.0: resolution: integrity: sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w== - /@types/micromatch/3.1.0: - dependencies: - '@types/braces': 3.0.0 - dev: true - resolution: - integrity: sha512-06uA9V7v68RTOzA3ky1Oi0HmCPa+YJ050vM+sTECwkxnHUQnO17TAcNCGX400QT6bldUiPb7ux5oKy0j8ccEDw== /@types/minimatch/3.0.3: resolution: integrity: sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== @@ -4379,6 +4373,12 @@ packages: node: '>=0.8.0' resolution: integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + /escape-string-regexp/2.0.0: + dev: false + engines: + node: '>=8' + resolution: + integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== /escodegen/1.12.0: dependencies: esprima: 3.1.3