contentful-migration
Version:
Migration tooling for contentful
262 lines • 11.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runMigration = exports.createMakeRequest = void 0;
const path = __importStar(require("path"));
const chalk_1 = __importDefault(require("chalk"));
const inquirer_1 = __importDefault(require("inquirer"));
const listr2_1 = require("listr2");
const contentful_client_1 = require("./lib/contentful-client");
const migration_parser_1 = __importDefault(require("../lib/migration-parser"));
const render_migration_1 = require("./lib/render-migration");
const steps_errors_1 = __importDefault(require("./lib/steps-errors"));
const write_errors_to_log_1 = __importDefault(require("./lib/write-errors-to-log"));
const config_1 = require("./lib/config");
const trim_1 = __importDefault(require("lodash/trim"));
const errors_1 = require("../lib/errors");
const p_throttle_1 = __importDefault(require("p-throttle"));
const { version } = require('../../package.json');
class ManyError extends Error {
constructor(message, errors) {
super(message);
this.errors = errors;
}
}
class BatchError extends Error {
constructor(message, batch, errors) {
super(message);
this.batch = batch;
this.errors = errors;
}
}
function hasManyErrors(error) {
return error instanceof ManyError || error instanceof BatchError;
}
const makeTerminatingFunction = ({ shouldThrow }) => (error) => {
if (shouldThrow) {
throw error;
}
else {
process.exit(1);
}
};
const createMakeRequest = (client, { spaceId, environmentId, limit = 10, host = 'api.contentful.com' }) => {
const throttle = (0, p_throttle_1.default)({
limit,
interval: 1000,
strict: false
});
const makeBaseUrl = (url) => {
const parts = [
`https://${host}`,
'spaces',
spaceId,
'environments',
environmentId,
(0, trim_1.default)(url, '/')
];
return parts.filter((x) => x !== '').join('/');
};
return function makeRequest(requestConfig) {
const { url } = requestConfig, config = __rest(requestConfig, ["url"]);
const fullUrl = makeBaseUrl(url);
return throttle(() => client.raw.http(fullUrl, config))();
};
};
exports.createMakeRequest = createMakeRequest;
const getMigrationFunctionFromFile = (filePath, terminate) => {
try {
return require(filePath);
}
catch (e) {
const message = (0, chalk_1.default) `{red.bold The ${filePath} script could not be parsed, as it seems to contain syntax errors.}\n`;
console.error(message);
console.error(e);
terminate(new Error(message));
}
};
const createRun = ({ shouldThrow }) => async function run(argv) {
const terminate = makeTerminatingFunction({ shouldThrow });
const migrationFunction = argv.migrationFunction || getMigrationFunctionFromFile(argv.filePath, terminate);
const application = argv.managementApplication || `contentful.migration-cli/${version}`;
const feature = argv.managementFeature || `migration-library`;
const clientConfig = Object.assign({
application,
feature
}, (0, config_1.getConfig)(argv));
// allow users to override the default host via the contentful-cli
argv.host && Object.assign(clientConfig, { host: argv.host });
const client = (0, contentful_client_1.createManagementClient)(clientConfig);
const makeRequest = (0, exports.createMakeRequest)(client, {
spaceId: clientConfig.spaceId,
environmentId: clientConfig.environmentId,
limit: argv.requestLimit,
host: clientConfig.host
});
const migrationParser = (0, migration_parser_1.default)(makeRequest, clientConfig);
let parseResult;
try {
parseResult = await migrationParser(migrationFunction);
}
catch (e) {
if (e instanceof errors_1.SpaceAccessError) {
const message = [
(0, chalk_1.default) `{red.bold ${e.message}}\n`,
(0, chalk_1.default) `🚨 {bold.red Migration unsuccessful}`
].join('\n');
console.error(message);
terminate(new Error(message));
}
console.error(e);
terminate(e);
}
if (parseResult.hasStepsValidationErrors()) {
(0, steps_errors_1.default)(parseResult.stepsValidationErrors);
terminate(new ManyError('Step Validation Errors', parseResult.stepsValidationErrors));
}
if (parseResult.hasPayloadValidationErrors()) {
(0, steps_errors_1.default)(parseResult.payloadValidationErrors);
terminate(new ManyError('Payload Validation Errors', parseResult.payloadValidationErrors));
}
const migrationName = argv.migrationFunction
? argv.migrationFunction.name
: path.basename(argv.filePath, '.js');
const errorsFile = path.join(process.cwd(), `errors-${migrationName}-${Date.now()}.log`);
const batches = parseResult.batches;
if (parseResult.hasValidationErrors()) {
(0, render_migration_1.renderValidationErrors)(batches, argv.environmentId);
terminate(new ManyError('Validation Errors', parseResult.getValidationErrors()));
}
if (parseResult.hasRuntimeErrors()) {
(0, render_migration_1.renderRuntimeErrors)(batches, errorsFile);
await (0, write_errors_to_log_1.default)(parseResult.getRuntimeErrors(), errorsFile);
terminate(new ManyError('Runtime Errors', parseResult.getRuntimeErrors()));
}
await (0, render_migration_1.renderPlan)(batches, argv.environmentId, argv.quiet);
const serverErrorsWritten = [];
const tasks = batches.map((batch) => {
return {
title: batch.intent.toPlanMessage().heading,
task: () => new listr2_1.Listr([
{
title: 'Making requests',
task: async (_ctx, task) => {
// TODO: We wanted to make this an async interator
// So we should not inspect the length but have a property for that
const numRequests = batch.requests.length;
const requestErrors = [];
let requestsDone = 0;
for (const request of batch.requests) {
requestsDone += 1;
task.title = `Making requests (${requestsDone}/${numRequests})`;
task.output = `${request.method} ${request.url} at V${request.headers['X-Contentful-Version']}`;
await makeRequest(request).catch((error) => {
serverErrorsWritten.push((0, write_errors_to_log_1.default)(error, errorsFile));
let errorMessage;
if (error instanceof TypeError) {
errorMessage = {
message: 'Value does not match the expected type',
details: {
message: error.message.toString()
}
};
}
else {
const parsed = JSON.parse(error.message);
errorMessage = {
status: parsed.statusText,
message: parsed.message,
details: parsed.details,
url: parsed.request.url
};
}
requestErrors.push(new Error(JSON.stringify(errorMessage)));
});
}
// Finish batch and only then throw all errors in there
if (requestErrors.length) {
throw new BatchError(`Batch failed`, batch, requestErrors);
}
}
}
])
};
});
const confirm = async function (options) {
if (options.skipConfirmation) {
return { applyMigration: true };
}
return inquirer_1.default.prompt([
{
type: 'confirm',
message: 'Do you want to apply the migration',
name: 'applyMigration'
}
]);
};
const answers = await confirm({ skipConfirmation: argv.yes });
if (answers.applyMigration) {
try {
const successfulMigration = await new listr2_1.Listr(tasks).run();
console.log((0, chalk_1.default) `🎉 {bold.green Migration successful}`);
return successfulMigration;
}
catch (err) {
console.error((0, chalk_1.default) `🚨 {bold.red Migration unsuccessful}`);
if (err instanceof Error) {
console.error((0, chalk_1.default) `{red ${err.message}}\n`);
if (hasManyErrors(err) && Array.isArray(err.errors)) {
err.errors.forEach((err) => console.error((0, chalk_1.default) `{red ${err}}\n\n`));
}
}
else {
console.error((0, chalk_1.default) `{red ${err}}\n`);
}
await Promise.all(serverErrorsWritten);
console.error(`Please check the errors log for more details: ${errorsFile}`);
terminate(err);
}
}
else {
console.warn((0, chalk_1.default) `⚠️ {bold.yellow Migration aborted}`);
}
};
exports.runMigration = createRun({ shouldThrow: true });
exports.default = createRun({ shouldThrow: false });
//# sourceMappingURL=cli.js.map