-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(builder): implement run-commands builder
- Loading branch information
Showing
6 changed files
with
284 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
packages/builders/src/run-commands/run-commands.builder.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { normalize } from '@angular-devkit/core'; | ||
import * as path from 'path'; | ||
import RunCommandsBuilder from './run-commands.builder'; | ||
import { fileSync } from 'tmp'; | ||
import { readFileSync } from 'fs'; | ||
|
||
describe('Command Runner Builder', () => { | ||
let builder: RunCommandsBuilder; | ||
|
||
beforeEach(() => { | ||
builder = new RunCommandsBuilder(); | ||
}); | ||
|
||
it('should error when no commands are given', async () => { | ||
const root = normalize('/root'); | ||
try { | ||
const result = await builder | ||
.run({ | ||
root, | ||
builder: '@nrwl/run-commands', | ||
projectType: 'application', | ||
options: {} as any | ||
}) | ||
.toPromise(); | ||
fail('should throw'); | ||
} catch (e) { | ||
expect(e).toEqual( | ||
`ERROR: Bad builder config for @nrwl/run-command - "commands" option is required` | ||
); | ||
} | ||
}); | ||
|
||
it('should error when no command is given', async () => { | ||
const root = normalize('/root'); | ||
try { | ||
const result = await builder | ||
.run({ | ||
root, | ||
builder: '@nrwl/run-commands', | ||
projectType: 'application', | ||
options: { | ||
commands: [{}] as any | ||
} | ||
}) | ||
.toPromise(); | ||
fail('should throw'); | ||
} catch (e) { | ||
expect(e).toEqual( | ||
`ERROR: Bad builder config for @nrwl/run-command - "command" option is required` | ||
); | ||
} | ||
}); | ||
|
||
it('should run commands serially', async () => { | ||
const root = normalize('/root'); | ||
const f = fileSync().name; | ||
const result = await builder | ||
.run({ | ||
root, | ||
builder: '@nrwl/run-commands', | ||
projectType: 'application', | ||
options: { | ||
commands: [ | ||
{ | ||
command: `sleep 0.2 && echo 1 >> ${f}` | ||
}, | ||
{ | ||
command: `sleep 0.1 && echo 2 >> ${f}` | ||
} | ||
] | ||
} | ||
}) | ||
.toPromise(); | ||
|
||
expect(result).toEqual({ success: true }); | ||
expect( | ||
readFileSync(f) | ||
.toString() | ||
.replace(/\s/g, '') | ||
).toEqual('12'); | ||
}); | ||
|
||
it('should run commands in parallel', async () => { | ||
const root = normalize('/root'); | ||
const f = fileSync().name; | ||
const result = await builder | ||
.run({ | ||
root, | ||
builder: '@nrwl/run-commands', | ||
projectType: 'application', | ||
options: { | ||
commands: [ | ||
{ | ||
command: `sleep 0.2 && echo 1 >> ${f}` | ||
}, | ||
{ | ||
command: `sleep 0.1 && echo 2 >> ${f}` | ||
} | ||
], | ||
parallel: true | ||
} | ||
}) | ||
.toPromise(); | ||
|
||
expect(result).toEqual({ success: true }); | ||
expect( | ||
readFileSync(f) | ||
.toString() | ||
.replace(/\s/g, '') | ||
).toEqual('21'); | ||
}); | ||
|
||
it('should stop execution when a command fails', async () => { | ||
const root = normalize('/root'); | ||
const f = fileSync().name; | ||
const result = await builder | ||
.run({ | ||
root, | ||
builder: '@nrwl/run-commands', | ||
projectType: 'application', | ||
options: { | ||
commands: [ | ||
{ | ||
command: `echo 1 >> ${f} && exit 1` | ||
}, | ||
{ | ||
command: `echo 2 >> ${f}` | ||
} | ||
] | ||
} | ||
}) | ||
.toPromise(); | ||
|
||
expect(result).toEqual({ success: false }); | ||
expect( | ||
readFileSync(f) | ||
.toString() | ||
.replace(/\s/g, '') | ||
).toEqual('1'); | ||
}); | ||
}); |
108 changes: 108 additions & 0 deletions
108
packages/builders/src/run-commands/run-commands.builder.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { | ||
Builder, | ||
BuilderConfiguration, | ||
BuildEvent | ||
} from '@angular-devkit/architect'; | ||
|
||
import { Observable } from 'rxjs'; | ||
import { exec } from 'child_process'; | ||
|
||
export interface RunCommandsBuilderOptions { | ||
commands: { command: string }[]; | ||
parallel?: boolean; | ||
} | ||
|
||
export default class RunCommandsBuilder | ||
implements Builder<RunCommandsBuilderOptions> { | ||
run( | ||
config: BuilderConfiguration<RunCommandsBuilderOptions> | ||
): Observable<BuildEvent> { | ||
return Observable.create(async observer => { | ||
if (!config || !config.options || !config.options.commands) { | ||
observer.error( | ||
'ERROR: Bad builder config for @nrwl/run-command - "commands" option is required' | ||
); | ||
return; | ||
} | ||
|
||
if (config.options.commands.some(c => !c.command)) { | ||
observer.error( | ||
'ERROR: Bad builder config for @nrwl/run-command - "command" option is required' | ||
); | ||
return; | ||
} | ||
|
||
try { | ||
const success = config.options.parallel | ||
? await this.runInParallel(config) | ||
: await this.runSerially(config); | ||
observer.next({ success }); | ||
observer.complete(); | ||
} catch (e) { | ||
observer.error( | ||
`ERROR: Something went wrong in @nrwl/run-command - ${e.message}` | ||
); | ||
} | ||
}); | ||
} | ||
|
||
private async runInParallel( | ||
config: BuilderConfiguration<RunCommandsBuilderOptions> | ||
) { | ||
const r = await Promise.all( | ||
config.options.commands.map(c => | ||
this.createProcess(c.command).then(result => ({ | ||
result, | ||
command: c.command | ||
})) | ||
) | ||
); | ||
const failed = r.filter(v => !v.result); | ||
if (failed.length > 0) { | ||
failed.forEach(f => { | ||
process.stderr.write( | ||
`Warning: @nrwl/run-command command "${ | ||
f.command | ||
}" exited with non-zero status code` | ||
); | ||
}); | ||
return false; | ||
} else { | ||
return true; | ||
} | ||
} | ||
|
||
private async runSerially( | ||
config: BuilderConfiguration<RunCommandsBuilderOptions> | ||
) { | ||
const failedCommand = await config.options.commands.reduce< | ||
Promise<string | null> | ||
>(async (m, c) => { | ||
if ((await m) === null) { | ||
const success = await this.createProcess(c.command); | ||
return !success ? c.command : null; | ||
} else { | ||
return m; | ||
} | ||
}, Promise.resolve(null)); | ||
|
||
if (failedCommand) { | ||
process.stderr.write( | ||
`Warning: @nrwl/run-command command "${failedCommand}" exited with non-zero status code` | ||
); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
private createProcess(command: string): Promise<boolean> { | ||
return new Promise(res => { | ||
const childProcess = exec(command, {}); | ||
childProcess.stdout.on('data', data => process.stdout.write(data)); | ||
childProcess.stderr.on('data', err => process.stderr.write(err)); | ||
childProcess.on('close', code => { | ||
res(code === 0); | ||
}); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"title": "Run Commands", | ||
"description": "Run Commands", | ||
"type": "object", | ||
"properties": { | ||
"commands": { | ||
"type": "array", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
"command": { | ||
"type": "string", | ||
"description": "Command to run in child process" | ||
} | ||
}, | ||
"additionalProperties": false, | ||
"required": ["command"] | ||
} | ||
}, | ||
"parallel": { | ||
"type": "boolean", | ||
"description": "Run commands in parallel", | ||
"default": false | ||
} | ||
}, | ||
"required": ["command"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
#!/bin/bash | ||
#!/usr/bin/env bash | ||
|
||
npx ng-packagr -p packages/nx/ng-package.json | ||
|
||
rm -rf build | ||
|