Make packages/scripts work

This commit is contained in:
Jerko Steiner 2019-08-01 13:21:11 +07:00
parent 166981fe3b
commit 33c401dbcb
9 changed files with 193 additions and 6 deletions

View File

@ -0,0 +1,41 @@
import {StdioOptions} from './Subprocess'
import {Subprocess} from './Subprocess'
describe('Subprocess', () => {
describe('constructor', () => {
it('sets stdio to inherit when log true', () => {
const p = new Subprocess('test', [], {})
expect(p.stdio).toEqual('pipe')
})
it('sets stdio to ignore when log false', () => {
const p = new Subprocess('test', [], {}, StdioOptions.IGNORE)
expect(p.stdio).toEqual('ignore')
})
})
describe('run', () => {
// it('rejects on error', async () => {
// const error = await getError(
// new Subprocess('exit 1', environment, StdioOptions.IGNORE).run())
// expect(error.message).toMatch(/exited with code 1/)
// })
// it('logs errors', async () => {
// await getError(
// new Subprocess(
// 'invalid-non-existing-command',
// environment, StdioOptions.PIPE,
// )
// .run(),
// )
// })
it('resolves on successful invocation', async () => {
await new Subprocess('ls', [], {}, StdioOptions.IGNORE).run()
})
})
})

View File

@ -0,0 +1,42 @@
import {spawn} from 'child_process'
export enum StdioOptions {
PIPE = 'pipe',
INHERIT = 'inherit',
IGNORE = 'ignore',
}
export class Subprocess {
constructor(
public readonly command: string,
public readonly args: readonly string[],
public readonly environment: Record<string, string | undefined>,
public readonly stdio: StdioOptions = StdioOptions.PIPE,
) {}
async run() {
return new Promise((resolve, reject) => {
process.stderr.write(`> ${this.command} ${this.args.join(' ')}\n`)
const subprocess = spawn(this.command, this.args, {
shell: false,
stdio: this.stdio,
env: this.environment,
})
if (this.stdio === StdioOptions.PIPE) {
subprocess.stdout.on('data', data => process.stdout.write(data))
subprocess.stderr.on('data', data => process.stderr.write(data))
}
subprocess.on('close', code => {
if (code === 0) {
resolve()
} else {
reject(new Error(`"${this.command}" exited with code ${code}`))
}
})
subprocess.on('error', reject)
})
}
}

View File

@ -1,8 +1,5 @@
import * as childProcess from 'child_process'
import {run} from '../run'
export async function build(path: string) {
// TODO fix this
await childProcess.spawn('npx', ['ttsc', '--build', path], {
stdio: 'inherit',
})
await run('ttsc', ['--build', path])
}

View File

@ -1,2 +1,3 @@
export * from './help'
export * from './build'
export * from './watch'

View File

@ -0,0 +1,5 @@
import {run} from '../run'
export async function watch(path: string) {
await run('ttsc', ['--build', '--watch', '--preserveWatchOutput', path])
}

View File

@ -5,7 +5,10 @@ import {TCommand} from './TCommand'
async function run(...argv: string[]) {
const commandName = argv[0] || 'help'
if (!(commandName in commands)) {
throw new Error('Command not found:' + commandName)
const c = Object.keys(commands).filter(cmd => !cmd.startsWith('_'))
console.log(
`Available commands:\n\n${c.join('\n')}`)
return
}
const command = (commands as any)[commandName] as TCommand
await command(...argv.slice(1))
@ -13,4 +16,8 @@ async function run(...argv: string[]) {
if (typeof require !== 'undefined' && require.main === module) {
run(...process.argv.slice(2))
.catch(err => {
console.log('> ' + err.message)
process.exit(1)
})
}

View File

@ -0,0 +1,48 @@
import {getPathVariable, getPathSeparator, findNodeModules} from './modules'
import {resolve} from 'path'
import {platform} from 'os'
describe('modules', () => {
describe('getPathSeparator', () => {
it('returns ";" when win32', () => {
expect(getPathSeparator('win32')).toEqual(';')
})
it('returns ":" otherwise', () => {
expect(getPathSeparator('linux')).toEqual(':')
expect(getPathSeparator('darwin')).toEqual(':')
expect(getPathSeparator('mac')).toEqual(':')
})
})
describe('findNodeModules', () => {
it('should find node_modules/.bin dirs in parent path(s)', () => {
const dirs = findNodeModules()
expect(dirs.length).toBeGreaterThanOrEqual(1)
})
it('should not fail when path does not exist', () => {
const dirs = findNodeModules('/non/existing/path/bla/123')
expect(dirs).toEqual([])
})
})
describe('addToPath', () => {
it('does nothing when pathsToAdd is empty', () => {
const paths = getPathVariable([])
expect(paths).toEqual(process.env.PATH)
})
it('adds paths to path variable', () => {
const separator = getPathSeparator(platform())
const paths = getPathVariable(['/a', '/b'], '/c')
expect(paths).toEqual(`/a${separator}/b${separator}/c`)
})
it('adds node modules paths to path variable by default', () => {
const paths = findNodeModules()
const separator = getPathSeparator(platform())
expect(paths.length).toBeGreaterThanOrEqual(1)
expect(getPathVariable())
.toEqual(`${paths.join(separator)}${separator}${process.env.PATH}`)
})
})
})

View File

@ -0,0 +1,36 @@
import * as fs from 'fs'
import * as path from 'path'
import {platform} from 'os'
export function getPathSeparator(platformValue: string) {
return platformValue === 'win32' ? ';' : ':'
}
export function findNodeModules(dir: string = process.cwd()): string[] {
let lastPath = ''
const paths = []
dir = path.resolve(dir)
while (dir !== lastPath) {
const nodeModulesDir = path.join(dir, 'node_modules', '.bin')
if (
fs.existsSync(nodeModulesDir)
&& fs.statSync(nodeModulesDir).isDirectory()
) {
paths.push(nodeModulesDir)
}
lastPath = dir
dir = path.resolve(dir, '..')
}
return paths
}
export function getPathVariable(
pathsToAdd: string[] = findNodeModules(),
currentPath = process.env.PATH,
) {
if (!pathsToAdd.length) {
return currentPath
}
const separator = getPathSeparator(platform())
return `${pathsToAdd.join(separator)}${separator}${currentPath}`
}

View File

@ -0,0 +1,10 @@
import {Subprocess} from './Subprocess'
import {getPathVariable} from './modules'
export async function run(command: string, args: string[]) {
return new Subprocess(command, args, {
...process.env,
PATH: getPathVariable(),
})
.run()
}