diff --git a/.changeset/new-carpets-approve.md b/.changeset/new-carpets-approve.md new file mode 100644 index 0000000000..d5fc60db84 --- /dev/null +++ b/.changeset/new-carpets-approve.md @@ -0,0 +1,12 @@ +--- +"@pnpm/plugin-commands-deploy": major +"pnpm": minor +--- + +A new experimental command added: `pnpm deploy`. The deploy command takes copies a project from a workspace and installs all of its production dependencies (even if some of those dependencies are other projects from the workspace). + +For example, the new command will deploy the project named `foo` to the `dist` directory in the root of the workspace: + +``` +pnpm --filter=foo deploy dist +``` diff --git a/.changeset/tidy-days-applaud.md b/.changeset/tidy-days-applaud.md new file mode 100644 index 0000000000..bac45df181 --- /dev/null +++ b/.changeset/tidy-days-applaud.md @@ -0,0 +1,6 @@ +--- +"@pnpm/core": patch +"pnpm": patch +--- + +Don't link local dev dependencies, when prod dependencies should only be installed. diff --git a/.changeset/weak-eels-guess.md b/.changeset/weak-eels-guess.md new file mode 100644 index 0000000000..8cc959b35b --- /dev/null +++ b/.changeset/weak-eels-guess.md @@ -0,0 +1,5 @@ +--- +"@pnpm/fs.indexed-pkg-importer": major +--- + +Initial release. diff --git a/.meta-updater/src/index.ts b/.meta-updater/src/index.ts index cac674dfe9..270a503acc 100644 --- a/.meta-updater/src/index.ts +++ b/.meta-updater/src/index.ts @@ -137,6 +137,7 @@ async function updateManifest (workspaceDir: string, manifest: ProjectManifest, case '@pnpm/plugin-commands-rebuild': case '@pnpm/plugin-commands-script-runners': case '@pnpm/plugin-commands-store': + case '@pnpm/plugin-commands-deploy': case CLI_PKG_NAME: case '@pnpm/core': { // @pnpm/core tests currently works only with port 4873 due to the usage of diff --git a/packages/core/src/install/index.ts b/packages/core/src/install/index.ts index cdc9fb08f7..e677c259a8 100644 --- a/packages/core/src/install/index.ts +++ b/packages/core/src/install/index.ts @@ -812,6 +812,33 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => { patchedDependencies: opts.patchedDependencies, } ) + if (!opts.include.optionalDependencies || !opts.include.devDependencies || !opts.include.dependencies) { + for (const projectId of Object.keys(linkedDependenciesByProjectId ?? {})) { + linkedDependenciesByProjectId[projectId] = linkedDependenciesByProjectId[projectId].filter((linkedDep) => + !( + linkedDep.dev && !opts.include.devDependencies || + linkedDep.optional && !opts.include.optionalDependencies || + !linkedDep.dev && !linkedDep.optional && !opts.include.dependencies + ) + ) + } + for (const { id, manifest } of projects) { + dependenciesByProjectId[id] = fromPairs( + Object.entries(dependenciesByProjectId[id]) + .filter(([, depPath]) => { + const dep = dependenciesGraph[depPath] + if (!dep) return false + const isDev = Boolean(manifest.devDependencies?.[dep.name]) + const isOptional = Boolean(manifest.optionalDependencies?.[dep.name]) + return !( + isDev && !opts.include.devDependencies || + isOptional && !opts.include.optionalDependencies || + !isDev && !isOptional && !opts.include.dependencies + ) + }) + ) + } + } stageLogger.debug({ prefix: ctx.lockfileDir, diff --git a/packages/core/src/install/link.ts b/packages/core/src/install/link.ts index f116b411e5..2a800c2dc6 100644 --- a/packages/core/src/install/link.ts +++ b/packages/core/src/install/link.ts @@ -165,17 +165,12 @@ export default async function linkPackages ( .map(([rootAlias, depPath]) => ({ rootAlias, depGraphNode: depGraph[depPath] })) .filter(({ depGraphNode }) => depGraphNode) .map(async ({ rootAlias, depGraphNode }) => { - const isDev = Boolean(manifest.devDependencies?.[depGraphNode.name]) - const isOptional = Boolean(manifest.optionalDependencies?.[depGraphNode.name]) - if ( - isDev && !opts.include.devDependencies || - isOptional && !opts.include.optionalDependencies || - !isDev && !isOptional && !opts.include.dependencies - ) return if ( (await symlinkDependency(depGraphNode.dir, modulesDir, rootAlias)).reused ) return + const isDev = Boolean(manifest.devDependencies?.[depGraphNode.name]) + const isOptional = Boolean(manifest.optionalDependencies?.[depGraphNode.name]) rootLogger.debug({ added: { dependencyType: isDev && 'dev' || isOptional && 'optional' || 'prod', diff --git a/packages/create-cafs-store/package.json b/packages/create-cafs-store/package.json index daa2b4c994..549cf2c243 100644 --- a/packages/create-cafs-store/package.json +++ b/packages/create-cafs-store/package.json @@ -16,17 +16,11 @@ }, "dependencies": { "@pnpm/cafs": "workspace:4.0.7", - "@pnpm/core-loggers": "workspace:7.0.5", "@pnpm/fetcher-base": "workspace:13.0.1", + "@pnpm/fs.indexed-pkg-importer": "workspace:0.0.0", "@pnpm/store-controller-types": "workspace:14.0.1", - "@zkochan/rimraf": "^2.1.2", - "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", - "rename-overwrite": "^4.0.2", - "sanitize-filename": "^1.6.3" + "path-temp": "^2.0.0" }, "devDependencies": { "@pnpm/create-cafs-store": "workspace:2.0.1", @@ -55,10 +49,8 @@ "scripts": { "start": "tsc --watch", "fix": "tslint -c tslint.json src/**/*.ts test/**/*.ts --fix", - "lint": "eslint src/**/*.ts test/**/*.ts", - "pretest": "rimraf .tmp", - "_test": "pnpm pretest && jest", - "test": "pnpm run compile && pnpm run _test", + "lint": "eslint src/**/*.ts", + "test": "pnpm run compile", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" }, diff --git a/packages/create-cafs-store/src/index.ts b/packages/create-cafs-store/src/index.ts index b85d808a84..cc8f8877ea 100644 --- a/packages/create-cafs-store/src/index.ts +++ b/packages/create-cafs-store/src/index.ts @@ -4,13 +4,13 @@ import createCafs, { getFilePathByModeInCafs, } from '@pnpm/cafs' import { PackageFilesResponse } from '@pnpm/fetcher-base' +import { createIndexedPkgImporter } from '@pnpm/fs.indexed-pkg-importer' import { ImportPackageFunction, PackageFileInfo, } from '@pnpm/store-controller-types' import memoize from 'mem' import pathTemp from 'path-temp' -import createImportPackage from './createImportPackage' function createPackageImporter ( opts: { @@ -18,7 +18,7 @@ function createPackageImporter ( cafsDir: string } ): ImportPackageFunction { - const cachedImporterCreator = memoize(createImportPackage) + const cachedImporterCreator = memoize(createIndexedPkgImporter) const packageImportMethod = opts.packageImportMethod const gfm = getFlatMap.bind(null, opts.cafsDir) return async (to, opts) => { diff --git a/packages/create-cafs-store/tsconfig.json b/packages/create-cafs-store/tsconfig.json index 340116b2c3..7d7f2ce140 100644 --- a/packages/create-cafs-store/tsconfig.json +++ b/packages/create-cafs-store/tsconfig.json @@ -16,10 +16,10 @@ "path": "../cafs" }, { - "path": "../core-loggers" + "path": "../fetcher-base" }, { - "path": "../fetcher-base" + "path": "../fs.indexed-pkg-importer" }, { "path": "../store-controller-types" diff --git a/packages/fs.indexed-pkg-importer/README.md b/packages/fs.indexed-pkg-importer/README.md new file mode 100644 index 0000000000..d703a7cad6 --- /dev/null +++ b/packages/fs.indexed-pkg-importer/README.md @@ -0,0 +1,13 @@ +# @pnpm/create-cafs-store + +> Replicates indexed directories using hard links, copies, or cloning + +## Installation + +``` +pnpm add @pnpm/create-cafs-store +``` + +## License + +[MIT](LICENSE) diff --git a/packages/fs.indexed-pkg-importer/jest.config.js b/packages/fs.indexed-pkg-importer/jest.config.js new file mode 100644 index 0000000000..9b65513eba --- /dev/null +++ b/packages/fs.indexed-pkg-importer/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../jest.config.js'); + +module.exports = Object.assign({}, config, {}); diff --git a/packages/fs.indexed-pkg-importer/package.json b/packages/fs.indexed-pkg-importer/package.json new file mode 100644 index 0000000000..9d0e8b446b --- /dev/null +++ b/packages/fs.indexed-pkg-importer/package.json @@ -0,0 +1,65 @@ +{ + "name": "@pnpm/fs.indexed-pkg-importer", + "description": "Replicates indexed directories using hard links, copies, or cloning", + "version": "0.0.0", + "bugs": { + "url": "https://github.com/pnpm/pnpm/issues" + }, + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib", + "!*.map" + ], + "peerDependencies": { + "@pnpm/logger": "^4.0.0" + }, + "dependencies": { + "@pnpm/core-loggers": "workspace:7.0.5", + "@zkochan/rimraf": "^2.1.2", + "make-empty-dir": "^2.0.0", + "p-limit": "^3.1.0", + "path-exists": "^4.0.0", + "path-temp": "^2.0.0", + "rename-overwrite": "^4.0.2", + "sanitize-filename": "^1.6.3" + }, + "devDependencies": { + "@pnpm/fs.indexed-pkg-importer": "workspace:0.0.0", + "@pnpm/logger": "^4.0.0", + "@pnpm/prepare": "workspace:*" + }, + "directories": { + "test": "test" + }, + "homepage": "https://github.com/pnpm/pnpm/blob/main/packages/fs.indexed-pkg-importer#readme", + "keywords": [ + "pnpm7", + "store", + "storage", + "global store", + "maching store", + "central storage", + "cache", + "packages" + ], + "license": "MIT", + "engines": { + "node": ">=14.6" + }, + "repository": "https://github.com/pnpm/pnpm/blob/main/packages/fs.indexed-pkg-importer", + "scripts": { + "start": "tsc --watch", + "fix": "tslint -c tslint.json src/**/*.ts test/**/*.ts --fix", + "lint": "eslint src/**/*.ts test/**/*.ts", + "pretest": "rimraf .tmp", + "_test": "pnpm pretest && jest", + "test": "pnpm run compile && pnpm run _test", + "prepublishOnly": "pnpm run compile", + "compile": "tsc --build && pnpm run lint --fix" + }, + "funding": "https://opencollective.com/pnpm", + "exports": { + ".": "./lib/index.js" + } +} diff --git a/packages/create-cafs-store/src/importIndexedDir.ts b/packages/fs.indexed-pkg-importer/src/importIndexedDir.ts similarity index 100% rename from packages/create-cafs-store/src/importIndexedDir.ts rename to packages/fs.indexed-pkg-importer/src/importIndexedDir.ts diff --git a/packages/create-cafs-store/src/createImportPackage.ts b/packages/fs.indexed-pkg-importer/src/index.ts similarity index 98% rename from packages/create-cafs-store/src/createImportPackage.ts rename to packages/fs.indexed-pkg-importer/src/index.ts index 4f63e23c83..c9f3f90dfa 100644 --- a/packages/create-cafs-store/src/createImportPackage.ts +++ b/packages/fs.indexed-pkg-importer/src/index.ts @@ -18,9 +18,9 @@ interface ImportOptions { type ImportFunction = (to: string, opts: ImportOptions) => Promise -export default ( +export function createIndexedPkgImporter ( packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy' -): ImportFunction => { +): ImportFunction { const importPackage = createImportPackage(packageImportMethod) return async (to, opts) => limitLinking(async () => importPackage(to, opts)) } diff --git a/packages/create-cafs-store/test/createImportPackage.test.ts b/packages/fs.indexed-pkg-importer/test/createImportPackage.test.ts similarity index 92% rename from packages/create-cafs-store/test/createImportPackage.test.ts rename to packages/fs.indexed-pkg-importer/test/createImportPackage.test.ts index d9e0849480..640f3c349b 100644 --- a/packages/create-cafs-store/test/createImportPackage.test.ts +++ b/packages/fs.indexed-pkg-importer/test/createImportPackage.test.ts @@ -19,10 +19,10 @@ baseLogger['globalWarn'] = globalWarn jest.mock('@pnpm/logger', () => baseLogger) // eslint-disable-next-line -import createImportPackage from '../lib/createImportPackage' +import { createIndexedPkgImporter } from '@pnpm/fs.indexed-pkg-importer' test('packageImportMethod=auto: clone files by default', async () => { - const importPackage = createImportPackage('auto') + const importPackage = createIndexedPkgImporter('auto') fsMock.promises.copyFile = jest.fn() fsMock.promises.rename = jest.fn() expect(await importPackage('project/package', { @@ -46,7 +46,7 @@ test('packageImportMethod=auto: clone files by default', async () => { }) test('packageImportMethod=auto: link files if cloning fails', async () => { - const importPackage = createImportPackage('auto') + const importPackage = createIndexedPkgImporter('auto') fsMock.promises.copyFile = jest.fn(() => { throw new Error('This file system does not support cloning') }) @@ -80,7 +80,7 @@ test('packageImportMethod=auto: link files if cloning fails', async () => { }) test('packageImportMethod=auto: link files if cloning fails and even hard linking fails but not with EXDEV error', async () => { - const importPackage = createImportPackage('auto') + const importPackage = createIndexedPkgImporter('auto') fsMock.promises.copyFile = jest.fn(() => { throw new Error('This file system does not support cloning') }) @@ -105,7 +105,7 @@ test('packageImportMethod=auto: link files if cloning fails and even hard linkin }) test('packageImportMethod=auto: chooses copying if cloning and hard linking is not possible', async () => { - const importPackage = createImportPackage('auto') + const importPackage = createIndexedPkgImporter('auto') fsMock.promises.copyFile = jest.fn((src: string, dest: string, flags?: number) => { if (flags === fsMock.constants.COPYFILE_FICLONE_FORCE) { throw new Error('This file system does not support cloning') @@ -127,7 +127,7 @@ test('packageImportMethod=auto: chooses copying if cloning and hard linking is n }) test('packageImportMethod=hardlink: fall back to copying if hardlinking fails', async () => { - const importPackage = createImportPackage('hardlink') + const importPackage = createIndexedPkgImporter('hardlink') fsMock.promises.link = jest.fn((src: string, dest: string) => { if (dest.endsWith('license')) { const err = new Error('') @@ -153,7 +153,7 @@ test('packageImportMethod=hardlink: fall back to copying if hardlinking fails', }) test('packageImportMethod=hardlink does not relink package from store if package.json is linked from the store', async () => { - const importPackage = createImportPackage('hardlink') + const importPackage = createIndexedPkgImporter('hardlink') fsMock.promises.copyFile = jest.fn() fsMock.promises.rename = jest.fn() fsMock.promises.stat = jest.fn(() => ({ ino: 1 })) @@ -169,7 +169,7 @@ test('packageImportMethod=hardlink does not relink package from store if package test('packageImportMethod=hardlink relinks package from store if package.json is not linked from the store', async () => { globalInfo.mockReset() - const importPackage = createImportPackage('hardlink') + const importPackage = createIndexedPkgImporter('hardlink') fsMock.promises.copyFile = jest.fn() fsMock.promises.rename = jest.fn() let ino = 0 @@ -186,7 +186,7 @@ test('packageImportMethod=hardlink relinks package from store if package.json is }) test('packageImportMethod=hardlink does not relink package from store if package.json is not present in the store', async () => { - const importPackage = createImportPackage('hardlink') + const importPackage = createIndexedPkgImporter('hardlink') fsMock.promises.copyFile = jest.fn() fsMock.promises.rename = jest.fn() fsMock.promises.stat = jest.fn((file) => { @@ -204,7 +204,7 @@ test('packageImportMethod=hardlink does not relink package from store if package test('packageImportMethod=hardlink links packages when they are not found', async () => { globalInfo.mockReset() - const importPackage = createImportPackage('hardlink') + const importPackage = createIndexedPkgImporter('hardlink') fsMock.promises.copyFile = jest.fn() fsMock.promises.rename = jest.fn() fsMock.promises.stat = jest.fn((file) => { diff --git a/packages/create-cafs-store/test/importingPkgWithInvalidFiles.ts b/packages/fs.indexed-pkg-importer/test/importingPkgWithInvalidFiles.ts similarity index 77% rename from packages/create-cafs-store/test/importingPkgWithInvalidFiles.ts rename to packages/fs.indexed-pkg-importer/test/importingPkgWithInvalidFiles.ts index 59c8c8e69d..e27b696e6f 100644 --- a/packages/create-cafs-store/test/importingPkgWithInvalidFiles.ts +++ b/packages/fs.indexed-pkg-importer/test/importingPkgWithInvalidFiles.ts @@ -1,11 +1,11 @@ import { promises as fs } from 'fs' import path from 'path' import { prepareEmpty } from '@pnpm/prepare' -import createImportPackage from '../lib/createImportPackage' +import { createIndexedPkgImporter } from '@pnpm/fs.indexed-pkg-importer' test('importing a package with invalid files', async () => { prepareEmpty() - const importPackage = createImportPackage('copy') + const importPackage = createIndexedPkgImporter('copy') const target = path.resolve('target') await importPackage(target, { filesMap: { diff --git a/packages/fs.indexed-pkg-importer/tsconfig.json b/packages/fs.indexed-pkg-importer/tsconfig.json new file mode 100644 index 0000000000..5a2c496235 --- /dev/null +++ b/packages/fs.indexed-pkg-importer/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "@pnpm/tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts", + "../../typings/**/*.d.ts" + ], + "references": [ + { + "path": "../../privatePackages/prepare" + }, + { + "path": "../core-loggers" + } + ] +} diff --git a/packages/fs.indexed-pkg-importer/tsconfig.lint.json b/packages/fs.indexed-pkg-importer/tsconfig.lint.json new file mode 100644 index 0000000000..0dc5add6b7 --- /dev/null +++ b/packages/fs.indexed-pkg-importer/tsconfig.lint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "../../typings/**/*.d.ts" + ] +} diff --git a/packages/make-dedicated-lockfile/src/index.ts b/packages/make-dedicated-lockfile/src/index.ts index 1ef974cb02..fbca02134a 100644 --- a/packages/make-dedicated-lockfile/src/index.ts +++ b/packages/make-dedicated-lockfile/src/index.ts @@ -55,7 +55,7 @@ export default async function (lockfileDir: string, projectDir: string) { 'install', '--frozen-lockfile', '--lockfile-dir=.', - '--lockfile-only', + '--fix-lockfile', '--filter=.', '--no-link-workspace-packages', ], { diff --git a/packages/make-dedicated-lockfile/test/index.ts b/packages/make-dedicated-lockfile/test/index.ts index b92ab305e6..69588f44a6 100644 --- a/packages/make-dedicated-lockfile/test/index.ts +++ b/packages/make-dedicated-lockfile/test/index.ts @@ -1,3 +1,4 @@ +import fs from 'fs' import path from 'path' import { readWantedLockfile } from '@pnpm/lockfile-file' import fixtures from '@pnpm/test-fixtures' @@ -7,6 +8,7 @@ const f = fixtures(__dirname) test('makeDedicatedLockfile()', async () => { const tmp = f.prepare('fixture') + fs.writeFileSync('.npmrc', 'store-dir=store\ncache-dir=cache', 'utf8') const projectDir = path.join(tmp, 'packages/is-negative') await makeDedicatedLockfile(tmp, projectDir) diff --git a/packages/plugin-commands-deploy/README.md b/packages/plugin-commands-deploy/README.md new file mode 100644 index 0000000000..0bd480f0e2 --- /dev/null +++ b/packages/plugin-commands-deploy/README.md @@ -0,0 +1,15 @@ +# @pnpm/plugin-commands-deploy + +> Commands for deploy + +[![npm version](https://img.shields.io/npm/v/@pnpm/plugin-commands-deploy.svg)](https://www.npmjs.com/package/@pnpm/plugin-commands-deploy) + +## deploy + +```sh +pnpm add @pnpm/plugin-commands-deploy +``` + +## License + +MIT diff --git a/packages/plugin-commands-deploy/jest.config.js b/packages/plugin-commands-deploy/jest.config.js new file mode 100644 index 0000000000..f697d83169 --- /dev/null +++ b/packages/plugin-commands-deploy/jest.config.js @@ -0,0 +1 @@ +module.exports = require('../../jest.config.js') diff --git a/packages/plugin-commands-deploy/package.json b/packages/plugin-commands-deploy/package.json new file mode 100644 index 0000000000..99bdfa6110 --- /dev/null +++ b/packages/plugin-commands-deploy/package.json @@ -0,0 +1,60 @@ +{ + "name": "@pnpm/plugin-commands-deploy", + "version": "0.0.0", + "description": "Commands for deploy", + "funding": "https://opencollective.com/pnpm", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib", + "!*.map" + ], + "exports": { + ".": "./lib/index.js" + }, + "engines": { + "node": ">=14.6" + }, + "scripts": { + "start": "tsc --watch", + "lint": "eslint src/**/*.ts test/**/*.ts", + "registry-mock": "registry-mock", + "test:jest": "jest", + "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7773 pnpm run test:e2e", + "test": "pnpm run compile && pnpm run _test", + "prepublishOnly": "pnpm run compile", + "compile": "tsc --build && pnpm run lint --fix" + }, + "repository": "https://github.com/pnpm/pnpm/blob/main/packages/plugin-commands-deploy", + "keywords": [ + "pnpm7", + "pnpm" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/pnpm/pnpm/issues" + }, + "homepage": "https://github.com/pnpm/pnpm/blob/main/packages/plugin-commands-deploy#readme", + "devDependencies": { + "@pnpm/assert-project": "workspace:*", + "@pnpm/filter-workspace-packages": "workspace:5.0.16", + "@pnpm/lockfile-types": "workspace:4.2.0", + "@pnpm/logger": "^4.0.0", + "@pnpm/plugin-commands-deploy": "workspace:0.0.0", + "@pnpm/prepare": "workspace:*", + "@pnpm/registry-mock": "2.20.0" + }, + "dependencies": { + "@pnpm/cli-utils": "workspace:0.7.16", + "@pnpm/directory-fetcher": "workspace:3.0.6", + "@pnpm/error": "workspace:3.0.1", + "@pnpm/fs.indexed-pkg-importer": "workspace:0.0.0", + "@pnpm/plugin-commands-installation": "workspace:10.3.2", + "@zkochan/rimraf": "^2.1.2", + "render-help": "^1.0.1" + }, + "peerDependencies": { + "@pnpm/logger": "^4.0.0" + } +} diff --git a/packages/plugin-commands-deploy/src/deploy.ts b/packages/plugin-commands-deploy/src/deploy.ts new file mode 100644 index 0000000000..87cdf392e6 --- /dev/null +++ b/packages/plugin-commands-deploy/src/deploy.ts @@ -0,0 +1,86 @@ +import fs from 'fs' +import path from 'path' +import { docsUrl } from '@pnpm/cli-utils' +import { fetchFromDir } from '@pnpm/directory-fetcher' +import { createIndexedPkgImporter } from '@pnpm/fs.indexed-pkg-importer' +import { install } from '@pnpm/plugin-commands-installation' +import PnpmError from '@pnpm/error' +import rimraf from '@zkochan/rimraf' +import renderHelp from 'render-help' + +export function rcOptionsTypes () { + return install.rcOptionsTypes() +} + +export function cliOptionsTypes () { + return install.cliOptionsTypes() +} + +export const commandNames = ['deploy'] + +export function help () { + return renderHelp({ + description: 'Experimental! Deploy a package from a workspace', + descriptionLists: [], + url: docsUrl('deploy'), + usages: ['pnpm --filter= deploy '], + }) +} + +export async function handler ( + opts: install.InstallCommandOptions, + params: string[] +) { + if (!opts.workspaceDir) { + throw new PnpmError('CANNOT_DEPLOY', 'A deploy is only possible from inside a workspace') + } + const selectedDirs = Object.keys(opts.selectedProjectsGraph ?? {}) + if (selectedDirs.length === 0) { + throw new PnpmError('NOTHING_TO_DEPLOY', 'No project was selected for deployment') + } + if (selectedDirs.length > 1) { + throw new PnpmError('CANNOT_DEPLOY_MANY', 'Cannot deploy more than 1 project') + } + if (params.length !== 1) { + throw new PnpmError('INVALID_DEPLOY_TARGET', 'This command requires one parameter') + } + const deployedDir = selectedDirs[0] + const deployDir = path.join(opts.workspaceDir, params[0]) + await rimraf(deployDir) + await fs.promises.mkdir(deployDir) + await copyProject(deployedDir, deployDir) + const readPackageHook = opts.hooks?.readPackage + // eslint-disable-next-line + const newReadPackageHook = readPackageHook ? (async (pkg: any, context: any) => deployHook(await readPackageHook(pkg, context))) : deployHook + await install.handler({ + ...opts, + depth: Infinity, + hooks: { + ...opts.hooks, + readPackage: newReadPackageHook, + }, + frozenLockfile: false, + preferFrozenLockfile: false, + dev: false, + virtualStoreDir: path.join(deployDir, 'node_modules/.pnpm'), + modulesDir: path.relative(deployedDir, path.join(deployDir, 'node_modules')), + }) +} + +async function copyProject (src: string, dest: string) { + const { filesIndex } = await fetchFromDir(src, { includeOnlyPackageFiles: true }) + const importPkg = createIndexedPkgImporter('clone-or-copy') + await importPkg(dest, { filesMap: filesIndex, force: true, fromStore: true }) +} + +function deployHook (pkg: any) { // eslint-disable-line + pkg.dependenciesMeta = pkg.dependenciesMeta || {} + for (const [depName, depVersion] of Object.entries(pkg.dependencies ?? {})) { + if ((depVersion as string).startsWith('workspace:')) { + pkg.dependenciesMeta[depName] = { + injected: true, + } + } + } + return pkg +} diff --git a/packages/plugin-commands-deploy/src/index.ts b/packages/plugin-commands-deploy/src/index.ts new file mode 100644 index 0000000000..95400dbc39 --- /dev/null +++ b/packages/plugin-commands-deploy/src/index.ts @@ -0,0 +1,3 @@ +import * as deploy from './deploy' + +export { deploy } diff --git a/packages/plugin-commands-deploy/test/deploy.test.ts b/packages/plugin-commands-deploy/test/deploy.test.ts new file mode 100644 index 0000000000..d3b6a7c7e5 --- /dev/null +++ b/packages/plugin-commands-deploy/test/deploy.test.ts @@ -0,0 +1,63 @@ +import fs from 'fs' +import path from 'path' +import { deploy } from '@pnpm/plugin-commands-deploy' +import assertProject from '@pnpm/assert-project' +import { preparePackages } from '@pnpm/prepare' +import { readProjects } from '@pnpm/filter-workspace-packages' +import { DEFAULT_OPTS } from './utils' + +test('deploy', async () => { + preparePackages([ + { + name: 'project-1', + version: '1.0.0', + files: ['index.js'], + dependencies: { + 'project-2': 'workspace:*', + 'is-positive': '1.0.0', + }, + devDependencies: { + 'project-3': 'workspace:*', + 'is-negative': '1.0.0', + }, + }, + { + name: 'project-2', + version: '2.0.0', + dependencies: { + 'project-3': 'workspace:*', + 'is-odd': '1.0.0', + }, + }, + { + name: 'project-3', + version: '2.0.0', + dependencies: { + 'project-3': 'workspace:*', + 'is-odd': '1.0.0', + }, + }, + ]) + + fs.writeFileSync('project-1/test.js', '', 'utf8') + fs.writeFileSync('project-1/index.js', '', 'utf8') + + const { allProjects, selectedProjectsGraph } = await readProjects(process.cwd(), [{ namePattern: 'project-1' }]) + + await deploy.handler({ + ...DEFAULT_OPTS, + allProjects, + dir: process.cwd(), + recursive: true, + selectedProjectsGraph, + workspaceDir: process.cwd(), + }, ['deploy']) + + const project = assertProject(path.resolve('deploy')) + await project.has('project-2') + await project.has('is-positive') + await project.hasNot('project-3') + await project.hasNot('is-negative') + expect(fs.existsSync('deploy/index.js')).toBeTruthy() + expect(fs.existsSync('deploy/test.js')).toBeFalsy() +}) diff --git a/packages/plugin-commands-deploy/test/utils/index.ts b/packages/plugin-commands-deploy/test/utils/index.ts new file mode 100644 index 0000000000..3a2df07346 --- /dev/null +++ b/packages/plugin-commands-deploy/test/utils/index.ts @@ -0,0 +1,50 @@ +import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' + +const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}` + +export const DEFAULT_OPTS = { + alwaysAuth: false, + argv: { + original: [], + }, + bail: true, + bin: 'node_modules/.bin', + ca: undefined, + cacheDir: '../cache', + cert: undefined, + cliOptions: {}, + fetchRetries: 2, + fetchRetryFactor: 90, + fetchRetryMaxtimeout: 90, + fetchRetryMintimeout: 10, + filter: [] as string[], + httpsProxy: undefined, + include: { + dependencies: true, + devDependencies: true, + optionalDependencies: true, + }, + key: undefined, + linkWorkspacePackages: true, + localAddress: undefined, + lock: false, + lockStaleDuration: 90, + networkConcurrency: 16, + offline: false, + pending: false, + pnpmfile: './.pnpmfile.cjs', + pnpmHomeDir: '', + proxy: undefined, + rawConfig: { registry: REGISTRY }, + rawLocalConfig: {}, + registries: { default: REGISTRY }, + registry: REGISTRY, + sort: true, + storeDir: '../store', + strictSsl: false, + userAgent: 'pnpm', + userConfig: {}, + useRunningStoreServer: false, + useStoreServer: false, + workspaceConcurrency: 4, +} diff --git a/packages/plugin-commands-deploy/tsconfig.json b/packages/plugin-commands-deploy/tsconfig.json new file mode 100644 index 0000000000..7608ddda7e --- /dev/null +++ b/packages/plugin-commands-deploy/tsconfig.json @@ -0,0 +1,40 @@ +{ + "extends": "@pnpm/tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts", + "../../typings/**/*.d.ts" + ], + "references": [ + { + "path": "../../privatePackages/assert-project" + }, + { + "path": "../../privatePackages/prepare" + }, + { + "path": "../cli-utils" + }, + { + "path": "../directory-fetcher" + }, + { + "path": "../error" + }, + { + "path": "../filter-workspace-packages" + }, + { + "path": "../fs.indexed-pkg-importer" + }, + { + "path": "../lockfile-types" + }, + { + "path": "../plugin-commands-installation" + } + ] +} diff --git a/packages/plugin-commands-deploy/tsconfig.lint.json b/packages/plugin-commands-deploy/tsconfig.lint.json new file mode 100644 index 0000000000..0dc5add6b7 --- /dev/null +++ b/packages/plugin-commands-deploy/tsconfig.lint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "../../typings/**/*.d.ts" + ] +} diff --git a/packages/plugin-commands-installation/package.json b/packages/plugin-commands-installation/package.json index 2673b6f534..f753d6feb7 100644 --- a/packages/plugin-commands-installation/package.json +++ b/packages/plugin-commands-installation/package.json @@ -17,7 +17,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7773 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7774 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/plugin-commands-installation/src/install.ts b/packages/plugin-commands-installation/src/install.ts index d02ea1a393..72f4d4e211 100644 --- a/packages/plugin-commands-installation/src/install.ts +++ b/packages/plugin-commands-installation/src/install.ts @@ -245,6 +245,7 @@ export type InstallCommandOptions = Pick & CreateStoreControllerOptions & { diff --git a/packages/plugin-commands-listing/package.json b/packages/plugin-commands-listing/package.json index 41db3f7e4d..c6f3241695 100644 --- a/packages/plugin-commands-listing/package.json +++ b/packages/plugin-commands-listing/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7774 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7775 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/plugin-commands-outdated/package.json b/packages/plugin-commands-outdated/package.json index 6eb7dd75cf..6ebbc2fb85 100644 --- a/packages/plugin-commands-outdated/package.json +++ b/packages/plugin-commands-outdated/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7775 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7776 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/plugin-commands-patching/package.json b/packages/plugin-commands-patching/package.json index ad72725a74..e8d530a291 100644 --- a/packages/plugin-commands-patching/package.json +++ b/packages/plugin-commands-patching/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7776 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7777 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/plugin-commands-publishing/package.json b/packages/plugin-commands-publishing/package.json index 47b98f884b..958648b032 100644 --- a/packages/plugin-commands-publishing/package.json +++ b/packages/plugin-commands-publishing/package.json @@ -17,7 +17,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7777 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7778 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/plugin-commands-rebuild/package.json b/packages/plugin-commands-rebuild/package.json index 5c82eb443b..4f7b24206d 100644 --- a/packages/plugin-commands-rebuild/package.json +++ b/packages/plugin-commands-rebuild/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7778 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7779 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/plugin-commands-script-runners/package.json b/packages/plugin-commands-script-runners/package.json index 0dba67f456..fd32c4a9a3 100644 --- a/packages/plugin-commands-script-runners/package.json +++ b/packages/plugin-commands-script-runners/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7779 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7780 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "start": "tsc --watch", diff --git a/packages/plugin-commands-store/package.json b/packages/plugin-commands-store/package.json index cbf891822c..f4dbf1d983 100644 --- a/packages/plugin-commands-store/package.json +++ b/packages/plugin-commands-store/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7780 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7781 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/pnpm/package.json b/packages/pnpm/package.json index d455125f27..982e563777 100644 --- a/packages/pnpm/package.json +++ b/packages/pnpm/package.json @@ -42,6 +42,7 @@ "@pnpm/pick-registry-for-package": "workspace:3.0.5", "@pnpm/plugin-commands-audit": "workspace:6.0.16", "@pnpm/plugin-commands-env": "workspace:2.1.15", + "@pnpm/plugin-commands-deploy": "workspace:0.0.0", "@pnpm/plugin-commands-init": "workspace:1.0.16", "@pnpm/plugin-commands-installation": "workspace:10.3.2", "@pnpm/plugin-commands-listing": "workspace:5.0.16", @@ -154,7 +155,7 @@ "test:jest": "jest", "pretest:e2e": "rimraf node_modules/.bin/pnpm", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7781 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7782 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm compile && npm cache clear --force && publish-packed --prune --npm-client=pnpm --dest=dist", "postpublish": "publish-packed", diff --git a/packages/pnpm/src/cmd/index.ts b/packages/pnpm/src/cmd/index.ts index 17fc14ee8f..8b6b22297b 100644 --- a/packages/pnpm/src/cmd/index.ts +++ b/packages/pnpm/src/cmd/index.ts @@ -2,6 +2,7 @@ import { CompletionFunc } from '@pnpm/command' import { types as allTypes } from '@pnpm/config' import { audit } from '@pnpm/plugin-commands-audit' import { env } from '@pnpm/plugin-commands-env' +import { deploy } from '@pnpm/plugin-commands-deploy' import { add, fetch, install, link, prune, remove, unlink, update, importCommand } from '@pnpm/plugin-commands-installation' import { list, ll, why } from '@pnpm/plugin-commands-listing' import { outdated } from '@pnpm/plugin-commands-outdated' @@ -97,6 +98,7 @@ const commands: CommandDefinition[] = [ audit, bin, create, + deploy, dlx, env, exec, diff --git a/packages/pnpm/tsconfig.json b/packages/pnpm/tsconfig.json index 7fc17a16ab..f2a1504015 100644 --- a/packages/pnpm/tsconfig.json +++ b/packages/pnpm/tsconfig.json @@ -75,6 +75,9 @@ { "path": "../plugin-commands-audit" }, + { + "path": "../plugin-commands-deploy" + }, { "path": "../plugin-commands-env" }, diff --git a/packages/types/src/package.ts b/packages/types/src/package.ts index 2b67aa22a3..d739d509ee 100644 --- a/packages/types/src/package.ts +++ b/packages/types/src/package.ts @@ -75,6 +75,7 @@ export interface BaseManifest { directories?: { bin?: string } + files?: string[] dependencies?: Dependencies devDependencies?: Dependencies optionalDependencies?: Dependencies diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7156e24db7..af92eaf7bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,8 @@ overrides: packageExtensionsChecksum: 1373aa954ee01713eb42037cfa3725eb +patchedDependencies: {} + importers: .: @@ -583,33 +585,21 @@ importers: packages/create-cafs-store: specifiers: '@pnpm/cafs': workspace:4.0.7 - '@pnpm/core-loggers': workspace:7.0.5 '@pnpm/create-cafs-store': workspace:2.0.1 '@pnpm/fetcher-base': workspace:13.0.1 + '@pnpm/fs.indexed-pkg-importer': workspace:0.0.0 '@pnpm/logger': ^4.0.0 '@pnpm/prepare': workspace:* '@pnpm/store-controller-types': workspace:14.0.1 - '@zkochan/rimraf': ^2.1.2 - 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 - rename-overwrite: ^4.0.2 - sanitize-filename: ^1.6.3 dependencies: '@pnpm/cafs': link:../cafs - '@pnpm/core-loggers': link:../core-loggers '@pnpm/fetcher-base': link:../fetcher-base + '@pnpm/fs.indexed-pkg-importer': link:../fs.indexed-pkg-importer '@pnpm/store-controller-types': link:../store-controller-types - '@zkochan/rimraf': 2.1.2 - 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 - rename-overwrite: 4.0.2 - sanitize-filename: 1.6.3 devDependencies: '@pnpm/create-cafs-store': 'link:' '@pnpm/logger': 4.0.0 @@ -1013,6 +1003,33 @@ importers: devDependencies: '@pnpm/find-workspace-packages': 'link:' + packages/fs.indexed-pkg-importer: + specifiers: + '@pnpm/core-loggers': workspace:7.0.5 + '@pnpm/fs.indexed-pkg-importer': workspace:0.0.0 + '@pnpm/logger': ^4.0.0 + '@pnpm/prepare': workspace:* + '@zkochan/rimraf': ^2.1.2 + make-empty-dir: ^2.0.0 + p-limit: ^3.1.0 + path-exists: ^4.0.0 + path-temp: ^2.0.0 + rename-overwrite: ^4.0.2 + sanitize-filename: ^1.6.3 + dependencies: + '@pnpm/core-loggers': link:../core-loggers + '@zkochan/rimraf': 2.1.2 + make-empty-dir: 2.0.0 + p-limit: 3.1.0 + path-exists: 4.0.0 + path-temp: 2.0.0 + rename-overwrite: 4.0.2 + sanitize-filename: 1.6.3 + devDependencies: + '@pnpm/fs.indexed-pkg-importer': 'link:' + '@pnpm/logger': 4.0.0 + '@pnpm/prepare': link:../../privatePackages/prepare + packages/get-context: specifiers: '@pnpm/constants': workspace:6.1.0 @@ -2146,6 +2163,39 @@ importers: strip-ansi: 6.0.1 tempy: 1.0.1 + packages/plugin-commands-deploy: + specifiers: + '@pnpm/assert-project': workspace:* + '@pnpm/cli-utils': workspace:0.7.16 + '@pnpm/directory-fetcher': workspace:3.0.6 + '@pnpm/error': workspace:3.0.1 + '@pnpm/filter-workspace-packages': workspace:5.0.16 + '@pnpm/fs.indexed-pkg-importer': workspace:0.0.0 + '@pnpm/lockfile-types': workspace:4.2.0 + '@pnpm/logger': ^4.0.0 + '@pnpm/plugin-commands-deploy': workspace:0.0.0 + '@pnpm/plugin-commands-installation': workspace:10.3.2 + '@pnpm/prepare': workspace:* + '@pnpm/registry-mock': 2.20.0 + '@zkochan/rimraf': ^2.1.2 + render-help: ^1.0.1 + dependencies: + '@pnpm/cli-utils': link:../cli-utils + '@pnpm/directory-fetcher': link:../directory-fetcher + '@pnpm/error': link:../error + '@pnpm/fs.indexed-pkg-importer': link:../fs.indexed-pkg-importer + '@pnpm/plugin-commands-installation': link:../plugin-commands-installation + '@zkochan/rimraf': 2.1.2 + render-help: 1.0.2 + devDependencies: + '@pnpm/assert-project': link:../../privatePackages/assert-project + '@pnpm/filter-workspace-packages': link:../filter-workspace-packages + '@pnpm/lockfile-types': link:../lockfile-types + '@pnpm/logger': 4.0.0 + '@pnpm/plugin-commands-deploy': 'link:' + '@pnpm/prepare': link:../../privatePackages/prepare + '@pnpm/registry-mock': 2.20.0 + packages/plugin-commands-env: specifiers: '@pnpm/cli-utils': workspace:0.7.16 @@ -2924,6 +2974,7 @@ importers: '@pnpm/parse-cli-args': workspace:5.0.2 '@pnpm/pick-registry-for-package': workspace:3.0.5 '@pnpm/plugin-commands-audit': workspace:6.0.16 + '@pnpm/plugin-commands-deploy': workspace:0.0.0 '@pnpm/plugin-commands-env': workspace:2.1.15 '@pnpm/plugin-commands-init': workspace:1.0.16 '@pnpm/plugin-commands-installation': workspace:10.3.2 @@ -3013,6 +3064,7 @@ importers: '@pnpm/parse-cli-args': link:../parse-cli-args '@pnpm/pick-registry-for-package': link:../pick-registry-for-package '@pnpm/plugin-commands-audit': link:../plugin-commands-audit + '@pnpm/plugin-commands-deploy': link:../plugin-commands-deploy '@pnpm/plugin-commands-env': link:../plugin-commands-env '@pnpm/plugin-commands-init': link:../plugin-commands-init '@pnpm/plugin-commands-installation': link:../plugin-commands-installation