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 { diff as semverDiff } from 'semver';
import type { Schema } from '@strapi/strapi'; import type { Schema } from '@strapi/strapi';
import chalk from 'chalk';
import type { import type {
IAsset, IAsset,
IDestinationProvider, IDestinationProvider,
@ -567,7 +568,11 @@ class TransferEngine<
// Cause an ongoing transfer to abort gracefully // Cause an ongoing transfer to abort gracefully
async abortTransfer(): Promise<void> { 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> { async init(): Promise<void> {
@ -659,12 +664,62 @@ class TransferEngine<
destination: this.destinationProvider, destination: this.destinationProvider,
}; };
let workflowsStatus;
const source = 'Schema Integrity';
Object.entries(context.diffs).forEach(([uid, diffs]) => { Object.entries(context.diffs).forEach(([uid, diffs]) => {
for (const diff of 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); await runMiddleware(context, this.#handlers.schemaDiff);
if (Object.keys(context.diffs).length) { if (Object.keys(context.diffs).length) {

View File

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

View File

@ -21,6 +21,7 @@ const {
exitMessageText, exitMessageText,
abortTransfer, abortTransfer,
getTransferTelemetryPayload, getTransferTelemetryPayload,
setSignalHandler,
} = require('../../utils/data-transfer'); } = require('../../utils/data-transfer');
const { exitWith } = require('../../utils/helpers'); const { exitWith } = require('../../utils/helpers');
const { confirmMessage } = require('../../utils/commander'); const { confirmMessage } = require('../../utils/commander');
@ -113,6 +114,12 @@ module.exports = async (opts) => {
const { updateLoader } = loadersFactory(); const { updateLoader } = loadersFactory();
engine.onSchemaDiff(async (context, next) => { 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( 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?', '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) { if (confirmed) {
context.diffs = []; context.diffs = [];
return next(context); return next(context);
@ -151,10 +161,7 @@ module.exports = async (opts) => {
let results; let results;
try { try {
// Abort transfer if user interrupts process // Abort transfer if user interrupts process
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => { setSignalHandler(() => abortTransfer({ engine, strapi }));
process.removeAllListeners(signal);
process.on(signal, () => abortTransfer({ engine, strapi }));
});
results = await engine.transfer(); results = await engine.transfer();
} catch (e) { } catch (e) {

View File

@ -22,6 +22,7 @@ const {
exitMessageText, exitMessageText,
abortTransfer, abortTransfer,
getTransferTelemetryPayload, getTransferTelemetryPayload,
setSignalHandler,
} = require('../../utils/data-transfer'); } = require('../../utils/data-transfer');
const { exitWith } = require('../../utils/helpers'); const { exitWith } = require('../../utils/helpers');
const { confirmMessage } = require('../../utils/commander'); const { confirmMessage } = require('../../utils/commander');
@ -148,6 +149,12 @@ module.exports = async (opts) => {
const { updateLoader } = loadersFactory(); const { updateLoader } = loadersFactory();
engine.onSchemaDiff(async (context, next) => { 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( 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?', '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) { if (confirmed) {
context.diffs = []; context.diffs = [];
return next(context); return next(context);
@ -188,10 +198,7 @@ module.exports = async (opts) => {
let results; let results;
try { try {
// Abort transfer if user interrupts process // Abort transfer if user interrupts process
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => { setSignalHandler(() => abortTransfer({ engine, strapi }));
process.removeAllListeners(signal);
process.on(signal, () => abortTransfer({ engine, strapi }));
});
results = await engine.transfer(); results = await engine.transfer();
} catch (e) { } catch (e) {

View File

@ -113,6 +113,15 @@ const abortTransfer = async ({ engine, strapi }) => {
return true; 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 = {}) => { const createStrapiInstance = async (opts = {}) => {
try { try {
const appContext = await strapi.compile(); const appContext = await strapi.compile();
@ -271,4 +280,5 @@ module.exports = {
validateExcludeOnly, validateExcludeOnly,
formatDiagnostic, formatDiagnostic,
abortTransfer, abortTransfer,
setSignalHandler,
}; };