@strapi/strapi 4.10.4 → 4.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,7 @@ const {
25
25
  exitMessageText,
26
26
  abortTransfer,
27
27
  getTransferTelemetryPayload,
28
+ setSignalHandler,
28
29
  } = require('../../utils/data-transfer');
29
30
  const { exitWith } = require('../../utils/helpers');
30
31
 
@@ -115,10 +116,7 @@ module.exports = async (opts) => {
115
116
  let outFile;
116
117
  try {
117
118
  // Abort transfer if user interrupts process
118
- ['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
119
- process.removeAllListeners(signal);
120
- process.on(signal, () => abortTransfer({ engine, strapi }));
121
- });
119
+ setSignalHandler(() => abortTransfer({ engine, strapi }));
122
120
 
123
121
  results = await engine.transfer();
124
122
  outFile = results.destination.file.path;
@@ -21,6 +21,8 @@ const {
21
21
  exitMessageText,
22
22
  abortTransfer,
23
23
  getTransferTelemetryPayload,
24
+ setSignalHandler,
25
+ getDiffHandler,
24
26
  } = require('../../utils/data-transfer');
25
27
  const { exitWith } = require('../../utils/helpers');
26
28
 
@@ -111,6 +113,8 @@ module.exports = async (opts) => {
111
113
 
112
114
  const { updateLoader } = loadersFactory();
113
115
 
116
+ engine.onSchemaDiff(getDiffHandler(engine, { force: opts.force, action: 'import' }));
117
+
114
118
  progress.on(`stage::start`, ({ stage, data }) => {
115
119
  updateLoader(stage, data).start();
116
120
  });
@@ -134,10 +138,7 @@ module.exports = async (opts) => {
134
138
  let results;
135
139
  try {
136
140
  // Abort transfer if user interrupts process
137
- ['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
138
- process.removeAllListeners(signal);
139
- process.on(signal, () => abortTransfer({ engine, strapi }));
140
- });
141
+ setSignalHandler(() => abortTransfer({ engine, strapi }));
141
142
 
142
143
  results = await engine.transfer();
143
144
  } catch (e) {
@@ -9,7 +9,7 @@ const {
9
9
  throttleOption,
10
10
  validateExcludeOnly,
11
11
  } = require('../../utils/data-transfer');
12
- const { confirmMessage, forceOption } = require('../../utils/commander');
12
+ const { getCommanderConfirmMessage, forceOption } = require('../../utils/commander');
13
13
  const { getLocalScript, exitWith } = require('../../utils/helpers');
14
14
 
15
15
  /**
@@ -88,7 +88,7 @@ module.exports = ({ command }) => {
88
88
  })
89
89
  .hook(
90
90
  'preAction',
91
- confirmMessage(
91
+ getCommanderConfirmMessage(
92
92
  'The import will delete all assets and data in your database. Are you sure you want to proceed?',
93
93
  { failMessage: 'Import process aborted' }
94
94
  )
@@ -22,6 +22,8 @@ const {
22
22
  exitMessageText,
23
23
  abortTransfer,
24
24
  getTransferTelemetryPayload,
25
+ setSignalHandler,
26
+ getDiffHandler,
25
27
  } = require('../../utils/data-transfer');
26
28
  const { exitWith } = require('../../utils/helpers');
27
29
 
@@ -146,6 +148,8 @@ module.exports = async (opts) => {
146
148
 
147
149
  const { updateLoader } = loadersFactory();
148
150
 
151
+ engine.onSchemaDiff(getDiffHandler(engine, { force: opts.force, action: 'transfer' }));
152
+
149
153
  progress.on(`stage::start`, ({ stage, data }) => {
150
154
  updateLoader(stage, data).start();
151
155
  });
@@ -171,10 +175,7 @@ module.exports = async (opts) => {
171
175
  let results;
172
176
  try {
173
177
  // Abort transfer if user interrupts process
174
- ['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
175
- process.removeAllListeners(signal);
176
- process.on(signal, () => abortTransfer({ engine, strapi }));
177
- });
178
+ setSignalHandler(() => abortTransfer({ engine, strapi }));
178
179
 
179
180
  results = await engine.transfer();
180
181
  } catch (e) {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const inquirer = require('inquirer');
4
4
  const { Option } = require('commander');
5
- const { confirmMessage, forceOption, parseURL } = require('../../utils/commander');
5
+ const { getCommanderConfirmMessage, forceOption, parseURL } = require('../../utils/commander');
6
6
  const {
7
7
  getLocalScript,
8
8
  exitWith,
@@ -76,7 +76,7 @@ module.exports = ({ command }) => {
76
76
  thisCommand.opts().fromToken = answers.fromToken;
77
77
  }
78
78
 
79
- await confirmMessage(
79
+ await getCommanderConfirmMessage(
80
80
  'The transfer will delete all the local Strapi assets and its database. Are you sure you want to proceed?',
81
81
  { failMessage: 'Transfer process aborted' }
82
82
  )(thisCommand);
@@ -104,7 +104,7 @@ module.exports = ({ command }) => {
104
104
  thisCommand.opts().toToken = answers.toToken;
105
105
  }
106
106
 
107
- await confirmMessage(
107
+ await getCommanderConfirmMessage(
108
108
  'The transfer will delete all the remote Strapi assets and its database. Are you sure you want to proceed?',
109
109
  { failMessage: 'Transfer process aborted' }
110
110
  )(thisCommand);
@@ -111,30 +111,35 @@ const promptEncryptionKey = async (thisCommand) => {
111
111
  * @param {object} options Additional options
112
112
  * @param {string|undefined} options.failMessage The message to display when prompt is not confirmed
113
113
  */
114
- const confirmMessage = (message, { failMessage } = {}) => {
114
+ const getCommanderConfirmMessage = (message, { failMessage } = {}) => {
115
115
  return async (command) => {
116
- // if we have a force option, assume yes
117
- const opts = command.opts();
118
- if (opts?.force === true) {
119
- // attempt to mimic the inquirer prompt exactly
120
- console.log(`${green('?')} ${bold(message)} ${cyan('Yes')}`);
121
- return;
122
- }
123
-
124
- const answers = await inquirer.prompt([
125
- {
126
- type: 'confirm',
127
- message,
128
- name: `confirm`,
129
- default: false,
130
- },
131
- ]);
132
- if (!answers.confirm) {
116
+ const confirmed = await confirmMessage(message, { force: command.opts().force });
117
+ if (!confirmed) {
133
118
  exitWith(1, failMessage);
134
119
  }
135
120
  };
136
121
  };
137
122
 
123
+ const confirmMessage = async (message, { force } = {}) => {
124
+ // if we have a force option, respond yes
125
+ if (force === true) {
126
+ // attempt to mimic the inquirer prompt exactly
127
+ console.log(`${green('?')} ${bold(message)} ${cyan('Yes')}`);
128
+ return true;
129
+ }
130
+
131
+ const answers = await inquirer.prompt([
132
+ {
133
+ type: 'confirm',
134
+ message,
135
+ name: `confirm`,
136
+ default: false,
137
+ },
138
+ ]);
139
+
140
+ return answers.confirm;
141
+ };
142
+
138
143
  const forceOption = new Option(
139
144
  '--force',
140
145
  `Automatically answer "yes" to all prompts, including potentially destructive requests, and run non-interactively.`
@@ -146,6 +151,7 @@ module.exports = {
146
151
  parseURL,
147
152
  parseInteger,
148
153
  promptEncryptionKey,
154
+ getCommanderConfirmMessage,
149
155
  confirmMessage,
150
156
  forceOption,
151
157
  };
@@ -12,9 +12,11 @@ const {
12
12
  createLogger,
13
13
  } = require('@strapi/logger');
14
14
  const ora = require('ora');
15
+ const { TransferEngineInitializationError } = require('@strapi/data-transfer/dist/engine/errors');
16
+ const { merge } = require('lodash/fp');
15
17
  const { readableBytes, exitWith } = require('./helpers');
16
18
  const strapi = require('../../index');
17
- const { getParseListWithChoices, parseInteger } = require('./commander');
19
+ const { getParseListWithChoices, parseInteger, confirmMessage } = require('./commander');
18
20
 
19
21
  const exitMessageText = (process, error = false) => {
20
22
  const processCapitalized = process[0].toUpperCase() + process.slice(1);
@@ -113,6 +115,15 @@ const abortTransfer = async ({ engine, strapi }) => {
113
115
  return true;
114
116
  };
115
117
 
118
+ const setSignalHandler = async (handler, signals = ['SIGINT', 'SIGTERM', 'SIGQUIT']) => {
119
+ signals.forEach((signal) => {
120
+ // We specifically remove ALL listeners because we have to clear the one added in Strapi bootstrap that has a process.exit
121
+ // TODO: Ideally Strapi bootstrap would not add that listener, and then this could be more flexible and add/remove only what it needs to
122
+ process.removeAllListeners(signal);
123
+ process.on(signal, handler);
124
+ });
125
+ };
126
+
116
127
  const createStrapiInstance = async (opts = {}) => {
117
128
  try {
118
129
  const appContext = await strapi.compile();
@@ -257,6 +268,79 @@ const getTransferTelemetryPayload = (engine) => {
257
268
  };
258
269
  };
259
270
 
271
+ /**
272
+ * Get a transfer engine schema diff handler that confirms with the user before bypassing a schema check
273
+ */
274
+ const getDiffHandler = (engine, { force, action }) => {
275
+ return async (context, next) => {
276
+ // if we abort here, we need to actually exit the process because of conflict with inquirer prompt
277
+ setSignalHandler(async () => {
278
+ await abortTransfer({ engine, strapi });
279
+ exitWith(1, exitMessageText(action, true));
280
+ });
281
+
282
+ let workflowsStatus;
283
+ const source = 'Schema Integrity';
284
+
285
+ Object.entries(context.diffs).forEach(([uid, diffs]) => {
286
+ for (const diff of diffs) {
287
+ const path = [uid].concat(diff.path).join('.');
288
+ const endPath = diff.path[diff.path.length - 1];
289
+
290
+ // Catch known features
291
+ if (
292
+ uid === 'admin::workflow' ||
293
+ uid === 'admin::workflow-stage' ||
294
+ endPath?.startsWith('strapi_reviewWorkflows_')
295
+ ) {
296
+ workflowsStatus = diff.kind;
297
+ }
298
+ // handle generic cases
299
+ else if (diff.kind === 'added') {
300
+ engine.reportWarning(chalk.red(`${chalk.bold(path)} does not exist on source`), source);
301
+ } else if (diff.kind === 'deleted') {
302
+ engine.reportWarning(
303
+ chalk.red(`${chalk.bold(path)} does not exist on destination`),
304
+ source
305
+ );
306
+ } else if (diff.kind === 'modified') {
307
+ engine.reportWarning(chalk.red(`${chalk.bold(path)} has a different data type`), source);
308
+ }
309
+ }
310
+ });
311
+
312
+ // output the known feature warnings
313
+ if (workflowsStatus === 'added') {
314
+ engine.reportWarning(chalk.red(`Review workflows feature does not exist on source`), source);
315
+ } else if (workflowsStatus === 'deleted') {
316
+ engine.reportWarning(
317
+ chalk.red(`Review workflows feature does not exist on destination`),
318
+ source
319
+ );
320
+ } else if (workflowsStatus === 'modified') {
321
+ engine.panic(
322
+ new TransferEngineInitializationError('Unresolved differences in schema [review workflows]')
323
+ );
324
+ }
325
+
326
+ const confirmed = await confirmMessage(
327
+ '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?',
328
+ {
329
+ force,
330
+ }
331
+ );
332
+
333
+ // reset handler back to normal
334
+ setSignalHandler(() => abortTransfer({ engine, strapi }));
335
+
336
+ if (confirmed) {
337
+ context.ignoredDiffs = merge(context.diffs, context.ignoredDiffs);
338
+ }
339
+
340
+ return next(context);
341
+ };
342
+ };
343
+
260
344
  module.exports = {
261
345
  loadersFactory,
262
346
  buildTransferTable,
@@ -271,4 +355,6 @@ module.exports = {
271
355
  validateExcludeOnly,
272
356
  formatDiagnostic,
273
357
  abortTransfer,
358
+ setSignalHandler,
359
+ getDiffHandler,
274
360
  };
@@ -21,6 +21,14 @@ interface CustomFieldServerOptions {
21
21
  * The existing Strapi data type the custom field uses
22
22
  */
23
23
  type: string;
24
+
25
+ /**
26
+ * Settings for the input size in the Admin UI
27
+ */
28
+ inputSize?: {
29
+ default: 4 | 6 | 8 | 12;
30
+ isResizable: boolean;
31
+ };
24
32
  }
25
33
 
26
34
  interface CustomFields {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/strapi",
3
- "version": "4.10.4",
3
+ "version": "4.10.5",
4
4
  "description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
5
5
  "keywords": [
6
6
  "strapi",
@@ -81,19 +81,19 @@
81
81
  "dependencies": {
82
82
  "@koa/cors": "3.4.3",
83
83
  "@koa/router": "10.1.1",
84
- "@strapi/admin": "4.10.4",
85
- "@strapi/data-transfer": "4.10.4",
86
- "@strapi/database": "4.10.4",
87
- "@strapi/generate-new": "4.10.4",
88
- "@strapi/generators": "4.10.4",
89
- "@strapi/logger": "4.10.4",
90
- "@strapi/permissions": "4.10.4",
91
- "@strapi/plugin-content-manager": "4.10.4",
92
- "@strapi/plugin-content-type-builder": "4.10.4",
93
- "@strapi/plugin-email": "4.10.4",
94
- "@strapi/plugin-upload": "4.10.4",
95
- "@strapi/typescript-utils": "4.10.4",
96
- "@strapi/utils": "4.10.4",
84
+ "@strapi/admin": "4.10.5",
85
+ "@strapi/data-transfer": "4.10.5",
86
+ "@strapi/database": "4.10.5",
87
+ "@strapi/generate-new": "4.10.5",
88
+ "@strapi/generators": "4.10.5",
89
+ "@strapi/logger": "4.10.5",
90
+ "@strapi/permissions": "4.10.5",
91
+ "@strapi/plugin-content-manager": "4.10.5",
92
+ "@strapi/plugin-content-type-builder": "4.10.5",
93
+ "@strapi/plugin-email": "4.10.5",
94
+ "@strapi/plugin-upload": "4.10.5",
95
+ "@strapi/typescript-utils": "4.10.5",
96
+ "@strapi/utils": "4.10.5",
97
97
  "bcryptjs": "2.4.3",
98
98
  "boxen": "5.1.2",
99
99
  "chalk": "4.1.2",
@@ -117,7 +117,7 @@
117
117
  "koa-compose": "4.1.0",
118
118
  "koa-compress": "5.1.0",
119
119
  "koa-favicon": "2.1.0",
120
- "koa-helmet": "6.1.0",
120
+ "koa-helmet": "7.0.2",
121
121
  "koa-ip": "^2.1.2",
122
122
  "koa-session": "6.4.0",
123
123
  "koa-static": "5.0.0",
@@ -142,5 +142,5 @@
142
142
  "node": ">=14.19.1 <=18.x.x",
143
143
  "npm": ">=6.0.0"
144
144
  },
145
- "gitHead": "3f55bac2e7fc3b15c85ac6910be1e95bb7eed9e5"
145
+ "gitHead": "d9277d616b4478a3839e79e47330a4aaf167a2f1"
146
146
  }