From 576752f1990da67b71cfb7785fb3233085f500b8 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 6 May 2019 01:53:39 +0300 Subject: [PATCH] feat: copy license files from workspace root when package has none close #1804 --- packages/pnpm/package.json | 2 + packages/pnpm/src/cmd/publish.ts | 37 +++++++++++++-- packages/pnpm/test/publish.ts | 80 +++++++++++++++++++++++++++++++- pnpm-lock.yaml | 43 +++++++++++++++++ 4 files changed, 158 insertions(+), 4 deletions(-) diff --git a/packages/pnpm/package.json b/packages/pnpm/package.json index 5c17fbe3bf..75f278b2ee 100644 --- a/packages/pnpm/package.json +++ b/packages/pnpm/package.json @@ -48,11 +48,13 @@ "camelcase-keys": "5.2.0", "chalk": "2.4.2", "common-tags": "1.8.0", + "cp-file": "7.0.0", "cross-spawn": "6.0.5", "delay": "4.2.0", "diable": "4.0.2", "dir-is-case-sensitive": "1.0.2", "execa": "1.0.0", + "fast-glob": "2.2.6", "find-packages": "5.0.0", "get-port": "5.0.0", "graceful-fs": "4.1.15", diff --git a/packages/pnpm/src/cmd/publish.ts b/packages/pnpm/src/cmd/publish.ts index 2918729a75..abb5083242 100644 --- a/packages/pnpm/src/cmd/publish.ts +++ b/packages/pnpm/src/cmd/publish.ts @@ -1,4 +1,7 @@ import readImporterManifest from '@pnpm/read-importer-manifest' +import cpFile = require('cp-file') +import fg = require('fast-glob') +import fs = require('mz/fs') import path = require('path') import rimraf = require('rimraf-then') import writeJsonFile = require('write-json-file') @@ -17,7 +20,7 @@ export default async function ( const prefix = args.length && args[0] || process.cwd() let _status!: number - await fakeRegularManifest(prefix, async () => { + await fakeRegularManifest(prefix, opts.workspacePrefix || prefix, async () => { const { status } = await runNpm(['publish', ...opts.argv.original.slice(1)]) _status = status }) @@ -32,7 +35,7 @@ export async function pack ( command: string, ) { let _status!: number - await fakeRegularManifest(opts.prefix, async () => { + await fakeRegularManifest(opts.prefix, opts.workspacePrefix || opts.prefix, async () => { const { status } = await runNpm(['pack', ...opts.argv.original.slice(1)]) _status = status }) @@ -41,7 +44,15 @@ export async function pack ( } } -async function fakeRegularManifest (prefix: string, fn: () => Promise) { +const LICENSE_GLOB = 'LICEN{S,C}E{,.*}' +const findLicenses = fg.bind(fg, [LICENSE_GLOB]) as (opts: { cwd: string }) => Promise + +async function fakeRegularManifest (prefix: string, workspacePrefix: string, fn: () => Promise) { + // If a workspace package has no License of its own, + // license files from the root of the workspace are used + const copiedLicenses: string[] = prefix !== workspacePrefix && (await findLicenses({ cwd: prefix })).length === 0 + ? await copyLicenses(workspacePrefix, prefix) : [] + const { fileName, manifest, writeImporterManifest } = await readImporterManifest(prefix) const exoticManifestFormat = fileName !== 'package.json' if (exoticManifestFormat) { @@ -53,4 +64,24 @@ async function fakeRegularManifest (prefix: string, fn: () => Promise) { await rimraf(path.join(prefix, 'package.json')) await writeImporterManifest(manifest, true) } + await Promise.all( + copiedLicenses.map((copiedLicense) => fs.unlink(copiedLicense)) + ) +} + +async function copyLicenses (sourceDir: string, destDir: string) { + const licenses = await findLicenses({ cwd: sourceDir }) + if (licenses.length === 0) return [] + + const copiedLicenses: string[] = [] + await Promise.all( + licenses + .map((licenseRelPath) => path.join(sourceDir, licenseRelPath)) + .map((licensePath) => { + const licenseCopyDest = path.join(destDir, path.basename(licensePath)) + copiedLicenses.push(licenseCopyDest) + return cpFile(licensePath, licenseCopyDest) + }) + ) + return copiedLicenses } diff --git a/packages/pnpm/test/publish.ts b/packages/pnpm/test/publish.ts index 6eaa4f7e13..c76e333e6f 100644 --- a/packages/pnpm/test/publish.ts +++ b/packages/pnpm/test/publish.ts @@ -1,7 +1,9 @@ -import prepare from '@pnpm/prepare' +import prepare, { preparePackages } from '@pnpm/prepare' +import fs = require('mz/fs') import exists = require('path-exists') import tape = require('tape') import promisifyTape from 'tape-promise' +import writeYamlFile = require('write-yaml-file') import { execPnpm } from './utils' const test = promisifyTape(tape) @@ -59,3 +61,79 @@ test('publish: package with package.json5 running publish from different folder' t.ok(await exists('project/package.json5')) t.notOk(await exists('project/package.json')) }) + +test('pack packages with workspace LICENSE if no own LICENSE is present', async (t: tape.Test) => { + preparePackages(t, [ + { + name: 'project-1', + version: '1.0.0', + }, + { + name: 'project-2', + version: '1.0.0', + }, + { + name: 'target', + version: '1.0.0', + }, + ], { manifestFormat: 'YAML' }) + + await writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] }) + await fs.writeFile('LICENSE', 'workspace license', 'utf8') + await fs.writeFile('project-2/LICENSE', 'project-2 license', 'utf8') + + process.chdir('project-1') + await execPnpm('pack') + + process.chdir('../project-2') + await execPnpm('pack') + + process.chdir('../target') + + await execPnpm('add', '../project-1/project-1-1.0.0.tgz', '../project-2/project-2-1.0.0.tgz') + + t.equal(await fs.readFile('node_modules/project-1/LICENSE', 'utf8'), 'workspace license') + t.equal(await fs.readFile('node_modules/project-2/LICENSE', 'utf8'), 'project-2 license') + + process.chdir('..') + t.notOk(await exists('project-1/LICENSE')) + t.ok(await exists('project-2/LICENSE')) +}) + +test('publish packages with workspace LICENSE if no own LICENSE is present', async (t: tape.Test) => { + preparePackages(t, [ + { + name: 'project-100', + version: '1.0.0', + }, + { + name: 'project-200', + version: '1.0.0', + }, + { + name: 'target', + version: '1.0.0', + }, + ], { manifestFormat: 'YAML' }) + + await writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] }) + await fs.writeFile('LICENSE', 'workspace license', 'utf8') + await fs.writeFile('project-200/LICENSE', 'project-200 license', 'utf8') + + process.chdir('project-100') + await execPnpm('publish', ...CREDENTIALS) + + process.chdir('../project-200') + await execPnpm('publish', ...CREDENTIALS) + + process.chdir('../target') + + await execPnpm('add', 'project-100', 'project-200', '--no-link-workspace-packages') + + t.equal(await fs.readFile('node_modules/project-100/LICENSE', 'utf8'), 'workspace license') + t.equal(await fs.readFile('node_modules/project-200/LICENSE', 'utf8'), 'project-200 license') + + process.chdir('..') + t.notOk(await exists('project-100/LICENSE')) + t.ok(await exists('project-200/LICENSE')) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d50305ace6..9a674decd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1412,11 +1412,13 @@ importers: camelcase-keys: 5.2.0 chalk: 2.4.2 common-tags: 1.8.0 + cp-file: 7.0.0 cross-spawn: 6.0.5 delay: 4.2.0 diable: 4.0.2 dir-is-case-sensitive: 1.0.2 execa: 1.0.0 + fast-glob: 2.2.6 find-packages: 'link:../find-packages' get-port: 5.0.0 graceful-fs: 4.1.15 @@ -1540,6 +1542,7 @@ importers: camelcase-keys: 5.2.0 chalk: 2.4.2 common-tags: 1.8.0 + cp-file: 7.0.0 cross-spawn: 6.0.5 deep-require-cwd: 1.0.0 delay: 4.2.0 @@ -1547,6 +1550,7 @@ importers: dir-is-case-sensitive: 1.0.2 execa: 1.0.0 exists-link: 2.0.0 + fast-glob: 2.2.6 find-packages: 5.0.0 get-port: 5.0.0 graceful-fs: 4.1.15 @@ -4467,6 +4471,17 @@ packages: dev: false resolution: integrity: sha512-16ZvhVjVhEP75sMflsPtXcwbly+79os1zhBVcpRWNmnwifEbZChW+0URYING/A2ehBwp8i0pOXJYzdpiGO3Ivw== + /cp-file/7.0.0: + dependencies: + graceful-fs: 4.1.15 + make-dir: 3.0.0 + nested-error-stacks: 2.1.0 + p-event: 4.1.0 + dev: false + engines: + node: '>=8' + resolution: + integrity: sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw== /cpr/3.0.1: dependencies: graceful-fs: 4.1.15 @@ -6811,6 +6826,14 @@ packages: node: '>=6' resolution: integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + /make-dir/3.0.0: + dependencies: + semver: 6.0.0 + dev: false + engines: + node: '>=8' + resolution: + integrity: sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw== /make-error/1.3.5: dev: true resolution: @@ -7434,6 +7457,10 @@ packages: dev: false resolution: integrity: sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo= + /nested-error-stacks/2.1.0: + dev: false + resolution: + integrity: sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== /next-path/1.0.0: engines: node: '>=6' @@ -7794,6 +7821,14 @@ packages: node: '>=4' resolution: integrity: sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + /p-event/4.1.0: + dependencies: + p-timeout: 2.0.1 + dev: false + engines: + node: '>=8' + resolution: + integrity: sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA== /p-every/2.0.0: dependencies: p-map: 2.1.0 @@ -7880,6 +7915,14 @@ packages: node: '>=8' resolution: integrity: sha512-MF/HIbq6GeBqTrTIl5OJubzkGU+qfFhAFi0gnTAK6rgEIJIknEiABHOTtQu4e6JiXjIwuMPMUFQzyHh5QjCl1g== + /p-timeout/2.0.1: + dependencies: + p-finally: 1.0.0 + dev: false + engines: + node: '>=4' + resolution: + integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== /p-try/1.0.0: dev: true engines: