diff --git a/.changeset/brave-kids-care.md b/.changeset/brave-kids-care.md new file mode 100644 index 0000000000..e5b5653747 --- /dev/null +++ b/.changeset/brave-kids-care.md @@ -0,0 +1,5 @@ +--- +"@pnpm/plugin-commands-import": patch +--- + +feat: support pnpm import diff --git a/fixtures/has-yarn-lock/.gitignore b/fixtures/has-yarn-lock/.gitignore new file mode 100644 index 0000000000..311bd539ed --- /dev/null +++ b/fixtures/has-yarn-lock/.gitignore @@ -0,0 +1 @@ +!yarn.lock \ No newline at end of file diff --git a/fixtures/has-yarn-lock/package.json b/fixtures/has-yarn-lock/package.json new file mode 100644 index 0000000000..bc75e952ab --- /dev/null +++ b/fixtures/has-yarn-lock/package.json @@ -0,0 +1,8 @@ +{ + "name": "has-yarn-lock", + "version": "0.0.0", + "dependencies": { + "dep-of-pkg-with-1-dep": "^101.0.0", + "pkg-with-1-dep": "*" + } +} diff --git a/fixtures/has-yarn-lock/yarn.lock b/fixtures/has-yarn-lock/yarn.lock new file mode 100644 index 0000000000..c36d617d27 --- /dev/null +++ b/fixtures/has-yarn-lock/yarn.lock @@ -0,0 +1,15 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +dep-of-pkg-with-1-dep@^1.0.0, dep-of-pkg-with-1-dep@^101.0.0: + version "1.1.0" + resolved "https://registry.nlark.com/dep-of-pkg-with-1-dep/download/dep-of-pkg-with-1-dep-1.1.0.tgz#67d3ac196cb08162e543b87b3b1829c76072c927" + integrity sha1-Z9OsGWywgWLlQ7h7Oxgpx2ByySc= + +pkg-with-1-dep@*: + version "1.0.0" + resolved "https://registry.nlark.com/pkg-with-1-dep/download/pkg-with-1-dep-1.0.0.tgz#0d4ed129376f707bac0bf5f6a58410f663e27c80" + integrity sha1-DU7RKTdvcHusC/X2pYQQ9mPifIA= + dependencies: + dep-of-pkg-with-1-dep "^1.0.0" diff --git a/fixtures/pnpm-workspace.yaml b/fixtures/pnpm-workspace.yaml index e03732536e..f43cb8e391 100644 --- a/fixtures/pnpm-workspace.yaml +++ b/fixtures/pnpm-workspace.yaml @@ -7,4 +7,5 @@ packages: - '!has-npm-shrinkwrap-json' - '!has-outdated-deps' - '!has-package-lock-json' - - '!hello-world-js-bin' \ No newline at end of file + - '!hello-world-js-bin' + - '!has-yarn-lock' \ No newline at end of file diff --git a/packages/plugin-commands-import/package.json b/packages/plugin-commands-import/package.json index 5efea14bd1..0fba2e099b 100644 --- a/packages/plugin-commands-import/package.json +++ b/packages/plugin-commands-import/package.json @@ -36,6 +36,8 @@ "@pnpm/assert-project": "workspace:*", "@pnpm/prepare": "workspace:0.0.26", "@types/ncp": "^2.0.4", + "@types/yarnpkg__lockfile": "^1.1.5", + "@yarnpkg/lockfile": "^1.1.0", "ncp": "^2.0.0", "tempy": "^1.0.0" }, @@ -44,9 +46,11 @@ "@pnpm/constants": "workspace:5.0.0", "@pnpm/error": "workspace:2.0.0", "@pnpm/read-project-manifest": "workspace:2.0.5", + "@pnpm/graceful-fs": "workspace:1.0.0", "@pnpm/store-connection-manager": "workspace:3.0.8", "@zkochan/rimraf": "^2.1.1", "load-json-file": "^6.2.0", + "path-exists": "^4.0.0", "render-help": "^1.0.1", "supi": "workspace:0.47.13" }, diff --git a/packages/plugin-commands-import/src/import.ts b/packages/plugin-commands-import/src/import.ts index 25bcb4f50e..e14078786f 100644 --- a/packages/plugin-commands-import/src/import.ts +++ b/packages/plugin-commands-import/src/import.ts @@ -7,10 +7,13 @@ import { createOrConnectStoreController, CreateStoreControllerOptions, } from '@pnpm/store-connection-manager' +import gfs from '@pnpm/graceful-fs' import { install, InstallOptions } from 'supi' import rimraf from '@zkochan/rimraf' import loadJsonFile from 'load-json-file' import renderHelp from 'render-help' +import { parse as parseYarnLock } from '@yarnpkg/lockfile' +import exists from 'path-exists' export const rcOptionsTypes = cliOptionsTypes @@ -20,9 +23,11 @@ export function cliOptionsTypes () { export function help () { return renderHelp({ - description: `Generates ${WANTED_LOCKFILE} from an npm package-lock.json (or npm-shrinkwrap.json) file.`, + description: `Generates ${WANTED_LOCKFILE} from an npm package-lock.json (or npm-shrinkwrap.json, yarn.lock) file.`, url: docsUrl('import'), - usages: ['pnpm import'], + usages: [ + 'pnpm import', + ], }) } @@ -34,10 +39,21 @@ export async function handler ( // Removing existing pnpm lockfile // it should not influence the new one await rimraf(path.join(opts.dir, WANTED_LOCKFILE)) - const npmPackageLock = await readNpmLockfile(opts.dir) const versionsByPackageNames = {} - getAllVersionsByPackageNames(npmPackageLock, versionsByPackageNames) - const preferredVersions = getPreferredVersions(versionsByPackageNames) + let preferredVersions = {} + if (await exists(path.join(opts.dir, 'yarn.lock'))) { + const yarnPackgeLockFile = await readYarnLockFile(opts.dir) + getAllVersionsFromYarnLockFile(yarnPackgeLockFile, versionsByPackageNames) + } else if ( + await exists(path.join(opts.dir, 'package-lock.json')) || + await exists(path.join(opts.dir, 'npm-shrinkwrap.json')) + ) { + const npmPackageLock = await readNpmLockfile(opts.dir) + getAllVersionsByPackageNames(npmPackageLock, versionsByPackageNames) + } else { + throw new PnpmError('LOCKFILE_NOT_FOUND', 'No lockfile found') + } + preferredVersions = getPreferredVersions(versionsByPackageNames) const store = await createOrConnectStoreController(opts) const installOpts = { ...opts, @@ -49,6 +65,21 @@ export async function handler ( await install(await readProjectManifestOnly(opts.dir), installOpts) } +async function readYarnLockFile (dir: string) { + try { + const yarnLockFile = await gfs.readFile(path.join(dir, 'yarn.lock'), 'utf8') + const lockJsonFile = await parseYarnLock(yarnLockFile) + if (lockJsonFile.type === 'success') { + return lockJsonFile.object + } else { + throw new PnpmError('GET_YARN_LOCKFILE_ERR', `Failed With ${lockJsonFile.type}`) + } + } catch (err) { + if (err['code'] !== 'ENOENT') throw err // eslint-disable-line @typescript-eslint/dot-notation + } + throw new PnpmError('YARN_LOCKFILE_NOT_FOUND', 'No yarn.lock found') +} + async function readNpmLockfile (dir: string) { try { return await loadJsonFile(path.join(dir, 'package-lock.json')) @@ -96,6 +127,21 @@ function getAllVersionsByPackageNames ( } } +function getAllVersionsFromYarnLockFile ( + yarnPackageLock: YarnPackgeLock, + versionsByPackageNames: { + [packageName: string]: Set + } +) { + for (const packageName of Object.keys(yarnPackageLock)) { + const pkgName = packageName.substring(0, packageName.lastIndexOf('@')) + if (!versionsByPackageNames[pkgName]) { + versionsByPackageNames[pkgName] = new Set() + } + versionsByPackageNames[pkgName].add(yarnPackageLock[packageName].version) + } +} + interface NpmPackageLock { dependencies: LockedPackagesMap } @@ -108,3 +154,15 @@ interface LockedPackage { interface LockedPackagesMap { [name: string]: LockedPackage } + +interface YarnLockPackage { + version: string + resolved: string + integrity: string + dependencies?: { + [name: string]: string + } +} +interface YarnPackgeLock { + [name: string]: YarnLockPackage +} diff --git a/packages/plugin-commands-import/test/index.ts b/packages/plugin-commands-import/test/index.ts index 7c20eca47c..cf75c4f900 100644 --- a/packages/plugin-commands-import/test/index.ts +++ b/packages/plugin-commands-import/test/index.ts @@ -64,6 +64,27 @@ test('import from package-lock.json', async () => { await project.hasNot('pkg-with-1-dep') }) +test('import from yarn.lock', async () => { + await addDistTag({ package: 'dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' }) + tempDir() + + await ncp(path.join(fixtures, 'has-yarn-lock'), process.cwd()) + + await importCommand.handler({ + ...DEFAULT_OPTS, + dir: process.cwd(), + }) + + const project = assertProject(process.cwd()) + const lockfile = await project.readLockfile() + expect(lockfile.packages).toHaveProperty(['/dep-of-pkg-with-1-dep/100.1.0']) + expect(lockfile.packages).not.toHaveProperty(['/dep-of-pkg-with-1-dep/100.0.0']) + + // node_modules is not created + await project.hasNot('dep-of-pkg-with-1-dep') + await project.hasNot('pkg-with-1-dep') +}) + test('import from npm-shrinkwrap.json', async () => { await addDistTag({ package: 'dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' }) tempDir() @@ -85,18 +106,15 @@ test('import from npm-shrinkwrap.json', async () => { await project.hasNot('pkg-with-1-dep') }) -test('import fails when no npm lockfiles are found', async () => { +test('import fails when no lockfiles are found', async () => { prepare(undefined) - let err!: PnpmError - try { - await importCommand.handler({ + await expect( + importCommand.handler({ ...DEFAULT_OPTS, dir: process.cwd(), }) - } catch (_err) { - err = _err - } - - expect(err.message.toString()).toMatch(/No package-lock.json or npm-shrinkwrap.json found/) + ).rejects.toThrow( + new PnpmError('LOCKFILE_NOT_FOUND', 'No lockfile found') + ) }) diff --git a/packages/plugin-commands-import/tsconfig.json b/packages/plugin-commands-import/tsconfig.json index 1aafa8cb38..2573983508 100644 --- a/packages/plugin-commands-import/tsconfig.json +++ b/packages/plugin-commands-import/tsconfig.json @@ -24,6 +24,9 @@ { "path": "../error" }, + { + "path": "../graceful-fs" + }, { "path": "../read-project-manifest" }, diff --git a/packages/plugin-commands-installation/src/installDeps.ts b/packages/plugin-commands-installation/src/installDeps.ts index 6b80215ea8..4a14e5da28 100644 --- a/packages/plugin-commands-installation/src/installDeps.ts +++ b/packages/plugin-commands-installation/src/installDeps.ts @@ -16,13 +16,13 @@ import { mutateModules, WorkspacePackages, } from 'supi' +import logger from '@pnpm/logger' +import { sequenceGraph } from '@pnpm/sort-packages' import getPinnedVersion from './getPinnedVersion' import getSaveType from './getSaveType' import recursive, { createMatcher, matchDependencies } from './recursive' import updateToLatestSpecsFromManifest, { createLatestSpecs } from './updateToLatestSpecsFromManifest' import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies' -import logger from '@pnpm/logger' -import { sequenceGraph } from '@pnpm/sort-packages' const OVERWRITE_UPDATE_OPTIONS = { allowNew: true, diff --git a/packages/plugin-commands-installation/test/peerDependencies.ts b/packages/plugin-commands-installation/test/peerDependencies.ts index e8fc46038f..0a102b3c81 100644 --- a/packages/plugin-commands-installation/test/peerDependencies.ts +++ b/packages/plugin-commands-installation/test/peerDependencies.ts @@ -1,7 +1,7 @@ +import path from 'path' import { add, install } from '@pnpm/plugin-commands-installation' import prepare from '@pnpm/prepare' import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' -import path from 'path' import tempy from 'tempy' const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}` diff --git a/packages/plugin-commands-installation/test/warnCyclicDependencies.ts b/packages/plugin-commands-installation/test/warnCyclicDependencies.ts index 6799638e01..c896e68052 100644 --- a/packages/plugin-commands-installation/test/warnCyclicDependencies.ts +++ b/packages/plugin-commands-installation/test/warnCyclicDependencies.ts @@ -1,8 +1,8 @@ import { install } from '@pnpm/plugin-commands-installation' import { readProjects } from '@pnpm/filter-workspace-packages' import { preparePackages } from '@pnpm/prepare' -import { DEFAULT_OPTS } from './utils' import logger from '@pnpm/logger' +import { DEFAULT_OPTS } from './utils' beforeEach(() => { jest.spyOn(logger, 'warn') diff --git a/packages/pnpm/package.json b/packages/pnpm/package.json index 1cbafffd06..ffcb4de9f1 100644 --- a/packages/pnpm/package.json +++ b/packages/pnpm/package.json @@ -150,7 +150,7 @@ "bundle:pnpm": "cross-var esbuild lib/pnpm.js --bundle --platform=node --outfile=dist/pnpm.cjs --external:node-gyp --define:process.env.npm_package_name=\\\"$npm_package_name\\\" --define:process.env.npm_package_version=\\\"$npm_package_version\\\"", "bundle:pnpx": "esbuild lib/pnpx.js --bundle --platform=node --outfile=dist/pnpx.cjs", "bundle": "pnpm bundle:pnpm && pnpm bundle:pnpx", - "start": "pnpm tsc -- --watch", + "start": "pnpm tsc --watch", "lint": "eslint -c ../../eslint.json src/**/*.ts test/**/*.ts", "registry-mock": "registry-mock", "test:jest": "jest", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc97d36d17..869ddacc28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1827,14 +1827,18 @@ importers: '@pnpm/cli-utils': workspace:0.6.16 '@pnpm/constants': workspace:5.0.0 '@pnpm/error': workspace:2.0.0 + '@pnpm/graceful-fs': workspace:1.0.0 '@pnpm/plugin-commands-import': 'link:' '@pnpm/prepare': workspace:0.0.26 '@pnpm/read-project-manifest': workspace:2.0.5 '@pnpm/store-connection-manager': workspace:3.0.8 '@types/ncp': ^2.0.4 + '@types/yarnpkg__lockfile': ^1.1.5 + '@yarnpkg/lockfile': ^1.1.0 '@zkochan/rimraf': ^2.1.1 load-json-file: ^6.2.0 ncp: ^2.0.0 + path-exists: ^4.0.0 render-help: ^1.0.1 supi: workspace:0.47.13 tempy: ^1.0.0 @@ -1842,10 +1846,12 @@ importers: '@pnpm/cli-utils': link:../cli-utils '@pnpm/constants': link:../constants '@pnpm/error': link:../error + '@pnpm/graceful-fs': link:../graceful-fs '@pnpm/read-project-manifest': link:../read-project-manifest '@pnpm/store-connection-manager': link:../store-connection-manager '@zkochan/rimraf': 2.1.1 load-json-file: 6.2.0 + path-exists: 4.0.0 render-help: 1.0.2 supi: link:../supi devDependencies: @@ -1853,6 +1859,8 @@ importers: '@pnpm/plugin-commands-import': 'link:' '@pnpm/prepare': link:../../privatePackages/prepare '@types/ncp': 2.0.5 + '@types/yarnpkg__lockfile': 1.1.5 + '@yarnpkg/lockfile': 1.1.0 ncp: 2.0.0 tempy: 1.0.1 @@ -5074,6 +5082,10 @@ packages: '@types/yargs-parser': 20.2.1 dev: true + /@types/yarnpkg__lockfile/1.1.5: + resolution: {integrity: sha512-8NYnGOctzsI4W0ApsP/BIHD/LnxpJ6XaGf2AZmz4EyDYJMxtprN4279dLNI1CPZcwC9H18qYcaFv4bXi0wmokg==} + dev: true + /@typescript-eslint/eslint-plugin/4.29.2_eslint@7.32.0+typescript@4.3.5: resolution: {integrity: sha512-x4EMgn4BTfVd9+Z+r+6rmWxoAzBaapt4QFqE+d8L8sUtYZYLDTK6VG/y/SMMWA5t1/BVU5Kf+20rX4PtWzUYZg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -5246,6 +5258,10 @@ packages: tslib: 1.14.1 dev: false + /@yarnpkg/lockfile/1.1.0: + resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + dev: true + /@yarnpkg/parsers/2.4.0: resolution: {integrity: sha512-XWgiNGh4MkhdBTJVEbXEqzk66JKjvxTtKGeLPqo3rnJ7JiJnRaK2n9MLTKQB0uoRMWYzPlISdIlok6H9OdlOVQ==} engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'}