fix signals and add messaging for specific features

This commit is contained in:
Ben Irvin 2023-05-08 12:41:07 +02:00
parent 58b8c18870
commit ff82ffff62
5 changed files with 91 additions and 14 deletions

View File

@ -5,6 +5,7 @@ import { isEmpty, uniq, last, isNumber, difference, omit, set } from 'lodash/fp'
import { diff as semverDiff } from 'semver';
import type { Schema } from '@strapi/strapi';
import chalk from 'chalk';
import type {
IAsset,
IDestinationProvider,
@ -567,7 +568,11 @@ class TransferEngine<
// Cause an ongoing transfer to abort gracefully
async abortTransfer(): Promise<void> {
this.#currentStream?.destroy(new TransferEngineError('fatal', 'Transfer aborted.'));
const err = new TransferEngineError('fatal', 'Transfer aborted.');
if (!this.#currentStream) {
throw err;
}
this.#currentStream?.destroy(err);
}
async init(): Promise<void> {
@ -659,12 +664,62 @@ class TransferEngine<
destination: this.destinationProvider,
};
let workflowsStatus;
const source = 'Schema Integrity';
Object.entries(context.diffs).forEach(([uid, diffs]) => {
for (const diff of diffs) {
this.#reportWarning(`${diff.path.join('.')} for ${uid}`, 'Schema Integrity Check');
const path = `${uid}.${diff.path.join('.')}`;
const endPath = diff.path[diff.path.length - 1];
// Catch known features
// TODO: can this be moved outside of the engine?
if (
uid === 'admin::workflow' ||
uid === 'admin::workflow-stage' ||
endPath.startsWith('strapi_reviewWorkflows_')
) {
workflowsStatus = diff.kind;
}
// handle generic cases
else if (diff.kind === 'added') {
this.#reportWarning(
chalk.red(`${chalk.bold(path)} does not exist on destination`),
source
);
} else if (diff.kind === 'deleted') {
this.#reportWarning(
chalk.red(`${chalk.bold(path)} does not exist on source`),
source
);
} else if (diff.kind === 'modified') {
this.#reportWarning(
chalk.red(`${chalk.bold(path)} has a different data type`),
source
);
}
}
});
// output the known feature warnings
if (workflowsStatus === 'added') {
this.#reportWarning(
chalk.red(`Review workflows feature does not exist on destination`),
source
);
} else if (workflowsStatus === 'deleted') {
this.#reportWarning(
chalk.red(`Review workflows feature does not exist on source`),
source
);
} else if (workflowsStatus === 'modified') {
this.#panic(
new TransferEngineInitializationError(
'Unresolved differences in schema [review workflows]'
)
);
}
await runMiddleware(context, this.#handlers.schemaDiff);
if (Object.keys(context.diffs).length) {

View File

@ -25,6 +25,7 @@ const {
exitMessageText,
abortTransfer,
getTransferTelemetryPayload,
setSignalHandler,
} = require('../../utils/data-transfer');
const { exitWith } = require('../../utils/helpers');
@ -115,10 +116,7 @@ module.exports = async (opts) => {
let outFile;
try {
// Abort transfer if user interrupts process
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
process.removeAllListeners(signal);
process.on(signal, () => abortTransfer({ engine, strapi }));
});
setSignalHandler(() => abortTransfer({ engine, strapi }));
results = await engine.transfer();
outFile = results.destination.file.path;

View File

@ -21,6 +21,7 @@ const {
exitMessageText,
abortTransfer,
getTransferTelemetryPayload,
setSignalHandler,
} = require('../../utils/data-transfer');
const { exitWith } = require('../../utils/helpers');
const { confirmMessage } = require('../../utils/commander');
@ -113,6 +114,12 @@ module.exports = async (opts) => {
const { updateLoader } = loadersFactory();
engine.onSchemaDiff(async (context, next) => {
// if we abort here, we need to actually exit the process because of conflict with inquirer prompt
setSignalHandler(async () => {
await abortTransfer({ engine, strapi });
exitWith(1, exitMessageText('import', true));
});
const confirmed = await confirmMessage(
'There are differences in schema between the source and destination, and the data listed above will be lost. Are you sure you want to continue?',
{
@ -120,6 +127,9 @@ module.exports = async (opts) => {
}
);
// reset handler back to normal
setSignalHandler(() => abortTransfer({ engine, strapi }));
if (confirmed) {
context.diffs = [];
return next(context);
@ -151,10 +161,7 @@ module.exports = async (opts) => {
let results;
try {
// Abort transfer if user interrupts process
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
process.removeAllListeners(signal);
process.on(signal, () => abortTransfer({ engine, strapi }));
});
setSignalHandler(() => abortTransfer({ engine, strapi }));
results = await engine.transfer();
} catch (e) {

View File

@ -22,6 +22,7 @@ const {
exitMessageText,
abortTransfer,
getTransferTelemetryPayload,
setSignalHandler,
} = require('../../utils/data-transfer');
const { exitWith } = require('../../utils/helpers');
const { confirmMessage } = require('../../utils/commander');
@ -148,6 +149,12 @@ module.exports = async (opts) => {
const { updateLoader } = loadersFactory();
engine.onSchemaDiff(async (context, next) => {
// if we abort here, we need to actually exit the process because of conflict with inquirer prompt
setSignalHandler(async () => {
await abortTransfer({ engine, strapi });
exitWith(1, exitMessageText('transfer', true));
});
const confirmed = await confirmMessage(
'There are differences in schema between the source and destination, and the data listed above will be lost. Are you sure you want to continue?',
{
@ -155,6 +162,9 @@ module.exports = async (opts) => {
}
);
// reset handler back to normal
setSignalHandler(() => abortTransfer({ engine, strapi }));
if (confirmed) {
context.diffs = [];
return next(context);
@ -188,10 +198,7 @@ module.exports = async (opts) => {
let results;
try {
// Abort transfer if user interrupts process
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
process.removeAllListeners(signal);
process.on(signal, () => abortTransfer({ engine, strapi }));
});
setSignalHandler(() => abortTransfer({ engine, strapi }));
results = await engine.transfer();
} catch (e) {

View File

@ -113,6 +113,15 @@ const abortTransfer = async ({ engine, strapi }) => {
return true;
};
const setSignalHandler = async (handler, signals = ['SIGINT', 'SIGTERM', 'SIGQUIT']) => {
signals.forEach((signal) => {
// We specifically remove ALL listeners because we have to clear the one added in Strapi bootstrap that has a process.exit
// TODO: Ideally Strapi bootstrap would not add that listener, and then this could be more flexible and add/remove only what it needs to
process.removeAllListeners(signal);
process.on(signal, handler);
});
};
const createStrapiInstance = async (opts = {}) => {
try {
const appContext = await strapi.compile();
@ -271,4 +280,5 @@ module.exports = {
validateExcludeOnly,
formatDiagnostic,
abortTransfer,
setSignalHandler,
};