feat: script runner commands support package.{json5,yaml}

pr #1805
ref #1803
This commit is contained in:
Zoltan Kochan
2019-05-03 23:02:40 +03:00
committed by GitHub
parent a1140256af
commit bb3fd532fb
12 changed files with 411 additions and 54 deletions

View File

@@ -1,13 +1,14 @@
import { lifecycleLogger } from '@pnpm/core-loggers'
import { PackageJson } from '@pnpm/types'
import { DependencyManifest, ImporterManifest } from '@pnpm/types'
import lifecycle = require('@zkochan/npm-lifecycle')
function noop () {} // tslint:disable-line:no-empty
export default async function runLifecycleHook (
stage: string,
pkg: PackageJson,
manifest: ImporterManifest | DependencyManifest,
opts: {
args?: string[],
depPath: string,
optional?: boolean,
pkgRoot: string,
@@ -22,13 +23,22 @@ export default async function runLifecycleHook (
lifecycleLogger.debug({
depPath: opts.depPath,
optional,
script: pkg.scripts![stage],
script: manifest.scripts![stage],
stage,
wd: opts.pkgRoot,
})
}
return lifecycle(pkg, stage, opts.pkgRoot, {
const m = { _id: getId(manifest), ...manifest }
m.scripts = { ...m.scripts }
if (stage === 'start' && !m.scripts.start) {
m.scripts.start = 'node server.js'
}
if (opts.args && opts.args.length && m.scripts && m.scripts[stage]) {
m.scripts[stage] = `${m.scripts[stage]} ${opts.args.map((arg) => `"${arg}"`).join(' ')}`
}
return lifecycle(m, stage, opts.pkgRoot, {
config: opts.rawNpmConfig,
dir: opts.rootNodeModulesDir,
log: {
@@ -76,3 +86,13 @@ export default async function runLifecycleHook (
}
}
}
function getId (manifest: ImporterManifest | DependencyManifest) {
if (!manifest.name) {
return undefined
}
if (!manifest.version) {
return manifest.name
}
return `${manifest.name}@${manifest.version}`
}

View File

@@ -43,15 +43,12 @@ if (argv.includes('--help') || argv.includes('-h') || argv.includes('--h')) {
case 'profile':
case 'publish':
case 'repo':
case 'restart':
case 's':
case 'se':
case 'search':
case 'set':
case 'star':
case 'stars':
case 'start':
case 'stop':
case 'team':
case 'token':
case 'unpublish':
@@ -63,17 +60,6 @@ if (argv.includes('--help') || argv.includes('-h') || argv.includes('--h')) {
case 'xmas':
await passThruToNpm()
break
case 't':
case 'tst':
case 'test':
case 'run':
case 'run-script':
if (argv.includes('--filter')) {
await runPnpm()
} else {
await passThruToNpm()
}
break
default:
await runPnpm()
break

View File

@@ -37,7 +37,8 @@ function getHelpText (command: string) {
-E, --save-exact install exact version
-g, --global install as a global package
-r run installation recursively in every package found in subdirectories
or in every workspace package, when executed inside a workspace
or in every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
--store the location where all the packages are saved on the disk.
--offline trigger an error if any required dependencies are not available in local store
--prefer-offline skip staleness checks for cached data, but request missing data from the server
@@ -128,7 +129,8 @@ function getHelpText (command: string) {
Options:
-r uninstall from every package found in subdirectories
or from every workspace package, when executed inside a workspace
or from every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
Discouraged options:
--shamefully-flatten attempt to flatten the dependency tree, similar to what npm and Yarn do
@@ -154,7 +156,8 @@ function getHelpText (command: string) {
Options:
-r unlink in every package found in subdirectories
or in every workspace package, when executed inside a workspace
or in every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
`
case 'update':
@@ -166,7 +169,8 @@ function getHelpText (command: string) {
Options:
-r update in every package found in subdirectories
or every workspace package, when executed inside a workspace
or every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
-g, --global update globally installed packages
--depth how deep should levels of dependencies be inspected
0 is default, which means top-level dependencies
@@ -187,7 +191,8 @@ function getHelpText (command: string) {
Options:
-r perform command on every package in subdirectories
or on every workspace package, when executed inside a workspace
or on every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
--long show extended information
--parseable show parseable output instead of tree view
-g, --global list packages in the global install prefix instead of in the current project
@@ -212,8 +217,12 @@ function getHelpText (command: string) {
case 'install-test':
return stripIndent`
This command runs an \`npm install\` followed immediately by an \`npm test\`.
It takes exactly the same arguments as \`npm install\`.
pnpm install-test
Aliases: it
Runs a \`pnpm install\` followed immediately by a \`pnpm test\`.
It takes exactly the same arguments as \`pnpm install\`.
`
case 'store':
@@ -269,7 +278,8 @@ function getHelpText (command: string) {
Options:
-r check for outdated dependencies in every package found in subdirectories
or in every workspace package, when executed inside a workspace
or in every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
`
case 'rebuild':
@@ -283,10 +293,62 @@ function getHelpText (command: string) {
Options:
-r rebuild every package found in subdirectories
or every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
--pending rebuild packages that were not build during installation.
Packages are not build when installing with the --ignore-scripts flag
`
case 'run':
return stripIndent`
pnpm run <command> [-- <args>...]
Aliases: run-script
Runs a defined package script.
Options:
-r run the defined package script in every package found in subdirectories
or every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
`
case 'test':
return stripIndent`
pnpm test [-- <args>...]
Aliases: t, tst
Runs a package's "test" script, if one was provided.
Options:
-r run the tests in every package found in subdirectories
or every workspace package, when executed inside a workspace.
For options that may be used with \`-r\`, see "pnpm help recursive"
`
case 'start':
return stripIndent`
pnpm start [-- <args>...]
Runs an arbitrary command specified in the package's "start" property of its "scripts" object.
If no "start" property is specified on the "scripts" object, it will run node server.js.
`
case 'stop':
return stripIndent`
pnpm stop [-- <args>...]
Runs a package's "stop" script, if one was provided.
`
case 'restart':
return stripIndent`
pnpm restart [-- <args>...]
Restarts a package.
Runs a package's "stop", "restart", and "start" scripts, and associated pre- and post- scripts.
`
case 'server':
return stripIndent`
pnpm server start
@@ -405,36 +467,42 @@ function getHelpText (command: string) {
Commands:
- import
- install
- update
- uninstall
- install-test
- link
- unlink
- list
- outdated
- prune
- install-test
- store add
- store status
- store prune
- root
- rebuild
- import
- restart
- root
- run
- start
- stop
- test
- uninstall
- unlink
- update
- recursive unlink
- recursive exec
- recursive install
- recursive update
- recursive uninstall
- recursive list
- recursive outdated
- recursive rebuild
- recursive run
- recursive test
- recursive rebuild
- recursive exec
- recursive uninstall
- recursive unlink
- recursive update
- server start
- server stop
- server status
- server stop
- store add
- store prune
- store status
Other commands are passed through to npm
`

View File

@@ -9,6 +9,7 @@ import prune from './prune'
import rebuild from './rebuild'
import recursive from './recursive'
import root from './root'
import run, { restart, start, stop, test } from './run'
import server from './server'
import store from './store'
import uninstall from './uninstall'
@@ -26,9 +27,14 @@ export default {
prune,
rebuild,
recursive,
restart,
root,
run,
server,
start,
stop,
store,
test,
uninstall,
unlink,
update,

View File

@@ -1,8 +1,8 @@
import { PnpmOptions } from '../types'
import install from './install'
import runNpm from './runNpm'
import { test } from './run'
export default async function (input: string[], opts: PnpmOptions) {
await install(input, opts)
runNpm(['test'])
await test(input, opts)
}

View File

@@ -0,0 +1,103 @@
import runLifecycleHooks from '@pnpm/lifecycle'
import { readImporterManifestOnly } from '@pnpm/read-importer-manifest'
import { realNodeModulesDir } from '@pnpm/utils'
export default async function run (
args: string[],
opts: {
prefix: string,
rawNpmConfig: object,
argv: {
cooked: string[],
original: string[],
remain: string[],
},
},
) {
const manifest = await readImporterManifestOnly(opts.prefix)
const scriptName = args[0]
if (scriptName !== 'start' && (!manifest.scripts || !manifest.scripts[scriptName])) {
const err = new Error(`Missing script: ${scriptName}`)
err['code'] = 'ERR_PNPM_NO_SCRIPT'
throw err
}
const dashDashIndex = opts.argv.cooked.indexOf('--')
const lifecycleOpts = {
args: dashDashIndex === -1 ? [] : opts.argv.cooked.slice(dashDashIndex + 1),
depPath: opts.prefix,
pkgRoot: opts.prefix,
rawNpmConfig: opts.rawNpmConfig,
rootNodeModulesDir: await realNodeModulesDir(opts.prefix),
stdio: 'inherit',
unsafePerm: true, // when running scripts explicitly, assume that they're trusted.
}
if (manifest.scripts && manifest.scripts[`pre${scriptName}`]) {
await runLifecycleHooks(`pre${scriptName}`, manifest, lifecycleOpts)
}
await runLifecycleHooks(scriptName, manifest, lifecycleOpts)
if (manifest.scripts && manifest.scripts[`post${scriptName}`]) {
await runLifecycleHooks(`post${scriptName}`, manifest, lifecycleOpts)
}
}
export async function start (
args: string[],
opts: {
prefix: string,
rawNpmConfig: object,
argv: {
cooked: string[],
original: string[],
remain: string[],
},
},
) {
return run(['start', ...args], opts)
}
export async function stop (
args: string[],
opts: {
prefix: string,
rawNpmConfig: object,
argv: {
cooked: string[],
original: string[],
remain: string[],
},
},
) {
return run(['stop', ...args], opts)
}
export async function test (
args: string[],
opts: {
prefix: string,
rawNpmConfig: object,
argv: {
cooked: string[],
original: string[],
remain: string[],
},
},
) {
return run(['test', ...args], opts)
}
export async function restart (
args: string[],
opts: {
prefix: string,
rawNpmConfig: object,
argv: {
cooked: string[],
original: string[],
remain: string[],
},
},
) {
await stop(args, opts)
await run(['restart', ...args], opts)
await start(args, opts)
}

View File

@@ -40,16 +40,19 @@ type CANONICAL_COMMAND_NAMES = 'help'
| 'prune'
| 'rebuild'
| 'recursive'
| 'restart'
| 'root'
| 'run'
| 'server'
| 'start'
| 'stop'
| 'store'
| 'test'
| 'uninstall'
| 'unlink'
| 'update'
const COMMANDS_WITH_NO_DASHDASH_FILTER = new Set(['run', 'exec', 'test'])
const COMMANDS_WITH_NO_DASHDASH_FILTER = new Set(['run', 'exec', 'restart', 'start', 'stop', 'test'])
const supportedCmds = new Set<CANONICAL_COMMAND_NAMES>([
'install',
@@ -58,7 +61,10 @@ const supportedCmds = new Set<CANONICAL_COMMAND_NAMES>([
'link',
'prune',
'install-test',
'restart',
'server',
'start',
'stop',
'store',
'list',
'unlink',
@@ -181,6 +187,7 @@ export default async function run (argv: string[]) {
optionalDependencies: opts.optional !== false,
}
opts.forceSharedLockfile = typeof opts.workspacePrefix === 'string' && opts.sharedWorkspaceLockfile === true
opts.argv = cliConf.argv
if (opts.filter) {
Array.prototype.push.apply(opts.filter, filterArgs)
} else {

View File

@@ -8,6 +8,11 @@ import {
export type ReadPackageHook = (pkg: PackageManifest) => PackageManifest
export interface PnpmOptions {
argv: {
cooked: string[],
original: string[],
remain: string[],
},
bail: boolean,
cliArgs: object,
filter: string[],

View File

@@ -1,12 +1,18 @@
import prepare from '@pnpm/prepare'
import prepare, {
prepareWithJson5Manifest,
prepareWithYamlManifest,
} from '@pnpm/prepare'
import fs = require('mz/fs')
import path = require('path')
import tape = require('tape')
import promisifyTape from 'tape-promise'
import { execPnpmSync } from './utils'
import { execPnpm, execPnpmSync } from './utils'
const test = promisifyTape(tape)
const testOnly = promisifyTape(tape.only)
test('pnpm run: returns correct exit code', async (t: tape.Test) => {
const project = prepare(t, {
prepare(t, {
scripts: {
exit0: 'exit 0',
exit1: 'exit 1',
@@ -17,14 +23,128 @@ test('pnpm run: returns correct exit code', async (t: tape.Test) => {
t.equal(execPnpmSync('run', 'exit1').status, 1)
})
test('pass the args to the command that is specfied in the build script', async t => {
test('run: pass the args to the command that is specfied in the build script', async (t: tape.Test) => {
prepare(t, {
scripts: {
foo: 'ts-node test'
},
})
const result = execPnpmSync('run', 'foo', '--', '--flag=true')
t.ok((result.stdout as Buffer).toString('utf8').match(/ts-node test "--flag=true"/), 'command was successful')
})
test('run: pass the args to the command that is specfied in the build script of a package.yaml manifest', async (t: tape.Test) => {
prepareWithYamlManifest(t, {
scripts: {
foo: 'ts-node test'
},
})
const result = execPnpmSync('run', 'foo', '--', '--flag=true')
t.ok((result.stdout as Buffer).toString('utf8').match(/ts-node test "--flag=true"/), 'command was successful')
})
test('test: pass the args to the command that is specfied in the build script of a package.yaml manifest', async (t: tape.Test) => {
prepareWithYamlManifest(t, {
scripts: {
test: 'ts-node test'
},
})
const result = execPnpmSync('run', 'test', '--', '--flag=true')
const result = execPnpmSync('test', '--', '--flag=true')
t.ok((result.stdout as Buffer).toString('utf8').match(/ts-node test "--flag=true"/), 'command was successful')
})
test('start: pass the args to the command that is specfied in the build script of a package.yaml manifest', async (t: tape.Test) => {
prepareWithYamlManifest(t, {
scripts: {
start: 'ts-node test'
},
})
const result = execPnpmSync('start', '--', '--flag=true')
t.ok((result.stdout as Buffer).toString('utf8').match(/ts-node test "--flag=true"/), 'command was successful')
})
test('start: run "node server.js" by default', async (t: tape.Test) => {
prepareWithYamlManifest(t)
await fs.writeFile('server.js', 'console.log("Hello world!")', 'utf8')
const result = execPnpmSync('start')
t.ok((result.stdout as Buffer).toString('utf8').match(/Hello world!/), 'command was successful')
})
test('stop: pass the args to the command that is specfied in the build script', async (t: tape.Test) => {
prepare(t, {
scripts: {
stop: 'ts-node test'
},
})
const result = execPnpmSync('stop', '--', '--flag=true')
t.ok((result.stdout as Buffer).toString('utf8').match(/ts-node test "--flag=true"/), 'command was successful')
})
test('restart: run stop, restart and start', async (t: tape.Test) => {
prepare(t, {
scripts: {
poststop: `node -e "process.stdout.write('poststop')" | json-append ./output.json`,
prestop: `node -e "process.stdout.write('prestop')" | json-append ./output.json`,
stop: `node -e "process.stdout.write('stop')" | json-append ./output.json`,
postrestart: `node -e "process.stdout.write('postrestart')" | json-append ./output.json`,
prerestart: `node -e "process.stdout.write('prerestart')" | json-append ./output.json`,
restart: `node -e "process.stdout.write('restart')" | json-append ./output.json`,
poststart: `node -e "process.stdout.write('poststart')" | json-append ./output.json`,
prestart: `node -e "process.stdout.write('prestart')" | json-append ./output.json`,
start: `node -e "process.stdout.write('start')" | json-append ./output.json`,
},
})
await execPnpm('add', 'json-append@1')
await execPnpm('restart')
const scriptsRan = await import(path.resolve('output.json'))
t.deepEqual(scriptsRan, [
'prestop',
'stop',
'poststop',
'prerestart',
'restart',
'postrestart',
'prestart',
'start',
'poststart',
])
})
test('install-test: install dependencies and runs tests', async (t: tape.Test) => {
prepareWithJson5Manifest(t, {
dependencies: {
'json-append': '1',
},
scripts: {
posttest: `node -e "process.stdout.write('posttest')" | json-append ./output.json`,
pretest: `node -e "process.stdout.write('pretest')" | json-append ./output.json`,
test: `node -e "process.stdout.write('test')" | json-append ./output.json`,
},
})
await execPnpm('install-test')
const scriptsRan = await import(path.resolve('output.json'))
t.deepEqual(scriptsRan, [
'pretest',
'test',
'posttest',
])
})

6
pnpm-lock.yaml generated
View File

@@ -2407,13 +2407,16 @@ importers:
dependencies:
'@pnpm/assert-project': 'link:../assert-project'
'@pnpm/modules-yaml': 'link:../../packages/modules-yaml'
'@pnpm/types': 'link:../../packages/types'
'@types/mkdirp': 0.5.2
'@types/node': 11.13.8
'@types/tape': 4.2.33
'@types/write-pkg': 3.1.0
mkdirp: 0.5.1
tape: 4.10.1
write-json5-file: 2.0.0
write-pkg: 4.0.0
write-yaml-file: 2.0.0
devDependencies:
rimraf: 2.6.3
tslint: 5.16.0_typescript@3.4.5
@@ -2423,6 +2426,7 @@ importers:
specifiers:
'@pnpm/assert-project': 2.0.0
'@pnpm/modules-yaml': 3.0.2
'@pnpm/types': 3.2.0
'@types/mkdirp': 0.5.2
'@types/node': '*'
'@types/tape': 4.2.33
@@ -2434,7 +2438,9 @@ importers:
tslint-config-standard: 8.0.1
tslint-eslint-rules: 5.4.0
typescript: 3.4.5
write-json5-file: 2.0.0
write-pkg: 4.0.0
write-yaml-file: 2.0.0
tools:
devDependencies:
'@commitlint/cli': 7.5.2

View File

@@ -7,13 +7,16 @@
"dependencies": {
"@pnpm/assert-project": "2.0.0",
"@pnpm/modules-yaml": "3.0.2",
"@pnpm/types": "3.2.0",
"@types/mkdirp": "0.5.2",
"@types/node": "*",
"@types/tape": "4.2.33",
"@types/write-pkg": "3.1.0",
"mkdirp": "0.5.1",
"tape": "4.10.1",
"write-pkg": "4.0.0"
"write-json5-file": "2.0.0",
"write-pkg": "4.0.0",
"write-yaml-file": "2.0.0"
},
"devDependencies": {
"rimraf": "2.6.3",

View File

@@ -1,9 +1,12 @@
import assertProject from '@pnpm/assert-project'
import { Modules } from '@pnpm/modules-yaml'
import { ImporterManifest } from '@pnpm/types'
import mkdirp = require('mkdirp')
import path = require('path')
import { Test } from 'tape'
import writePkg = require('write-pkg')
import { sync as writeYamlFile } from 'write-yaml-file'
import { sync as writeJson5File } from 'write-json5-file'
// the testing folder should be outside of the project to avoid lookup in the project's node_modules
const tmpPath = path.join(__dirname, '..', '..', '..', '..', '.tmp')
@@ -26,7 +29,7 @@ export function tempDir (t: Test) {
export function preparePackages (
t: Test,
pkgs: Array<{ location: string, package: Object } | Object>,
pkgs: Array<{ location: string, package: ImporterManifest } | ImporterManifest>,
pkgTmpPath?: string,
): {
[name: string]: {
@@ -52,18 +55,22 @@ export function preparePackages (
if (typeof aPkg['location'] === 'string') {
result[aPkg['package']['name']] = prepare(t, aPkg['package'], path.join(dirname, aPkg['location']))
} else {
result[aPkg['name']] = prepare(t, aPkg, path.join(dirname, aPkg['name']))
result[aPkg['name']] = prepare(t, aPkg as ImporterManifest, path.join(dirname, aPkg['name']))
}
}
process.chdir('..')
return result
}
export default function prepare (t: Test, pkg?: Object, pkgTmpPath?: string) {
export default function prepare (
t: Test,
manifest?: ImporterManifest,
pkgTmpPath?: string,
) {
pkgTmpPath = pkgTmpPath || path.join(tempDir(t), 'project')
mkdirp.sync(pkgTmpPath)
writePkg.sync(pkgTmpPath, Object.assign({ name: 'project', version: '0.0.0' }, pkg))
writePkg.sync(pkgTmpPath, { name: 'project', version: '0.0.0', ...manifest } as any) // tslint:disable-line
process.chdir(pkgTmpPath)
return assertProject(t, pkgTmpPath)
@@ -77,3 +84,29 @@ export function prepareEmpty (t: Test) {
return assertProject(t, pkgTmpPath)
}
export function prepareWithYamlManifest (
t: Test,
manifest?: ImporterManifest,
) {
const pkgTmpPath = path.join(tempDir(t), 'project')
mkdirp.sync(pkgTmpPath)
writeYamlFile(path.join(pkgTmpPath, 'package.yaml'), { name: 'project', version: '0.0.0', ...manifest } as any) // tslint:disable-line
process.chdir(pkgTmpPath)
return assertProject(t, pkgTmpPath)
}
export function prepareWithJson5Manifest (
t: Test,
manifest?: ImporterManifest,
) {
const pkgTmpPath = path.join(tempDir(t), 'project')
mkdirp.sync(pkgTmpPath)
writeJson5File(path.join(pkgTmpPath, 'package.json5'), { name: 'project', version: '0.0.0', ...manifest } as any) // tslint:disable-line
process.chdir(pkgTmpPath)
return assertProject(t, pkgTmpPath)
}