Skip to content

Commit

Permalink
feat: Add support for throwing instead of process.exit(1) on errors (#…
Browse files Browse the repository at this point in the history
…118)

* Add support for throwing instead of process.exit(1) on errors

When using the package as a library, it's a good idea to throw an error
instead of exiting with code 1. This way the user can recover from the
mistake somehow (e.g. deleting a throwaway branch on during continuous
integration).

* Add usage as library e2e test
  • Loading branch information
charlespwd authored and Khaledgarbaya committed Aug 1, 2018
1 parent bc54995 commit 0b67383
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 10 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ npm install contentful-migration
### Usage as a library

```javascript
const runMigration = require('contentful-migration/built/bin/cli').default
const runMigration = require('contentful-migration/built/bin/cli').runMigration
const options = {
filePath: '<migration-file-path>',
spaceId: '<space-id>',
Expand Down Expand Up @@ -446,7 +446,7 @@ You can check out the [examples](/examples) to learn more about the migrations D
Each example file is prefixed with a sequence number, specifying the order in which you're supposed to run the migrations, as follows:
```javascript
const runMigration = require('contentful-migration/built/bin/cli').default
const runMigration = require('contentful-migration/built/bin/cli').runMigration
const options = {
spaceId: '<space-id>',
Expand Down
24 changes: 24 additions & 0 deletions examples/usage-as-lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { runMigration } = require('../built/bin/cli');

async function main (filePath) {
let statusCode = 0;

try {
await runMigration({
spaceId: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_MANAGEMENT_ACCESS_TOKEN,
environmentId: 'master',
yes: true,
filePath
});
} catch (e) {
statusCode = 1;
console.log('Catching Error');
} finally {
console.log('Cleaning Up');
}

process.exit(statusCode);
}

main(process.argv[2]);
36 changes: 28 additions & 8 deletions src/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ import writeErrorsToLog from './lib/write-errors-to-log'
import { RequestBatch } from '../lib/offline-api/index'
import { ParseResult } from '../lib/migration-parser'
import { getConfig } from './lib/config'
import ValidationError from '../lib/interfaces/errors'

class ManyError extends Error {
public errors: (Error | ValidationError)[]
constructor (message: string, errors: (Error | ValidationError)[]) {
super(message)
this.errors = errors
}
}

class BatchError extends Error {
public batch: RequestBatch
Expand All @@ -25,8 +34,18 @@ class BatchError extends Error {
this.errors = errors
}
}
const run = async function (argv) {

const makeTerminatingFunction = ({ shouldThrow }) => (error: Error) => {
if (shouldThrow) {
throw error
} else {
process.exit(1)
}
}

const createRun = ({ shouldThrow }) => async function run (argv) {
let migrationFunction
const terminate = makeTerminatingFunction({ shouldThrow })
try {
migrationFunction = require(argv.filePath)
} catch (e) {
Expand Down Expand Up @@ -64,20 +83,20 @@ const run = async function (argv) {
chalk`🚨 {bold.red Migration unsuccessful}`
].join('\n')
console.error(message)
process.exit(1)
terminate(new Error(message))
}
console.error(e)
process.exit(1)
terminate(e)
}

if (parseResult.hasStepsValidationErrors()) {
renderStepsErrors(parseResult.stepsValidationErrors)
process.exit(1)
terminate(new ManyError('Step Validation Errors', parseResult.stepsValidationErrors))
}

if (parseResult.hasPayloadValidationErrors()) {
renderStepsErrors(parseResult.payloadValidationErrors)
process.exit(1)
terminate(new ManyError('Payload Validation Errors', parseResult.payloadValidationErrors))
}

const migrationName = path.basename(argv.filePath, '.js')
Expand All @@ -90,13 +109,13 @@ const run = async function (argv) {

if (parseResult.hasValidationErrors()) {
renderValidationErrors(batches, argv.environmentId)
process.exit(1)
terminate(new ManyError('Validation Errors', parseResult.getValidationErrors()))
}

if (parseResult.hasRuntimeErrors()) {
renderRuntimeErrors(batches, errorsFile)
await writeErrorsToLog(parseResult.getRuntimeErrors(), errorsFile)
process.exit(1)
terminate(new ManyError('Runtime Errors', parseResult.getRuntimeErrors()))
}

await renderPlan(batches, argv.environmentId)
Expand Down Expand Up @@ -174,4 +193,5 @@ const run = async function (argv) {
}
}

export default run
export const runMigration = createRun({ shouldThrow: true })
export default createRun({ shouldThrow: false })
6 changes: 6 additions & 0 deletions src/lib/migration-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ class ParseResult {
return errors.concat(batch.runtimeErrors)
}, [])
}

getValidationErrors () {
return this.batches.reduce((errors, batch) => {
return errors.concat(batch.validationErrors)
}, [])
}
}

const createMigrationParser = function (makeRequest: Function, config: ClientConfig): (migrationCreator: (migration: any) => any) => Promise<ParseResult> {
Expand Down
5 changes: 5 additions & 0 deletions test/end-to-end/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ const { expect } = require('chai');

module.exports = {
errors: {
recovery: result => {
const withoutAnsiCodes = stripAnsi(result.stdout);
expect(withoutAnsiCodes).to.include('Catching Error');
expect(withoutAnsiCodes).to.include('Cleaning Up');
},
field: {
invalidPropertyWithSuggestion: function (invalid, valid) {
return result => {
Expand Down
15 changes: 15 additions & 0 deletions test/end-to-end/cli-as-lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

const nixt = require('nixt');

const SPACE_ID = process.env.CONTENTFUL_INTEGRATION_SOURCE_SPACE;
const ACCESS_TOKEN = process.env.CONTENTFUL_INTEGRATION_MANAGEMENT_TOKEN;
const cli = () => {
return nixt()
.env('CONTENTFUL_SPACE_ID', SPACE_ID)
.env('CONTENTFUL_MANAGEMENT_ACCESS_TOKEN', ACCESS_TOKEN)
.base('node ./examples/usage-as-lib.js ')
.clone();
};

module.exports = cli;
17 changes: 17 additions & 0 deletions test/end-to-end/error-recovery.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const assert = require('./assertions');
const cliAsLib = require('./cli-as-lib');

describe('cli-as-lib', () => {
describe('04-steps-errors.js', function () {
it('should throw and allow for cleanup code to execute', function (done) {
this.timeout(10000);

cliAsLib()
.run(require.resolve(`../../examples/04-steps-errors.js`))
.expect(assert.errors.recovery)
.end(done);
});
});
});

0 comments on commit 0b67383

Please sign in to comment.