@strapi/strapi 5.9.0 → 5.10.0
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.
- package/dist/admin-test.js +11 -8
- package/dist/admin-test.js.map +1 -1
- package/dist/admin-test.mjs +1 -1
- package/dist/admin-test.mjs.map +1 -1
- package/dist/admin.js +36 -42
- package/dist/admin.js.map +1 -1
- package/dist/admin.mjs +28 -35
- package/dist/admin.mjs.map +1 -1
- package/dist/chunks/aliases-Ca20Yntz.js +80 -0
- package/dist/chunks/aliases-Ca20Yntz.js.map +1 -0
- package/dist/chunks/aliases-DQ51KexN.mjs +76 -0
- package/dist/chunks/aliases-DQ51KexN.mjs.map +1 -0
- package/dist/chunks/build-3I6A92J1.js +59 -0
- package/dist/chunks/build-3I6A92J1.js.map +1 -0
- package/dist/chunks/build-BqigoS45.mjs +85 -0
- package/dist/chunks/build-BqigoS45.mjs.map +1 -0
- package/dist/chunks/build-CWUlYFQo.js +87 -0
- package/dist/chunks/build-CWUlYFQo.js.map +1 -0
- package/dist/chunks/build-gBR43Dth.mjs +57 -0
- package/dist/chunks/build-gBR43Dth.mjs.map +1 -0
- package/dist/chunks/config-5tFWkbOi.js +154 -0
- package/dist/chunks/config-5tFWkbOi.js.map +1 -0
- package/dist/chunks/config-BSlsDIVc.mjs +243 -0
- package/dist/chunks/config-BSlsDIVc.mjs.map +1 -0
- package/dist/chunks/config-CLeubzgx.mjs +150 -0
- package/dist/chunks/config-CLeubzgx.mjs.map +1 -0
- package/dist/chunks/config-CQ94iNYq.js +247 -0
- package/dist/chunks/config-CQ94iNYq.js.map +1 -0
- package/dist/chunks/index-DtHH3PFG.mjs +3160 -0
- package/dist/chunks/index-DtHH3PFG.mjs.map +1 -0
- package/dist/chunks/index-kv_K5f7_.js +3185 -0
- package/dist/chunks/index-kv_K5f7_.js.map +1 -0
- package/dist/chunks/watch-B7qfL21s.js +146 -0
- package/dist/chunks/watch-B7qfL21s.js.map +1 -0
- package/dist/chunks/watch-BBDpuCFC.js +133 -0
- package/dist/chunks/watch-BBDpuCFC.js.map +1 -0
- package/dist/chunks/watch-Bhd-75O5.mjs +131 -0
- package/dist/chunks/watch-Bhd-75O5.mjs.map +1 -0
- package/dist/chunks/watch-CJr1UoWF.mjs +144 -0
- package/dist/chunks/watch-CJr1UoWF.mjs.map +1 -0
- package/dist/cli/commands/plugin/init/files/typescript.d.ts +20 -7
- package/dist/cli/commands/plugin/init/files/typescript.d.ts.map +1 -1
- package/dist/cli/commands/version.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli.js +50 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +43 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.js +11 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/node/core/errors.d.ts.map +1 -1
- package/dist/node/create-build-context.d.ts +1 -1
- package/dist/node/create-build-context.d.ts.map +1 -1
- package/package.json +26 -26
- package/dist/cli/commands/admin/create-user.js +0 -91
- package/dist/cli/commands/admin/create-user.js.map +0 -1
- package/dist/cli/commands/admin/create-user.mjs +0 -88
- package/dist/cli/commands/admin/create-user.mjs.map +0 -1
- package/dist/cli/commands/admin/reset-user-password.js +0 -47
- package/dist/cli/commands/admin/reset-user-password.js.map +0 -1
- package/dist/cli/commands/admin/reset-user-password.mjs +0 -44
- package/dist/cli/commands/admin/reset-user-password.mjs.map +0 -1
- package/dist/cli/commands/build.js +0 -24
- package/dist/cli/commands/build.js.map +0 -1
- package/dist/cli/commands/build.mjs +0 -24
- package/dist/cli/commands/build.mjs.map +0 -1
- package/dist/cli/commands/components/list.js +0 -27
- package/dist/cli/commands/components/list.js.map +0 -1
- package/dist/cli/commands/components/list.mjs +0 -24
- package/dist/cli/commands/components/list.mjs.map +0 -1
- package/dist/cli/commands/configuration/dump.js +0 -43
- package/dist/cli/commands/configuration/dump.js.map +0 -1
- package/dist/cli/commands/configuration/dump.mjs +0 -41
- package/dist/cli/commands/configuration/dump.mjs.map +0 -1
- package/dist/cli/commands/configuration/restore.js +0 -142
- package/dist/cli/commands/configuration/restore.js.map +0 -1
- package/dist/cli/commands/configuration/restore.mjs +0 -139
- package/dist/cli/commands/configuration/restore.mjs.map +0 -1
- package/dist/cli/commands/console.js +0 -29
- package/dist/cli/commands/console.js.map +0 -1
- package/dist/cli/commands/console.mjs +0 -27
- package/dist/cli/commands/console.mjs.map +0 -1
- package/dist/cli/commands/content-types/list.js +0 -27
- package/dist/cli/commands/content-types/list.js.map +0 -1
- package/dist/cli/commands/content-types/list.mjs +0 -24
- package/dist/cli/commands/content-types/list.mjs.map +0 -1
- package/dist/cli/commands/controllers/list.js +0 -27
- package/dist/cli/commands/controllers/list.js.map +0 -1
- package/dist/cli/commands/controllers/list.mjs +0 -24
- package/dist/cli/commands/controllers/list.mjs.map +0 -1
- package/dist/cli/commands/develop.js +0 -29
- package/dist/cli/commands/develop.js.map +0 -1
- package/dist/cli/commands/develop.mjs +0 -27
- package/dist/cli/commands/develop.mjs.map +0 -1
- package/dist/cli/commands/export/action.js +0 -118
- package/dist/cli/commands/export/action.js.map +0 -1
- package/dist/cli/commands/export/action.mjs +0 -116
- package/dist/cli/commands/export/action.mjs.map +0 -1
- package/dist/cli/commands/export/command.js +0 -23
- package/dist/cli/commands/export/command.js.map +0 -1
- package/dist/cli/commands/export/command.mjs +0 -24
- package/dist/cli/commands/export/command.mjs.map +0 -1
- package/dist/cli/commands/generate.js +0 -35
- package/dist/cli/commands/generate.js.map +0 -1
- package/dist/cli/commands/generate.mjs +0 -13
- package/dist/cli/commands/generate.mjs.map +0 -1
- package/dist/cli/commands/hooks/list.js +0 -27
- package/dist/cli/commands/hooks/list.js.map +0 -1
- package/dist/cli/commands/hooks/list.mjs +0 -24
- package/dist/cli/commands/hooks/list.mjs.map +0 -1
- package/dist/cli/commands/import/action.js +0 -106
- package/dist/cli/commands/import/action.js.map +0 -1
- package/dist/cli/commands/import/action.mjs +0 -105
- package/dist/cli/commands/import/action.mjs.map +0 -1
- package/dist/cli/commands/import/command.js +0 -70
- package/dist/cli/commands/import/command.js.map +0 -1
- package/dist/cli/commands/import/command.mjs +0 -68
- package/dist/cli/commands/import/command.mjs.map +0 -1
- package/dist/cli/commands/index.js +0 -63
- package/dist/cli/commands/index.js.map +0 -1
- package/dist/cli/commands/index.mjs +0 -63
- package/dist/cli/commands/index.mjs.map +0 -1
- package/dist/cli/commands/middlewares/list.js +0 -27
- package/dist/cli/commands/middlewares/list.js.map +0 -1
- package/dist/cli/commands/middlewares/list.mjs +0 -24
- package/dist/cli/commands/middlewares/list.mjs.map +0 -1
- package/dist/cli/commands/policies/list.js +0 -27
- package/dist/cli/commands/policies/list.js.map +0 -1
- package/dist/cli/commands/policies/list.mjs +0 -24
- package/dist/cli/commands/policies/list.mjs.map +0 -1
- package/dist/cli/commands/report.js +0 -35
- package/dist/cli/commands/report.js.map +0 -1
- package/dist/cli/commands/report.mjs +0 -35
- package/dist/cli/commands/report.mjs.map +0 -1
- package/dist/cli/commands/routes/list.js +0 -31
- package/dist/cli/commands/routes/list.js.map +0 -1
- package/dist/cli/commands/routes/list.mjs +0 -28
- package/dist/cli/commands/routes/list.mjs.map +0 -1
- package/dist/cli/commands/services/list.js +0 -27
- package/dist/cli/commands/services/list.js.map +0 -1
- package/dist/cli/commands/services/list.mjs +0 -24
- package/dist/cli/commands/services/list.mjs.map +0 -1
- package/dist/cli/commands/start.js +0 -27
- package/dist/cli/commands/start.js.map +0 -1
- package/dist/cli/commands/start.mjs +0 -24
- package/dist/cli/commands/start.mjs.map +0 -1
- package/dist/cli/commands/telemetry/disable.js +0 -70
- package/dist/cli/commands/telemetry/disable.js.map +0 -1
- package/dist/cli/commands/telemetry/disable.mjs +0 -67
- package/dist/cli/commands/telemetry/disable.mjs.map +0 -1
- package/dist/cli/commands/telemetry/enable.js +0 -89
- package/dist/cli/commands/telemetry/enable.js.map +0 -1
- package/dist/cli/commands/telemetry/enable.mjs +0 -86
- package/dist/cli/commands/telemetry/enable.mjs.map +0 -1
- package/dist/cli/commands/templates/generate.js +0 -11
- package/dist/cli/commands/templates/generate.js.map +0 -1
- package/dist/cli/commands/templates/generate.mjs +0 -11
- package/dist/cli/commands/templates/generate.mjs.map +0 -1
- package/dist/cli/commands/transfer/action.js +0 -130
- package/dist/cli/commands/transfer/action.js.map +0 -1
- package/dist/cli/commands/transfer/action.mjs +0 -131
- package/dist/cli/commands/transfer/action.mjs.map +0 -1
- package/dist/cli/commands/transfer/command.js +0 -85
- package/dist/cli/commands/transfer/command.js.map +0 -1
- package/dist/cli/commands/transfer/command.mjs +0 -84
- package/dist/cli/commands/transfer/command.mjs.map +0 -1
- package/dist/cli/commands/ts/generate-types.js +0 -36
- package/dist/cli/commands/ts/generate-types.js.map +0 -1
- package/dist/cli/commands/ts/generate-types.mjs +0 -34
- package/dist/cli/commands/ts/generate-types.mjs.map +0 -1
- package/dist/cli/commands/version.js +0 -13
- package/dist/cli/commands/version.js.map +0 -1
- package/dist/cli/commands/version.mjs +0 -13
- package/dist/cli/commands/version.mjs.map +0 -1
- package/dist/cli/index.js +0 -79
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/index.mjs +0 -79
- package/dist/cli/index.mjs.map +0 -1
- package/dist/cli/utils/commander.js +0 -111
- package/dist/cli/utils/commander.js.map +0 -1
- package/dist/cli/utils/commander.mjs +0 -108
- package/dist/cli/utils/commander.mjs.map +0 -1
- package/dist/cli/utils/data-transfer.js +0 -354
- package/dist/cli/utils/data-transfer.js.map +0 -1
- package/dist/cli/utils/data-transfer.mjs +0 -350
- package/dist/cli/utils/data-transfer.mjs.map +0 -1
- package/dist/cli/utils/helpers.js +0 -102
- package/dist/cli/utils/helpers.js.map +0 -1
- package/dist/cli/utils/helpers.mjs +0 -100
- package/dist/cli/utils/helpers.mjs.map +0 -1
- package/dist/cli/utils/logger.js +0 -137
- package/dist/cli/utils/logger.js.map +0 -1
- package/dist/cli/utils/logger.mjs +0 -116
- package/dist/cli/utils/logger.mjs.map +0 -1
- package/dist/cli/utils/telemetry.js +0 -22
- package/dist/cli/utils/telemetry.js.map +0 -1
- package/dist/cli/utils/telemetry.mjs +0 -22
- package/dist/cli/utils/telemetry.mjs.map +0 -1
- package/dist/cli/utils/tsconfig.js +0 -26
- package/dist/cli/utils/tsconfig.js.map +0 -1
- package/dist/cli/utils/tsconfig.mjs +0 -23
- package/dist/cli/utils/tsconfig.mjs.map +0 -1
- package/dist/node/build.js +0 -76
- package/dist/node/build.js.map +0 -1
- package/dist/node/build.mjs +0 -58
- package/dist/node/build.mjs.map +0 -1
- package/dist/node/core/admin-customisations.js +0 -24
- package/dist/node/core/admin-customisations.js.map +0 -1
- package/dist/node/core/admin-customisations.mjs +0 -22
- package/dist/node/core/admin-customisations.mjs.map +0 -1
- package/dist/node/core/aliases.js +0 -33
- package/dist/node/core/aliases.js.map +0 -1
- package/dist/node/core/aliases.mjs +0 -31
- package/dist/node/core/aliases.mjs.map +0 -1
- package/dist/node/core/config.js +0 -18
- package/dist/node/core/config.js.map +0 -1
- package/dist/node/core/config.mjs +0 -16
- package/dist/node/core/config.mjs.map +0 -1
- package/dist/node/core/dependencies.js +0 -163
- package/dist/node/core/dependencies.js.map +0 -1
- package/dist/node/core/dependencies.mjs +0 -155
- package/dist/node/core/dependencies.mjs.map +0 -1
- package/dist/node/core/env.js +0 -26
- package/dist/node/core/env.js.map +0 -1
- package/dist/node/core/env.mjs +0 -23
- package/dist/node/core/env.mjs.map +0 -1
- package/dist/node/core/errors.js +0 -33
- package/dist/node/core/errors.js.map +0 -1
- package/dist/node/core/errors.mjs +0 -29
- package/dist/node/core/errors.mjs.map +0 -1
- package/dist/node/core/files.js +0 -45
- package/dist/node/core/files.js.map +0 -1
- package/dist/node/core/files.mjs +0 -43
- package/dist/node/core/files.mjs.map +0 -1
- package/dist/node/core/managers.js +0 -17
- package/dist/node/core/managers.js.map +0 -1
- package/dist/node/core/managers.mjs +0 -17
- package/dist/node/core/managers.mjs.map +0 -1
- package/dist/node/core/monorepo.js +0 -23
- package/dist/node/core/monorepo.js.map +0 -1
- package/dist/node/core/monorepo.mjs +0 -20
- package/dist/node/core/monorepo.mjs.map +0 -1
- package/dist/node/core/plugins.js +0 -116
- package/dist/node/core/plugins.js.map +0 -1
- package/dist/node/core/plugins.mjs +0 -111
- package/dist/node/core/plugins.mjs.map +0 -1
- package/dist/node/core/timer.js +0 -27
- package/dist/node/core/timer.js.map +0 -1
- package/dist/node/core/timer.mjs +0 -27
- package/dist/node/core/timer.mjs.map +0 -1
- package/dist/node/create-build-context.js +0 -101
- package/dist/node/create-build-context.js.map +0 -1
- package/dist/node/create-build-context.mjs +0 -96
- package/dist/node/create-build-context.mjs.map +0 -1
- package/dist/node/develop.js +0 -275
- package/dist/node/develop.js.map +0 -1
- package/dist/node/develop.mjs +0 -252
- package/dist/node/develop.mjs.map +0 -1
- package/dist/node/staticFiles.js +0 -102
- package/dist/node/staticFiles.js.map +0 -1
- package/dist/node/staticFiles.mjs +0 -76
- package/dist/node/staticFiles.mjs.map +0 -1
- package/dist/node/vite/build.js +0 -34
- package/dist/node/vite/build.js.map +0 -1
- package/dist/node/vite/build.mjs +0 -12
- package/dist/node/vite/build.mjs.map +0 -1
- package/dist/node/vite/config.js +0 -106
- package/dist/node/vite/config.js.map +0 -1
- package/dist/node/vite/config.mjs +0 -103
- package/dist/node/vite/config.mjs.map +0 -1
- package/dist/node/vite/plugins.js +0 -43
- package/dist/node/vite/plugins.js.map +0 -1
- package/dist/node/vite/plugins.mjs +0 -43
- package/dist/node/vite/plugins.mjs.map +0 -1
- package/dist/node/vite/watch.js +0 -111
- package/dist/node/vite/watch.js.map +0 -1
- package/dist/node/vite/watch.mjs +0 -85
- package/dist/node/vite/watch.mjs.map +0 -1
- package/dist/node/webpack/build.js +0 -41
- package/dist/node/webpack/build.js.map +0 -1
- package/dist/node/webpack/build.mjs +0 -39
- package/dist/node/webpack/build.mjs.map +0 -1
- package/dist/node/webpack/config.js +0 -259
- package/dist/node/webpack/config.js.map +0 -1
- package/dist/node/webpack/config.mjs +0 -228
- package/dist/node/webpack/config.mjs.map +0 -1
- package/dist/node/webpack/watch.js +0 -95
- package/dist/node/webpack/watch.js.map +0 -1
- package/dist/node/webpack/watch.mjs +0 -90
- package/dist/node/webpack/watch.mjs.map +0 -1
|
@@ -0,0 +1,3185 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var commander = require('commander');
|
|
4
|
+
var cloudCli = require('@strapi/cloud-cli');
|
|
5
|
+
var utils = require('@strapi/utils');
|
|
6
|
+
var _ = require('lodash');
|
|
7
|
+
var inquirer = require('inquirer');
|
|
8
|
+
var core = require('@strapi/core');
|
|
9
|
+
var chalk = require('chalk');
|
|
10
|
+
var fp = require('lodash/fp');
|
|
11
|
+
var boxen = require('boxen');
|
|
12
|
+
var CLITable = require('cli-table3');
|
|
13
|
+
var fs = require('fs');
|
|
14
|
+
var path = require('path');
|
|
15
|
+
var fse = require('fs-extra');
|
|
16
|
+
var crypto = require('crypto');
|
|
17
|
+
var tsUtils = require('@strapi/typescript-utils');
|
|
18
|
+
var os = require('node:os');
|
|
19
|
+
var fs$1 = require('node:fs/promises');
|
|
20
|
+
var path$1 = require('node:path');
|
|
21
|
+
var semver = require('semver');
|
|
22
|
+
var resolveFrom = require('resolve-from');
|
|
23
|
+
var execa = require('execa');
|
|
24
|
+
var readPkgUp = require('read-pkg-up');
|
|
25
|
+
var perf_hooks = require('perf_hooks');
|
|
26
|
+
var browserslist = require('browserslist');
|
|
27
|
+
var dotenv = require('dotenv');
|
|
28
|
+
var node = require('esbuild-register/dist/node');
|
|
29
|
+
var fs$2 = require('node:fs');
|
|
30
|
+
var camelCase = require('lodash/camelCase');
|
|
31
|
+
var outdent = require('outdent');
|
|
32
|
+
var react = require('react');
|
|
33
|
+
var server = require('react-dom/server');
|
|
34
|
+
var _internal = require('@strapi/admin/_internal');
|
|
35
|
+
var REPL = require('repl');
|
|
36
|
+
var cluster = require('node:cluster');
|
|
37
|
+
var chokidar = require('chokidar');
|
|
38
|
+
var os$1 = require('os');
|
|
39
|
+
var logger = require('@strapi/logger');
|
|
40
|
+
var ora = require('ora');
|
|
41
|
+
var dataTransfer = require('@strapi/data-transfer');
|
|
42
|
+
var cliProgress = require('cli-progress');
|
|
43
|
+
var ts = require('typescript');
|
|
44
|
+
|
|
45
|
+
function _interopNamespaceDefault(e) {
|
|
46
|
+
var n = Object.create(null);
|
|
47
|
+
if (e) {
|
|
48
|
+
Object.keys(e).forEach(function (k) {
|
|
49
|
+
if (k !== 'default') {
|
|
50
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
51
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
get: function () { return e[k]; }
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
n.default = e;
|
|
59
|
+
return Object.freeze(n);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var tsUtils__namespace = /*#__PURE__*/_interopNamespaceDefault(tsUtils);
|
|
63
|
+
var cliProgress__namespace = /*#__PURE__*/_interopNamespaceDefault(cliProgress);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Helper functions for the Strapi CLI
|
|
67
|
+
*/ const bytesPerKb = 1024;
|
|
68
|
+
const sizes = [
|
|
69
|
+
'B ',
|
|
70
|
+
'KB',
|
|
71
|
+
'MB',
|
|
72
|
+
'GB',
|
|
73
|
+
'TB',
|
|
74
|
+
'PB'
|
|
75
|
+
];
|
|
76
|
+
/**
|
|
77
|
+
* Convert bytes to a human readable formatted string, for example "1024" becomes "1KB"
|
|
78
|
+
*/ const readableBytes = (bytes, decimals = 1, padStart = 0)=>{
|
|
79
|
+
if (!bytes) {
|
|
80
|
+
return '0';
|
|
81
|
+
}
|
|
82
|
+
const i = Math.floor(Math.log(bytes) / Math.log(bytesPerKb));
|
|
83
|
+
const result = `${parseFloat((bytes / bytesPerKb ** i).toFixed(decimals))} ${sizes[i].padStart(2)}`;
|
|
84
|
+
return result.padStart(padStart);
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
*
|
|
88
|
+
* Display message(s) to console and then call process.exit with code.
|
|
89
|
+
* If code is zero, console.log and green text is used for messages, otherwise console.error and red text.
|
|
90
|
+
*
|
|
91
|
+
*/ const exitWith = (code, message, options = {})=>{
|
|
92
|
+
const { logger = console, prc = process } = options;
|
|
93
|
+
const log = (message)=>{
|
|
94
|
+
if (code === 0) {
|
|
95
|
+
logger.log(chalk.green(message));
|
|
96
|
+
} else {
|
|
97
|
+
logger.error(chalk.red(message));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
if (fp.isString(message)) {
|
|
101
|
+
log(message);
|
|
102
|
+
} else if (fp.isArray(message)) {
|
|
103
|
+
message.forEach((msg)=>log(msg));
|
|
104
|
+
}
|
|
105
|
+
prc.exit(code);
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* assert that a URL object has a protocol value
|
|
109
|
+
*
|
|
110
|
+
*/ const assertUrlHasProtocol = (url, protocol)=>{
|
|
111
|
+
if (!url.protocol) {
|
|
112
|
+
exitWith(1, `${url.toString()} does not have a protocol`);
|
|
113
|
+
}
|
|
114
|
+
// if just checking for the existence of a protocol, return
|
|
115
|
+
if (!protocol) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (fp.isString(protocol)) {
|
|
119
|
+
if (protocol !== url.protocol) {
|
|
120
|
+
exitWith(1, `${url.toString()} must have the protocol ${protocol}`);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// assume an array
|
|
125
|
+
if (!protocol.some((protocol)=>url.protocol === protocol)) {
|
|
126
|
+
return exitWith(1, `${url.toString()} must have one of the following protocols: ${protocol.join(',')}`);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Passes commander options to conditionCallback(). If it returns true, call isMetCallback otherwise call isNotMetCallback
|
|
131
|
+
*/ const ifOptions = (conditionCallback, isMetCallback = async ()=>{}, isNotMetCallback = async ()=>{})=>{
|
|
132
|
+
return async (command)=>{
|
|
133
|
+
const opts = command.opts();
|
|
134
|
+
if (await conditionCallback(opts)) {
|
|
135
|
+
await isMetCallback(command);
|
|
136
|
+
} else {
|
|
137
|
+
await isNotMetCallback(command);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
const assertCwdContainsStrapiProject = (name)=>{
|
|
142
|
+
const logErrorAndExit = ()=>{
|
|
143
|
+
console.log(`You need to run ${chalk.yellow(`strapi ${name}`)} in a Strapi project. Make sure you are in the right directory.`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
const pkgJSON = require(`${process.cwd()}/package.json`);
|
|
148
|
+
if (!fp.has('dependencies.@strapi/strapi', pkgJSON) && !fp.has('devDependencies.@strapi/strapi', pkgJSON)) {
|
|
149
|
+
logErrorAndExit();
|
|
150
|
+
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
logErrorAndExit();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const runAction = (name, action)=>(...args)=>{
|
|
156
|
+
assertCwdContainsStrapiProject(name);
|
|
157
|
+
Promise.resolve().then(()=>{
|
|
158
|
+
return action(...args);
|
|
159
|
+
}).catch((error)=>{
|
|
160
|
+
console.error(error);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const emailValidator = utils.yup.string().email('Invalid email address').lowercase();
|
|
166
|
+
const passwordValidator = utils.yup.string().min(8, 'Password must be at least 8 characters long').matches(/[a-z]/, 'Password must contain at least one lowercase character').matches(/[A-Z]/, 'Password must contain at least one uppercase character').matches(/\d/, 'Password must contain at least one number');
|
|
167
|
+
const adminCreateSchema = utils.yup.object().shape({
|
|
168
|
+
email: emailValidator,
|
|
169
|
+
password: passwordValidator,
|
|
170
|
+
firstname: utils.yup.string().trim().required('First name is required'),
|
|
171
|
+
lastname: utils.yup.string()
|
|
172
|
+
});
|
|
173
|
+
/**
|
|
174
|
+
* It's not an observable, in reality this is
|
|
175
|
+
* `ReadOnlyArray<inquirer.DistinctQuestion<Answers>>`
|
|
176
|
+
* but then the logic of the validate function needs to change.
|
|
177
|
+
*/ // eslint-disable-next-line rxjs/finnish
|
|
178
|
+
const promptQuestions$1 = [
|
|
179
|
+
{
|
|
180
|
+
type: 'input',
|
|
181
|
+
name: 'email',
|
|
182
|
+
message: 'Admin email?',
|
|
183
|
+
async validate (value) {
|
|
184
|
+
const validEmail = await emailValidator.validate(value);
|
|
185
|
+
return validEmail === value || validEmail;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
type: 'password',
|
|
190
|
+
name: 'password',
|
|
191
|
+
message: 'Admin password?',
|
|
192
|
+
async validate (value) {
|
|
193
|
+
const validPassword = await passwordValidator.validate(value);
|
|
194
|
+
return validPassword === value || validPassword;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: 'input',
|
|
199
|
+
name: 'firstname',
|
|
200
|
+
message: 'First name?'
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: 'input',
|
|
204
|
+
name: 'lastname',
|
|
205
|
+
message: 'Last name?'
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: 'confirm',
|
|
209
|
+
name: 'confirm',
|
|
210
|
+
message: 'Do you really want to create a new admin?'
|
|
211
|
+
}
|
|
212
|
+
];
|
|
213
|
+
async function createAdmin({ email, password, firstname, lastname }) {
|
|
214
|
+
const appContext = await core.compileStrapi();
|
|
215
|
+
const app = await core.createStrapi(appContext).load();
|
|
216
|
+
const user = await app.admin.services.user.exists({
|
|
217
|
+
email
|
|
218
|
+
});
|
|
219
|
+
if (user) {
|
|
220
|
+
console.error(`User with email "${email}" already exists`);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
const superAdminRole = await app.admin.services.role.getSuperAdmin();
|
|
224
|
+
await app.admin.services.user.create({
|
|
225
|
+
email,
|
|
226
|
+
firstname,
|
|
227
|
+
lastname,
|
|
228
|
+
isActive: true,
|
|
229
|
+
roles: [
|
|
230
|
+
superAdminRole.id
|
|
231
|
+
],
|
|
232
|
+
...password && {
|
|
233
|
+
password,
|
|
234
|
+
registrationToken: null
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
console.log(`Successfully created new admin`);
|
|
238
|
+
process.exit(0);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Create new admin user
|
|
242
|
+
*/ const action$m = async (cmdOptions = {})=>{
|
|
243
|
+
let { email, password, firstname, lastname } = cmdOptions;
|
|
244
|
+
if (_.isEmpty(email) && _.isEmpty(password) && _.isEmpty(firstname) && _.isEmpty(lastname) && process.stdin.isTTY) {
|
|
245
|
+
const inquiry = await inquirer.prompt(promptQuestions$1);
|
|
246
|
+
if (!inquiry.confirm) {
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
({ email, password, firstname, lastname } = inquiry);
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
await adminCreateSchema.validate({
|
|
253
|
+
email,
|
|
254
|
+
password,
|
|
255
|
+
firstname,
|
|
256
|
+
lastname
|
|
257
|
+
});
|
|
258
|
+
} catch (err) {
|
|
259
|
+
if (err instanceof utils.yup.ValidationError) {
|
|
260
|
+
console.error(err.errors[0]);
|
|
261
|
+
}
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
return createAdmin({
|
|
265
|
+
email,
|
|
266
|
+
password,
|
|
267
|
+
firstname,
|
|
268
|
+
lastname
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* `$ strapi admin:create-user`
|
|
273
|
+
*/ const command$p = ()=>{
|
|
274
|
+
return commander.createCommand('admin:create-user').alias('admin:create').description('Create a new admin').option('-e, --email <email>', 'Email of the new admin').option('-p, --password <password>', 'Password of the new admin').option('-f, --firstname <first name>', 'First name of the new admin').option('-l, --lastname <last name>', 'Last name of the new admin').action(runAction('admin:create-user', action$m));
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const promptQuestions = [
|
|
278
|
+
{
|
|
279
|
+
type: 'input',
|
|
280
|
+
name: 'email',
|
|
281
|
+
message: 'User email?'
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
type: 'password',
|
|
285
|
+
name: 'password',
|
|
286
|
+
message: 'New password?'
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
type: 'confirm',
|
|
290
|
+
name: 'confirm',
|
|
291
|
+
message: "Do you really want to reset this user's password?"
|
|
292
|
+
}
|
|
293
|
+
];
|
|
294
|
+
async function changePassword({ email, password }) {
|
|
295
|
+
const appContext = await core.compileStrapi();
|
|
296
|
+
const app = await core.createStrapi(appContext).load();
|
|
297
|
+
await app.admin.services.user.resetPasswordByEmail(email, password);
|
|
298
|
+
console.log(`Successfully reset user's password`);
|
|
299
|
+
process.exit(0);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Reset user's password
|
|
303
|
+
*/ const action$l = async (cmdOptions = {})=>{
|
|
304
|
+
const { email, password } = cmdOptions;
|
|
305
|
+
if (_.isEmpty(email) && _.isEmpty(password) && process.stdin.isTTY) {
|
|
306
|
+
const inquiry = await inquirer.prompt(promptQuestions);
|
|
307
|
+
if (!inquiry.confirm) {
|
|
308
|
+
process.exit(0);
|
|
309
|
+
}
|
|
310
|
+
return changePassword(inquiry);
|
|
311
|
+
}
|
|
312
|
+
if (_.isEmpty(email) || _.isEmpty(password)) {
|
|
313
|
+
console.error('Missing required options `email` or `password`');
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
return changePassword({
|
|
317
|
+
email,
|
|
318
|
+
password
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* `$ strapi admin:reset-user-password`
|
|
323
|
+
*/ const command$o = ()=>{
|
|
324
|
+
return commander.createCommand('admin:reset-user-password').alias('admin:reset-password').description("Reset an admin user's password").option('-e, --email <email>', 'The user email').option('-p, --password <password>', 'New password for the user').action(runAction('admin:reset-user-password', action$l));
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const action$k = async ()=>{
|
|
328
|
+
const appContext = await core.compileStrapi();
|
|
329
|
+
const app = await core.createStrapi(appContext).register();
|
|
330
|
+
const list = Object.keys(app.components);
|
|
331
|
+
const infoTable = new CLITable({
|
|
332
|
+
head: [
|
|
333
|
+
chalk.blue('Name')
|
|
334
|
+
]
|
|
335
|
+
});
|
|
336
|
+
list.forEach((name)=>infoTable.push([
|
|
337
|
+
name
|
|
338
|
+
]));
|
|
339
|
+
console.log(infoTable.toString());
|
|
340
|
+
await app.destroy();
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* `$ strapi components:list`
|
|
344
|
+
*/ const command$n = ()=>{
|
|
345
|
+
return commander.createCommand('components:list').description('List all the application components').action(runAction('components:list', action$k));
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const CHUNK_SIZE = 100;
|
|
349
|
+
/**
|
|
350
|
+
* Will dump configurations to a file or stdout
|
|
351
|
+
* @param {string} file filepath to use as output
|
|
352
|
+
*/ const action$j = async ({ file: filePath, pretty })=>{
|
|
353
|
+
const output = filePath ? fs.createWriteStream(filePath) : process.stdout;
|
|
354
|
+
const appContext = await core.compileStrapi();
|
|
355
|
+
const app = await core.createStrapi(appContext).load();
|
|
356
|
+
const count = await app.query('strapi::core-store').count();
|
|
357
|
+
const exportData = [];
|
|
358
|
+
const pageCount = Math.ceil(count / CHUNK_SIZE);
|
|
359
|
+
for(let page = 0; page < pageCount; page += 1){
|
|
360
|
+
const results = await app.query('strapi::core-store').findMany({
|
|
361
|
+
limit: CHUNK_SIZE,
|
|
362
|
+
offset: page * CHUNK_SIZE,
|
|
363
|
+
orderBy: 'key'
|
|
364
|
+
});
|
|
365
|
+
results.filter((result)=>result.key.startsWith('plugin_')).forEach((result)=>{
|
|
366
|
+
exportData.push({
|
|
367
|
+
key: result.key,
|
|
368
|
+
value: result.value,
|
|
369
|
+
type: result.type,
|
|
370
|
+
environment: result.environment,
|
|
371
|
+
tag: result.tag
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const str = JSON.stringify(exportData, null, pretty ? 2 : undefined);
|
|
376
|
+
output.write(str);
|
|
377
|
+
output.write('\n');
|
|
378
|
+
output.end();
|
|
379
|
+
// log success only when writting to file
|
|
380
|
+
if (filePath) {
|
|
381
|
+
console.log(`Successfully exported ${exportData.length} configuration entries`);
|
|
382
|
+
}
|
|
383
|
+
process.exit(0);
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* `$ strapi configuration:dump`
|
|
387
|
+
*/ const command$m = ()=>{
|
|
388
|
+
return commander.createCommand('configuration:dump').alias('config:dump').description('Dump configurations of your application').option('-f, --file <file>', 'Output file, default output is stdout').option('-p, --pretty', 'Format the output JSON with indentation and line breaks', false).action(runAction('configuration:dump', action$j));
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Will restore configurations. It reads from a file or stdin
|
|
393
|
+
*/ const action$i = async ({ file: filePath, strategy = 'replace' })=>{
|
|
394
|
+
const input = filePath ? fs.readFileSync(filePath) : await readStdin();
|
|
395
|
+
const appContext = await core.compileStrapi();
|
|
396
|
+
const app = await core.createStrapi(appContext).load();
|
|
397
|
+
let dataToImport;
|
|
398
|
+
try {
|
|
399
|
+
dataToImport = JSON.parse(_.toString(input));
|
|
400
|
+
} catch (error) {
|
|
401
|
+
if (error instanceof Error) {
|
|
402
|
+
throw new Error(`Invalid input data: ${error.message}. Expected a valid JSON array.`);
|
|
403
|
+
}
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
if (!Array.isArray(dataToImport)) {
|
|
407
|
+
throw new Error(`Invalid input data. Expected a valid JSON array.`);
|
|
408
|
+
}
|
|
409
|
+
if (!app.db) {
|
|
410
|
+
throw new Error('Cannot import configuration without a database connection.');
|
|
411
|
+
}
|
|
412
|
+
const importer = createImporter(app.db, strategy);
|
|
413
|
+
for (const config of dataToImport){
|
|
414
|
+
await importer.import(config);
|
|
415
|
+
}
|
|
416
|
+
console.log(`Successfully imported configuration with ${strategy} strategy. Statistics: ${importer.printStatistics()}.`);
|
|
417
|
+
process.exit(0);
|
|
418
|
+
};
|
|
419
|
+
const readStdin = ()=>{
|
|
420
|
+
const { stdin } = process;
|
|
421
|
+
let result = '';
|
|
422
|
+
if (stdin.isTTY) return Promise.resolve(result);
|
|
423
|
+
return new Promise((resolve, reject)=>{
|
|
424
|
+
stdin.setEncoding('utf8');
|
|
425
|
+
stdin.on('readable', ()=>{
|
|
426
|
+
let chunk;
|
|
427
|
+
// eslint-disable-next-line no-cond-assign
|
|
428
|
+
while(chunk = stdin.read()){
|
|
429
|
+
result += chunk;
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
stdin.on('end', ()=>{
|
|
433
|
+
resolve(result);
|
|
434
|
+
});
|
|
435
|
+
stdin.on('error', reject);
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
const createImporter = (db, strategy)=>{
|
|
439
|
+
switch(strategy){
|
|
440
|
+
case 'replace':
|
|
441
|
+
return createReplaceImporter(db);
|
|
442
|
+
case 'merge':
|
|
443
|
+
return createMergeImporter(db);
|
|
444
|
+
case 'keep':
|
|
445
|
+
return createKeepImporter(db);
|
|
446
|
+
default:
|
|
447
|
+
throw new Error(`No importer available for strategy "${strategy}"`);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Replace importer. Will replace the keys that already exist and create the new ones
|
|
452
|
+
*/ const createReplaceImporter = (db)=>{
|
|
453
|
+
const stats = {
|
|
454
|
+
created: 0,
|
|
455
|
+
replaced: 0
|
|
456
|
+
};
|
|
457
|
+
return {
|
|
458
|
+
printStatistics () {
|
|
459
|
+
return `${stats.created} created, ${stats.replaced} replaced`;
|
|
460
|
+
},
|
|
461
|
+
async import (conf) {
|
|
462
|
+
const matching = await db.query('strapi::core-store').count({
|
|
463
|
+
where: {
|
|
464
|
+
key: conf.key
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
if (matching > 0) {
|
|
468
|
+
stats.replaced += 1;
|
|
469
|
+
await db.query('strapi::core-store').update({
|
|
470
|
+
where: {
|
|
471
|
+
key: conf.key
|
|
472
|
+
},
|
|
473
|
+
data: conf
|
|
474
|
+
});
|
|
475
|
+
} else {
|
|
476
|
+
stats.created += 1;
|
|
477
|
+
await db.query('strapi::core-store').create({
|
|
478
|
+
data: conf
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
};
|
|
484
|
+
/**
|
|
485
|
+
* Merge importer. Will merge the keys that already exist with their new value and create the new ones
|
|
486
|
+
*/ const createMergeImporter = (db)=>{
|
|
487
|
+
const stats = {
|
|
488
|
+
created: 0,
|
|
489
|
+
merged: 0
|
|
490
|
+
};
|
|
491
|
+
return {
|
|
492
|
+
printStatistics () {
|
|
493
|
+
return `${stats.created} created, ${stats.merged} merged`;
|
|
494
|
+
},
|
|
495
|
+
async import (conf) {
|
|
496
|
+
const existingConf = await db.query('strapi::core-store').findOne({
|
|
497
|
+
where: {
|
|
498
|
+
key: conf.key
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
if (existingConf) {
|
|
502
|
+
stats.merged += 1;
|
|
503
|
+
await db.query('strapi::core-store').update({
|
|
504
|
+
where: {
|
|
505
|
+
key: conf.key
|
|
506
|
+
},
|
|
507
|
+
data: _.merge(existingConf, conf)
|
|
508
|
+
});
|
|
509
|
+
} else {
|
|
510
|
+
stats.created += 1;
|
|
511
|
+
await db.query('strapi::core-store').create({
|
|
512
|
+
data: conf
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* Merge importer. Will keep the keys that already exist without changing them and create the new ones
|
|
520
|
+
*/ const createKeepImporter = (db)=>{
|
|
521
|
+
const stats = {
|
|
522
|
+
created: 0,
|
|
523
|
+
untouched: 0
|
|
524
|
+
};
|
|
525
|
+
return {
|
|
526
|
+
printStatistics () {
|
|
527
|
+
return `${stats.created} created, ${stats.untouched} untouched`;
|
|
528
|
+
},
|
|
529
|
+
async import (conf) {
|
|
530
|
+
const matching = await db.query('strapi::core-store').count({
|
|
531
|
+
where: {
|
|
532
|
+
key: conf.key
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
if (matching > 0) {
|
|
536
|
+
stats.untouched += 1;
|
|
537
|
+
// if configuration already exists do not overwrite it
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
stats.created += 1;
|
|
541
|
+
await db.query('strapi::core-store').create({
|
|
542
|
+
data: conf
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* `$ strapi configuration:restore`
|
|
549
|
+
*/ const command$l = ()=>{
|
|
550
|
+
return commander.createCommand('configuration:restore').alias('config:restore').description('Restore configurations of your application').option('-f, --file <file>', 'Input file, default input is stdin').option('-s, --strategy <strategy>', 'Strategy name, one of: "replace", "merge", "keep"').action(runAction('configuration:restore', action$i));
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const action$h = async ()=>{
|
|
554
|
+
const appContext = await core.compileStrapi();
|
|
555
|
+
const app = await core.createStrapi(appContext).register();
|
|
556
|
+
const list = app.get('content-types').keys();
|
|
557
|
+
const infoTable = new CLITable({
|
|
558
|
+
head: [
|
|
559
|
+
chalk.blue('Name')
|
|
560
|
+
]
|
|
561
|
+
});
|
|
562
|
+
list.forEach((name)=>infoTable.push([
|
|
563
|
+
name
|
|
564
|
+
]));
|
|
565
|
+
console.log(infoTable.toString());
|
|
566
|
+
await app.destroy();
|
|
567
|
+
};
|
|
568
|
+
/**
|
|
569
|
+
* `$ strapi content-types:list`
|
|
570
|
+
*/ const command$k = ()=>{
|
|
571
|
+
return commander.createCommand('content-types:list').description('List all the application content-types').action(runAction('content-types:list', action$h));
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const action$g = async ()=>{
|
|
575
|
+
const appContext = await core.compileStrapi();
|
|
576
|
+
const app = await core.createStrapi(appContext).register();
|
|
577
|
+
const list = app.get('controllers').keys();
|
|
578
|
+
const infoTable = new CLITable({
|
|
579
|
+
head: [
|
|
580
|
+
chalk.blue('Name')
|
|
581
|
+
]
|
|
582
|
+
});
|
|
583
|
+
list.forEach((name)=>infoTable.push([
|
|
584
|
+
name
|
|
585
|
+
]));
|
|
586
|
+
console.log(infoTable.toString());
|
|
587
|
+
await app.destroy();
|
|
588
|
+
};
|
|
589
|
+
/**
|
|
590
|
+
* `$ strapi controllers:list`
|
|
591
|
+
*/ const command$j = ()=>{
|
|
592
|
+
return commander.createCommand('controllers:list').description('List all the application controllers').action(runAction('controllers:list', action$g));
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
const action$f = async ()=>{
|
|
596
|
+
const appContext = await core.compileStrapi();
|
|
597
|
+
const app = await core.createStrapi(appContext).register();
|
|
598
|
+
const list = app.get('hooks').keys();
|
|
599
|
+
const infoTable = new CLITable({
|
|
600
|
+
head: [
|
|
601
|
+
chalk.blue('Name')
|
|
602
|
+
]
|
|
603
|
+
});
|
|
604
|
+
list.forEach((name)=>infoTable.push([
|
|
605
|
+
name
|
|
606
|
+
]));
|
|
607
|
+
console.log(infoTable.toString());
|
|
608
|
+
await app.destroy();
|
|
609
|
+
};
|
|
610
|
+
/**
|
|
611
|
+
* `$ strapi hooks:list`
|
|
612
|
+
*/ const command$i = ()=>{
|
|
613
|
+
return commander.createCommand('hooks:list').description('List all the application hooks').action(runAction('hooks:list', action$f));
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const action$e = async ()=>{
|
|
617
|
+
const appContext = await core.compileStrapi();
|
|
618
|
+
const app = await core.createStrapi(appContext).register();
|
|
619
|
+
const list = app.get('middlewares').keys();
|
|
620
|
+
const infoTable = new CLITable({
|
|
621
|
+
head: [
|
|
622
|
+
chalk.blue('Name')
|
|
623
|
+
]
|
|
624
|
+
});
|
|
625
|
+
list.forEach((name)=>infoTable.push([
|
|
626
|
+
name
|
|
627
|
+
]));
|
|
628
|
+
console.log(infoTable.toString());
|
|
629
|
+
await app.destroy();
|
|
630
|
+
};
|
|
631
|
+
/**
|
|
632
|
+
* `$ strapi middlewares:list`
|
|
633
|
+
*/ const command$h = ()=>{
|
|
634
|
+
return commander.createCommand('middlewares:list').description('List all the application middlewares').action(runAction('middlewares:list', action$e));
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const action$d = async ()=>{
|
|
638
|
+
const appContext = await core.compileStrapi();
|
|
639
|
+
const app = await core.createStrapi(appContext).register();
|
|
640
|
+
const list = app.get('policies').keys();
|
|
641
|
+
const infoTable = new CLITable({
|
|
642
|
+
head: [
|
|
643
|
+
chalk.blue('Name')
|
|
644
|
+
]
|
|
645
|
+
});
|
|
646
|
+
list.forEach((name)=>infoTable.push([
|
|
647
|
+
name
|
|
648
|
+
]));
|
|
649
|
+
console.log(infoTable.toString());
|
|
650
|
+
await app.destroy();
|
|
651
|
+
};
|
|
652
|
+
/**
|
|
653
|
+
* `$ strapi policies:list`
|
|
654
|
+
*/ const command$g = ()=>{
|
|
655
|
+
return commander.createCommand('policies:list').description('List all the application policies').action(runAction('policies:list', action$d));
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const action$c = async ()=>{
|
|
659
|
+
const appContext = await core.compileStrapi();
|
|
660
|
+
const app = await core.createStrapi(appContext).load();
|
|
661
|
+
const list = app.server.mount().listRoutes();
|
|
662
|
+
const infoTable = new CLITable({
|
|
663
|
+
head: [
|
|
664
|
+
chalk.blue('Method'),
|
|
665
|
+
chalk.blue('Path')
|
|
666
|
+
],
|
|
667
|
+
colWidths: [
|
|
668
|
+
20
|
|
669
|
+
]
|
|
670
|
+
});
|
|
671
|
+
list.filter((route)=>route.methods.length).forEach((route)=>{
|
|
672
|
+
infoTable.push([
|
|
673
|
+
route.methods.map(fp.toUpper).join('|'),
|
|
674
|
+
route.path
|
|
675
|
+
]);
|
|
676
|
+
});
|
|
677
|
+
console.log(infoTable.toString());
|
|
678
|
+
await app.destroy();
|
|
679
|
+
};
|
|
680
|
+
/**
|
|
681
|
+
* `$ strapi routes:list``
|
|
682
|
+
*/ const command$f = ()=>{
|
|
683
|
+
return commander.createCommand('routes:list').description('List all the application routes').action(runAction('routes:list', action$c));
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const action$b = async ()=>{
|
|
687
|
+
const appContext = await core.compileStrapi();
|
|
688
|
+
const app = await core.createStrapi(appContext).register();
|
|
689
|
+
const list = app.get('services').keys();
|
|
690
|
+
const infoTable = new CLITable({
|
|
691
|
+
head: [
|
|
692
|
+
chalk.blue('Name')
|
|
693
|
+
]
|
|
694
|
+
});
|
|
695
|
+
list.forEach((name)=>infoTable.push([
|
|
696
|
+
name
|
|
697
|
+
]));
|
|
698
|
+
console.log(infoTable.toString());
|
|
699
|
+
await app.destroy();
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* `$ strapi services:list`
|
|
703
|
+
*/ const command$e = ()=>{
|
|
704
|
+
return commander.createCommand('services:list').description('List all the application services').action(runAction('services:list', action$b));
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
const sendEvent = async (event, uuid)=>{
|
|
708
|
+
try {
|
|
709
|
+
await fetch('https://analytics.strapi.io/api/v2/track', {
|
|
710
|
+
method: 'POST',
|
|
711
|
+
body: JSON.stringify({
|
|
712
|
+
event,
|
|
713
|
+
deviceId: utils.machineID(),
|
|
714
|
+
groupProperties: {
|
|
715
|
+
projectId: uuid
|
|
716
|
+
}
|
|
717
|
+
}),
|
|
718
|
+
headers: {
|
|
719
|
+
'Content-Type': 'application/json',
|
|
720
|
+
'X-Strapi-Event': event
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
} catch (e) {
|
|
724
|
+
// ...
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const readPackageJSON$1 = async (path)=>{
|
|
729
|
+
try {
|
|
730
|
+
const packageObj = await fse.readJson(path);
|
|
731
|
+
const uuid = packageObj.strapi ? packageObj.strapi.uuid : null;
|
|
732
|
+
return {
|
|
733
|
+
uuid,
|
|
734
|
+
packageObj
|
|
735
|
+
};
|
|
736
|
+
} catch (err) {
|
|
737
|
+
if (err instanceof Error) {
|
|
738
|
+
console.error(`${chalk.red('Error')}: ${err.message}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
const writePackageJSON$1 = async (path, file, spacing)=>{
|
|
743
|
+
try {
|
|
744
|
+
await fse.writeJson(path, file, {
|
|
745
|
+
spaces: spacing
|
|
746
|
+
});
|
|
747
|
+
return true;
|
|
748
|
+
} catch (err) {
|
|
749
|
+
if (err instanceof Error) {
|
|
750
|
+
console.error(`${chalk.red('Error')}: ${err.message}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
const action$a = async ()=>{
|
|
755
|
+
const packageJSONPath = path.resolve(process.cwd(), 'package.json');
|
|
756
|
+
const exists = await fse.pathExists(packageJSONPath);
|
|
757
|
+
if (!exists) {
|
|
758
|
+
console.log(`${chalk.yellow('Warning')}: could not find package.json`);
|
|
759
|
+
process.exit(0);
|
|
760
|
+
}
|
|
761
|
+
const { uuid, packageObj } = await readPackageJSON$1(packageJSONPath) ?? {};
|
|
762
|
+
if (packageObj.strapi && packageObj.strapi.telemetryDisabled || !uuid) {
|
|
763
|
+
console.log(`${chalk.yellow('Warning:')} telemetry is already disabled`);
|
|
764
|
+
process.exit(0);
|
|
765
|
+
}
|
|
766
|
+
const updatedPackageJSON = {
|
|
767
|
+
...packageObj,
|
|
768
|
+
strapi: {
|
|
769
|
+
...packageObj.strapi,
|
|
770
|
+
telemetryDisabled: true
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
const write = await writePackageJSON$1(packageJSONPath, updatedPackageJSON, 2);
|
|
774
|
+
if (!write) {
|
|
775
|
+
console.log(`${chalk.yellow('Warning')}: There has been an error, please set "telemetryDisabled": true in the "strapi" object of your package.json manually.`);
|
|
776
|
+
process.exit(0);
|
|
777
|
+
}
|
|
778
|
+
await sendEvent('didOptOutTelemetry', uuid);
|
|
779
|
+
console.log(`${chalk.green('Successfully opted out of Strapi telemetry')}`);
|
|
780
|
+
process.exit(0);
|
|
781
|
+
};
|
|
782
|
+
/**
|
|
783
|
+
* `$ strapi telemetry:disable`
|
|
784
|
+
*/ const command$d = ()=>{
|
|
785
|
+
return commander.createCommand('telemetry:disable').description('Disable anonymous telemetry and metadata sending to Strapi analytics').action(runAction('telemetry:disable', action$a));
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
const readPackageJSON = async (path)=>{
|
|
789
|
+
try {
|
|
790
|
+
const packageObj = await fse.readJson(path);
|
|
791
|
+
return packageObj;
|
|
792
|
+
} catch (err) {
|
|
793
|
+
if (err instanceof Error) {
|
|
794
|
+
console.error(`${chalk.red('Error')}: ${err.message}`);
|
|
795
|
+
} else {
|
|
796
|
+
throw err;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
const writePackageJSON = async (path, file, spacing)=>{
|
|
801
|
+
try {
|
|
802
|
+
await fse.writeJson(path, file, {
|
|
803
|
+
spaces: spacing
|
|
804
|
+
});
|
|
805
|
+
return true;
|
|
806
|
+
} catch (err) {
|
|
807
|
+
if (err instanceof Error) {
|
|
808
|
+
console.error(`${chalk.red('Error')}: ${err.message}`);
|
|
809
|
+
console.log(`${chalk.yellow('Warning')}: There has been an error, please set "telemetryDisabled": false in the "strapi" object of your package.json manually.`);
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
throw err;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
const generateNewPackageJSON = (packageObj)=>{
|
|
816
|
+
if (!packageObj.strapi) {
|
|
817
|
+
return {
|
|
818
|
+
...packageObj,
|
|
819
|
+
strapi: {
|
|
820
|
+
uuid: crypto.randomUUID(),
|
|
821
|
+
telemetryDisabled: false
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
...packageObj,
|
|
827
|
+
strapi: {
|
|
828
|
+
...packageObj.strapi,
|
|
829
|
+
uuid: packageObj.strapi.uuid ? packageObj.strapi.uuid : crypto.randomUUID(),
|
|
830
|
+
telemetryDisabled: false
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
};
|
|
834
|
+
const action$9 = async ()=>{
|
|
835
|
+
const packageJSONPath = path.resolve(process.cwd(), 'package.json');
|
|
836
|
+
const exists = await fse.pathExists(packageJSONPath);
|
|
837
|
+
if (!exists) {
|
|
838
|
+
console.log(`${chalk.yellow('Warning')}: could not find package.json`);
|
|
839
|
+
process.exit(0);
|
|
840
|
+
}
|
|
841
|
+
const packageObj = await readPackageJSON(packageJSONPath);
|
|
842
|
+
if (packageObj.strapi && packageObj.strapi.uuid) {
|
|
843
|
+
if (packageObj.strapi.telemetryDisabled === false) {
|
|
844
|
+
console.log(`${chalk.yellow('Warning:')} telemetry is already enabled`);
|
|
845
|
+
process.exit(0);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
const updatedPackageJSON = generateNewPackageJSON(packageObj);
|
|
849
|
+
const write = await writePackageJSON(packageJSONPath, updatedPackageJSON, 2);
|
|
850
|
+
if (!write) {
|
|
851
|
+
process.exit(0);
|
|
852
|
+
}
|
|
853
|
+
await sendEvent('didOptInTelemetry', updatedPackageJSON.strapi.uuid);
|
|
854
|
+
console.log(`${chalk.green('Successfully opted into and enabled Strapi telemetry')}`);
|
|
855
|
+
process.exit(0);
|
|
856
|
+
};
|
|
857
|
+
/**
|
|
858
|
+
* `$ strapi telemetry:enable`
|
|
859
|
+
*/ const command$c = ()=>{
|
|
860
|
+
return commander.createCommand('telemetry:enable').description('Enable anonymous telemetry and metadata sending to Strapi analytics').action(runAction('telemetry:enable', action$9));
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
*`$ strapi templates:generate <directory>`
|
|
865
|
+
*/ const command$b = ()=>{
|
|
866
|
+
return commander.createCommand('templates:generate <directory>').description('(deprecated) Generate template from Strapi project').action(()=>{
|
|
867
|
+
console.warn('This command is deprecated and will be removed in the next major release.');
|
|
868
|
+
console.warn('You can now copy an existing app and use it as a template.');
|
|
869
|
+
});
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
const action$8 = async ({ debug, silent, verbose, outDir })=>{
|
|
873
|
+
if ((debug || verbose) && silent) {
|
|
874
|
+
console.error('Flags conflict: both silent and debug mode are enabled, exiting...');
|
|
875
|
+
process.exit(1);
|
|
876
|
+
}
|
|
877
|
+
const appContext = await core.compileStrapi({
|
|
878
|
+
ignoreDiagnostics: true
|
|
879
|
+
});
|
|
880
|
+
const app = await core.createStrapi(appContext).register();
|
|
881
|
+
await tsUtils.generators.generate({
|
|
882
|
+
strapi: app,
|
|
883
|
+
pwd: appContext.appDir,
|
|
884
|
+
rootDir: outDir ?? undefined,
|
|
885
|
+
logger: {
|
|
886
|
+
silent,
|
|
887
|
+
debug
|
|
888
|
+
},
|
|
889
|
+
artifacts: {
|
|
890
|
+
contentTypes: true,
|
|
891
|
+
components: true
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
await app.destroy();
|
|
895
|
+
};
|
|
896
|
+
/**
|
|
897
|
+
* `$ strapi ts:generate-types`
|
|
898
|
+
*/ const command$a = ()=>{
|
|
899
|
+
return commander.createCommand('ts:generate-types').description(`Generate TypeScript typings for your schemas`).option('-d, --debug', `Run the generation with debug messages`, false).option('-s, --silent', `Run the generation silently, without any output`, false).option('-o, --out-dir <outDir>', 'Specify a relative root directory in which the definitions will be generated. Changing this value might break types exposed by Strapi that relies on generated types.').action(runAction('ts:generate-types', action$8));
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* @description Supports the following managers:
|
|
904
|
+
* – npm
|
|
905
|
+
* – yarn
|
|
906
|
+
* – pnpm
|
|
907
|
+
*/ const getPackageManager = ()=>{
|
|
908
|
+
// Yes, the env var is lowercase - it is set by the package managers themselves
|
|
909
|
+
const agent = process.env.npm_config_user_agent || '';
|
|
910
|
+
if (agent.includes('yarn')) {
|
|
911
|
+
return 'yarn';
|
|
912
|
+
}
|
|
913
|
+
if (agent.includes('pnpm')) {
|
|
914
|
+
return 'pnpm';
|
|
915
|
+
}
|
|
916
|
+
// Both yarn and pnpm does a `npm/?` thing, thus the slightly different match here
|
|
917
|
+
// Theoretically not needed since we check for yarn/pnpm above, but in case other
|
|
918
|
+
// package managers do the same thing, we'll (hopefully) catch them here.
|
|
919
|
+
if (/^npm\/\d/.test(agent)) {
|
|
920
|
+
return 'npm';
|
|
921
|
+
}
|
|
922
|
+
return undefined;
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* From V5 this will be imported from the package.json of `@strapi/strapi`.
|
|
927
|
+
*/ const PEER_DEPS = {
|
|
928
|
+
react: '^18.0.0',
|
|
929
|
+
'react-dom': '^18.0.0',
|
|
930
|
+
'react-router-dom': '^6.0.0',
|
|
931
|
+
'styled-components': '^6.0.0'
|
|
932
|
+
};
|
|
933
|
+
/**
|
|
934
|
+
* Checks the user's project that it has declared and installed the required dependencies
|
|
935
|
+
* needed by the Strapi admin project. Whilst generally speaking most modules will be
|
|
936
|
+
* declared by the actual packages there are some packages where you only really want one of
|
|
937
|
+
* and thus they are declared as peer dependencies – react / styled-components / etc.
|
|
938
|
+
*
|
|
939
|
+
* If these deps are not installed or declared, then we prompt the user to correct this. In
|
|
940
|
+
* V4 this is not a hard requirement, but in V5 it will be. Might as well get people started now.
|
|
941
|
+
*/ const checkRequiredDependencies = async ({ cwd, logger })=>{
|
|
942
|
+
/**
|
|
943
|
+
* This enables us to use experimental deps for libraries like
|
|
944
|
+
* react or styled-components. This is useful for testing against.
|
|
945
|
+
*/ if (process.env.USE_EXPERIMENTAL_DEPENDENCIES === 'true') {
|
|
946
|
+
logger.warn('You are using experimental dependencies that may not be compatible with Strapi.');
|
|
947
|
+
return {
|
|
948
|
+
didInstall: false
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
const pkg = await readPkgUp({
|
|
952
|
+
cwd
|
|
953
|
+
});
|
|
954
|
+
if (!pkg) {
|
|
955
|
+
throw new Error(`Could not find package.json at path: ${cwd}`);
|
|
956
|
+
}
|
|
957
|
+
logger.debug('Loaded package.json:', os.EOL, pkg.packageJson);
|
|
958
|
+
/**
|
|
959
|
+
* Run through each of the peer deps and figure out if they need to be
|
|
960
|
+
* installed or they need their version checked against.
|
|
961
|
+
*/ const { install, review } = Object.entries(PEER_DEPS).reduce((acc, [name, version])=>{
|
|
962
|
+
if (!pkg.packageJson.dependencies) {
|
|
963
|
+
throw new Error(`Could not find dependencies in package.json at path: ${cwd}`);
|
|
964
|
+
}
|
|
965
|
+
const declaredVersion = pkg.packageJson.dependencies[name];
|
|
966
|
+
if (!declaredVersion) {
|
|
967
|
+
acc.install.push({
|
|
968
|
+
name,
|
|
969
|
+
wantedVersion: version
|
|
970
|
+
});
|
|
971
|
+
} else {
|
|
972
|
+
acc.review.push({
|
|
973
|
+
name,
|
|
974
|
+
wantedVersion: version,
|
|
975
|
+
declaredVersion
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
return acc;
|
|
979
|
+
}, {
|
|
980
|
+
install: [],
|
|
981
|
+
review: []
|
|
982
|
+
});
|
|
983
|
+
if (install.length > 0) {
|
|
984
|
+
logger.info('The Strapi admin needs to install the following dependencies:', os.EOL, install.map(({ name, wantedVersion })=>` - ${name}@${wantedVersion}`).join(os.EOL));
|
|
985
|
+
await installDependencies(install, {
|
|
986
|
+
cwd,
|
|
987
|
+
logger
|
|
988
|
+
});
|
|
989
|
+
const [file, ...args] = process.argv;
|
|
990
|
+
/**
|
|
991
|
+
* Re-run the same command after installation e.g. strapi build because the yarn.lock might
|
|
992
|
+
* not be the same and could break installations. It's not the best solution, but it works.
|
|
993
|
+
*/ await execa(file, args, {
|
|
994
|
+
cwd,
|
|
995
|
+
stdio: 'inherit'
|
|
996
|
+
});
|
|
997
|
+
return {
|
|
998
|
+
didInstall: true
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
if (review.length) {
|
|
1002
|
+
const errors = [];
|
|
1003
|
+
for (const dep of review){
|
|
1004
|
+
// The version specified in package.json could be incorrect, eg `foo`
|
|
1005
|
+
let minDeclaredVersion = null;
|
|
1006
|
+
try {
|
|
1007
|
+
minDeclaredVersion = semver.minVersion(dep.declaredVersion);
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
// Intentional fall-through (variable will be left as null, throwing below)
|
|
1010
|
+
}
|
|
1011
|
+
if (!minDeclaredVersion) {
|
|
1012
|
+
errors.push(`The declared dependency, ${dep.name} has an invalid version in package.json: ${dep.declaredVersion}`);
|
|
1013
|
+
} else if (!semver.satisfies(minDeclaredVersion, dep.wantedVersion)) {
|
|
1014
|
+
/**
|
|
1015
|
+
* The delcared version should be semver compatible with our required version
|
|
1016
|
+
* of the dependency. If it's not, we should advise the user to change it.
|
|
1017
|
+
*/ logger.warn([
|
|
1018
|
+
`Declared version of ${dep.name} (${minDeclaredVersion}) is not compatible with the version required by Strapi (${dep.wantedVersion}).`,
|
|
1019
|
+
'You may experience issues, we recommend you change this.'
|
|
1020
|
+
].join(os.EOL));
|
|
1021
|
+
}
|
|
1022
|
+
const installedVersion = await getModuleVersion(dep.name, cwd);
|
|
1023
|
+
if (!installedVersion) {
|
|
1024
|
+
/**
|
|
1025
|
+
* TODO: when we know the packageManager we can advise the actual install command.
|
|
1026
|
+
*/ errors.push(`The declared dependency, ${dep.name} is not installed. You should install before re-running this command`);
|
|
1027
|
+
} else if (!semver.satisfies(installedVersion, dep.wantedVersion)) {
|
|
1028
|
+
logger.warn([
|
|
1029
|
+
`Declared version of ${dep.name} (${installedVersion}) is not compatible with the version required by Strapi (${dep.wantedVersion}).`,
|
|
1030
|
+
'You may experience issues, we recommend you change this.'
|
|
1031
|
+
].join(os.EOL));
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (errors.length > 0 && process.env.NODE_ENV === 'development') {
|
|
1035
|
+
throw new Error(`${os.EOL}- ${errors.join(`${os.EOL}- `)}`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
didInstall: false
|
|
1040
|
+
};
|
|
1041
|
+
};
|
|
1042
|
+
const getModule = async (name, cwd)=>{
|
|
1043
|
+
const modulePackagePath = resolveFrom.silent(cwd, path$1.join(name, 'package.json'));
|
|
1044
|
+
if (!modulePackagePath) {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
const file = await fs$1.readFile(modulePackagePath, 'utf8').then((res)=>JSON.parse(res));
|
|
1048
|
+
return file;
|
|
1049
|
+
};
|
|
1050
|
+
const getModuleVersion = async (name, cwd)=>{
|
|
1051
|
+
const pkg = await getModule(name, cwd);
|
|
1052
|
+
return pkg?.version || null;
|
|
1053
|
+
};
|
|
1054
|
+
const installDependencies = async (install, { cwd, logger })=>{
|
|
1055
|
+
const packageManager = getPackageManager();
|
|
1056
|
+
if (!packageManager) {
|
|
1057
|
+
logger.error('Could not find a supported package manager, please install the dependencies manually.');
|
|
1058
|
+
process.exit(1);
|
|
1059
|
+
}
|
|
1060
|
+
const execOptions = {
|
|
1061
|
+
encoding: 'utf8',
|
|
1062
|
+
cwd,
|
|
1063
|
+
stdio: 'inherit'
|
|
1064
|
+
};
|
|
1065
|
+
const packages = install.map(({ name, wantedVersion })=>`${name}@${wantedVersion}`);
|
|
1066
|
+
let result;
|
|
1067
|
+
if (packageManager === 'npm') {
|
|
1068
|
+
const npmArgs = [
|
|
1069
|
+
'install',
|
|
1070
|
+
'--legacy-peer-deps',
|
|
1071
|
+
'--save',
|
|
1072
|
+
...packages
|
|
1073
|
+
];
|
|
1074
|
+
logger.info(`Running 'npm ${npmArgs.join(' ')}'`);
|
|
1075
|
+
result = await execa('npm', npmArgs, execOptions);
|
|
1076
|
+
} else if (packageManager === 'yarn') {
|
|
1077
|
+
const yarnArgs = [
|
|
1078
|
+
'add',
|
|
1079
|
+
...packages
|
|
1080
|
+
];
|
|
1081
|
+
logger.info(`Running 'yarn ${yarnArgs.join(' ')}'`);
|
|
1082
|
+
result = await execa('yarn', yarnArgs, execOptions);
|
|
1083
|
+
} else if (packageManager === 'pnpm') {
|
|
1084
|
+
const pnpmArgs = [
|
|
1085
|
+
'add',
|
|
1086
|
+
'--save-prod',
|
|
1087
|
+
...packages
|
|
1088
|
+
];
|
|
1089
|
+
logger.info(`Running 'pnpm ${pnpmArgs.join(' ')}'`);
|
|
1090
|
+
result = await execa('pnpm', pnpmArgs, execOptions);
|
|
1091
|
+
}
|
|
1092
|
+
if (result?.exitCode || result?.failed) {
|
|
1093
|
+
throw new Error('Package installation failed');
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
function getTimer() {
|
|
1098
|
+
const timings = {};
|
|
1099
|
+
const startTimes = {};
|
|
1100
|
+
function start(name) {
|
|
1101
|
+
if (typeof startTimes[name] !== 'undefined') {
|
|
1102
|
+
throw new Error(`Timer "${name}" already started, cannot overwrite`);
|
|
1103
|
+
}
|
|
1104
|
+
startTimes[name] = perf_hooks.performance.now();
|
|
1105
|
+
}
|
|
1106
|
+
function end(name) {
|
|
1107
|
+
if (typeof startTimes[name] === 'undefined') {
|
|
1108
|
+
throw new Error(`Timer "${name}" never started, cannot end`);
|
|
1109
|
+
}
|
|
1110
|
+
timings[name] = perf_hooks.performance.now() - startTimes[name];
|
|
1111
|
+
return timings[name];
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
start,
|
|
1115
|
+
end,
|
|
1116
|
+
getTimings: ()=>timings
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
const prettyTime = (timeInMs)=>{
|
|
1120
|
+
return `${Math.ceil(timeInMs)}ms`;
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* @internal
|
|
1125
|
+
*/ const pathExists = async (path)=>{
|
|
1126
|
+
try {
|
|
1127
|
+
await fs$1.access(path);
|
|
1128
|
+
return true;
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
/**
|
|
1134
|
+
* @internal
|
|
1135
|
+
*/ const loadFile = async (path)=>{
|
|
1136
|
+
if (await pathExists(path)) {
|
|
1137
|
+
const esbuildOptions = {
|
|
1138
|
+
extensions: [
|
|
1139
|
+
'.js',
|
|
1140
|
+
'.mjs',
|
|
1141
|
+
'.ts'
|
|
1142
|
+
]
|
|
1143
|
+
};
|
|
1144
|
+
const { unregister } = node.register(esbuildOptions);
|
|
1145
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1146
|
+
const mod = require(path);
|
|
1147
|
+
unregister();
|
|
1148
|
+
/**
|
|
1149
|
+
* handles esm or cjs exporting.
|
|
1150
|
+
*/ const file = mod?.default || mod || undefined;
|
|
1151
|
+
return file;
|
|
1152
|
+
}
|
|
1153
|
+
return undefined;
|
|
1154
|
+
};
|
|
1155
|
+
/**
|
|
1156
|
+
* @internal
|
|
1157
|
+
*
|
|
1158
|
+
* @description Converts a system path to a module path mainly for `Windows` systems.
|
|
1159
|
+
* where the path separator is `\` instead of `/`, on linux systems the path separator
|
|
1160
|
+
* is identical to the module path separator.
|
|
1161
|
+
*/ const convertSystemPathToModulePath = (sysPath)=>{
|
|
1162
|
+
if (process.platform === 'win32') {
|
|
1163
|
+
return sysPath.split(path$1.sep).join(path$1.posix.sep);
|
|
1164
|
+
}
|
|
1165
|
+
return sysPath;
|
|
1166
|
+
};
|
|
1167
|
+
/**
|
|
1168
|
+
* @internal
|
|
1169
|
+
*
|
|
1170
|
+
* @description Converts a module path to a system path, again largely used for Windows systems.
|
|
1171
|
+
* The original use case was plugins where the resolve path was in module format but we want to
|
|
1172
|
+
* have it relative to the runtime directory.
|
|
1173
|
+
*/ const convertModulePathToSystemPath = (modulePath)=>{
|
|
1174
|
+
if (process.platform === 'win32') {
|
|
1175
|
+
return modulePath.split(path$1.posix.sep).join(path$1.sep);
|
|
1176
|
+
}
|
|
1177
|
+
return modulePath;
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* @internal
|
|
1182
|
+
*
|
|
1183
|
+
* @description Load the .env file if it exists
|
|
1184
|
+
*/ const loadEnv = async (cwd)=>{
|
|
1185
|
+
const pathToEnv = path$1.resolve(cwd, '.env');
|
|
1186
|
+
if (await pathExists(pathToEnv)) {
|
|
1187
|
+
dotenv.config({
|
|
1188
|
+
path: pathToEnv
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
/**
|
|
1193
|
+
* @internal
|
|
1194
|
+
*
|
|
1195
|
+
* @description Get all the environment variables that start with `STRAPI_ADMIN_`
|
|
1196
|
+
*/ const getStrapiAdminEnvVars = (defaultEnv)=>{
|
|
1197
|
+
return Object.keys(process.env).filter((key)=>key.toUpperCase().startsWith('STRAPI_ADMIN_')).reduce((acc, key)=>{
|
|
1198
|
+
acc[key] = process.env[key];
|
|
1199
|
+
return acc;
|
|
1200
|
+
}, defaultEnv);
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
const isError = (err)=>err instanceof Error;
|
|
1204
|
+
/**
|
|
1205
|
+
* @description Handle unexpected errors. No, but really, your CLI should anticipate error cases.
|
|
1206
|
+
* If a user hits an error we don't expect, then we need to flag to them that this is not normal
|
|
1207
|
+
* and they should use the `--debug` flag to get more information (assuming you've implemented this
|
|
1208
|
+
* in your action).
|
|
1209
|
+
*/ const handleUnexpectedError = (err)=>{
|
|
1210
|
+
console.error(chalk.red(`[ERROR] `, 'There seems to be an unexpected error, try again with --debug for more information', os.EOL));
|
|
1211
|
+
if (isError(err) && err.stack) {
|
|
1212
|
+
// eslint-disable-next-line no-console
|
|
1213
|
+
console.log(chalk.red(boxen(err.stack, {
|
|
1214
|
+
padding: 1,
|
|
1215
|
+
align: 'left'
|
|
1216
|
+
})));
|
|
1217
|
+
}
|
|
1218
|
+
if (err instanceof utils.errors.YupValidationError) {
|
|
1219
|
+
const message = [];
|
|
1220
|
+
const size = err.details.errors.length;
|
|
1221
|
+
for (const error of err.details.errors){
|
|
1222
|
+
// No need to repeat the error message as it's the same as the err.message
|
|
1223
|
+
if (size === 1) {
|
|
1224
|
+
message.push(` value: ${error.value}`);
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
message.push([
|
|
1228
|
+
` [${error.name}]`,
|
|
1229
|
+
` message: ${error.message}`,
|
|
1230
|
+
` value: ${error.value}`
|
|
1231
|
+
].join('\n'));
|
|
1232
|
+
}
|
|
1233
|
+
console.log(chalk.red(boxen([
|
|
1234
|
+
'Details:',
|
|
1235
|
+
message.join('\n\n')
|
|
1236
|
+
].join('\n'), {
|
|
1237
|
+
padding: 1,
|
|
1238
|
+
align: 'left'
|
|
1239
|
+
})));
|
|
1240
|
+
}
|
|
1241
|
+
process.exit(1);
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
const validatePackageHasStrapi = (pkg)=>'strapi' in pkg && typeof pkg.strapi === 'object' && !Array.isArray(pkg.strapi) && pkg.strapi !== null;
|
|
1245
|
+
const validatePackageIsPlugin = (pkg)=>validatePackageHasStrapi(pkg) && pkg.strapi.kind === 'plugin';
|
|
1246
|
+
const getEnabledPlugins = async ({ cwd, logger, runtimeDir, strapi })=>{
|
|
1247
|
+
const plugins = {};
|
|
1248
|
+
/**
|
|
1249
|
+
* This is the list of dependencies that are installed in the user's project.
|
|
1250
|
+
* It will include libraries like "react", so we need to collect the ones that
|
|
1251
|
+
* are plugins.
|
|
1252
|
+
*/ const deps = strapi.config.get('info.dependencies', {});
|
|
1253
|
+
logger.debug("Dependencies from user's project", os.EOL, deps);
|
|
1254
|
+
for (const dep of Object.keys(deps)){
|
|
1255
|
+
const pkg = await getModule(dep, cwd);
|
|
1256
|
+
if (pkg && validatePackageIsPlugin(pkg)) {
|
|
1257
|
+
const name = pkg.strapi.name || pkg.name;
|
|
1258
|
+
if (!name) {
|
|
1259
|
+
/**
|
|
1260
|
+
* Unlikely to happen, but you never know.
|
|
1261
|
+
*/ throw Error("You're trying to import a plugin that doesn't have a name – check the package.json of that plugin!");
|
|
1262
|
+
}
|
|
1263
|
+
plugins[name] = {
|
|
1264
|
+
name,
|
|
1265
|
+
importName: camelCase(name),
|
|
1266
|
+
type: 'module',
|
|
1267
|
+
modulePath: dep
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
const userPluginsFile = await loadUserPluginsFile(strapi.dirs.app.config);
|
|
1272
|
+
logger.debug("User's plugins file", os.EOL, userPluginsFile);
|
|
1273
|
+
for (const [userPluginName, userPluginConfig] of Object.entries(userPluginsFile)){
|
|
1274
|
+
if (userPluginConfig.enabled && userPluginConfig.resolve) {
|
|
1275
|
+
const sysPath = convertModulePathToSystemPath(userPluginConfig.resolve);
|
|
1276
|
+
plugins[userPluginName] = {
|
|
1277
|
+
name: userPluginName,
|
|
1278
|
+
importName: camelCase(userPluginName),
|
|
1279
|
+
type: 'local',
|
|
1280
|
+
/**
|
|
1281
|
+
* User plugin paths are resolved from the entry point
|
|
1282
|
+
* of the app, because that's how you import them.
|
|
1283
|
+
*/ modulePath: convertSystemPathToModulePath(path$1.relative(runtimeDir, sysPath)),
|
|
1284
|
+
path: sysPath
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
return plugins;
|
|
1289
|
+
};
|
|
1290
|
+
const PLUGIN_CONFIGS = [
|
|
1291
|
+
'plugins.js',
|
|
1292
|
+
'plugins.mjs',
|
|
1293
|
+
'plugins.ts'
|
|
1294
|
+
];
|
|
1295
|
+
const loadUserPluginsFile = async (root)=>{
|
|
1296
|
+
for (const file of PLUGIN_CONFIGS){
|
|
1297
|
+
const filePath = path$1.join(root, file);
|
|
1298
|
+
const configFile = await loadFile(filePath);
|
|
1299
|
+
if (configFile) {
|
|
1300
|
+
/**
|
|
1301
|
+
* Configs can be a function or they can be just an object!
|
|
1302
|
+
*/ return typeof configFile === 'function' ? configFile({
|
|
1303
|
+
env: utils.env
|
|
1304
|
+
}) : configFile;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
return {};
|
|
1308
|
+
};
|
|
1309
|
+
const getMapOfPluginsWithAdmin = (plugins)=>{
|
|
1310
|
+
/**
|
|
1311
|
+
* This variable stores the import paths for plugins.
|
|
1312
|
+
* The keys are the module paths of the plugins, and the values are the paths
|
|
1313
|
+
* to the admin part of the plugins, which is either loaded from the
|
|
1314
|
+
* package.json exports or from the legacy strapi-admin.js file.
|
|
1315
|
+
*/ const pluginImportPaths = {};
|
|
1316
|
+
return Object.values(plugins).filter((plugin)=>{
|
|
1317
|
+
if (!plugin) {
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* There are two ways a plugin should be imported, either it's local to the strapi app,
|
|
1322
|
+
* or it's an actual npm module that's installed and resolved via node_modules.
|
|
1323
|
+
*
|
|
1324
|
+
* We first check if the plugin is local to the strapi app, using a regular `fs.existsSync` because
|
|
1325
|
+
* the pathToPlugin will be relative i.e. `/Users/my-name/strapi-app/src/plugins/my-plugin`.
|
|
1326
|
+
*
|
|
1327
|
+
* If the file doesn't exist well then it's probably a node_module, so instead we use `require.resolve`
|
|
1328
|
+
* which will resolve the path to the module in node_modules. If it fails with the specific code `MODULE_NOT_FOUND`
|
|
1329
|
+
* then it doesn't have an admin part to the package.
|
|
1330
|
+
*/ try {
|
|
1331
|
+
const localPluginPath = plugin.path;
|
|
1332
|
+
if (localPluginPath) {
|
|
1333
|
+
// Here we are loading a locally installed plugin
|
|
1334
|
+
const packageJsonPath = path$1.join(localPluginPath, 'package.json');
|
|
1335
|
+
if (fs$2.existsSync(packageJsonPath)) {
|
|
1336
|
+
const packageJson = JSON.parse(fs$2.readFileSync(packageJsonPath, 'utf-8'));
|
|
1337
|
+
const localAdminPath = packageJson?.exports?.['./strapi-admin']?.import;
|
|
1338
|
+
if (localAdminPath) {
|
|
1339
|
+
pluginImportPaths[plugin.modulePath] = localAdminPath;
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
// Check if legacy admin file exists in local plugin
|
|
1344
|
+
if (fs$2.existsSync(path$1.join(localPluginPath, 'strapi-admin.js'))) {
|
|
1345
|
+
pluginImportPaths[plugin.modulePath] = 'strapi-admin';
|
|
1346
|
+
return true;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
// This plugin is a module, so we need to check if it has a strapi-admin export
|
|
1350
|
+
if (require.resolve(`${plugin.modulePath}/strapi-admin`)) {
|
|
1351
|
+
pluginImportPaths[plugin.modulePath] = 'strapi-admin';
|
|
1352
|
+
return true;
|
|
1353
|
+
}
|
|
1354
|
+
return false;
|
|
1355
|
+
} catch (err) {
|
|
1356
|
+
if (isError(err) && 'code' in err && (err.code === 'MODULE_NOT_FOUND' || err.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED')) {
|
|
1357
|
+
/**
|
|
1358
|
+
* the plugin does not contain FE code, so we
|
|
1359
|
+
* don't want to import it anyway
|
|
1360
|
+
*/ return false;
|
|
1361
|
+
}
|
|
1362
|
+
throw err;
|
|
1363
|
+
}
|
|
1364
|
+
}).map((plugin)=>({
|
|
1365
|
+
...plugin,
|
|
1366
|
+
modulePath: `${plugin.modulePath}/${pluginImportPaths[plugin.modulePath]}`
|
|
1367
|
+
}));
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
const ADMIN_APP_FILES = [
|
|
1371
|
+
'app.js',
|
|
1372
|
+
'app.mjs',
|
|
1373
|
+
'app.ts',
|
|
1374
|
+
'app.jsx',
|
|
1375
|
+
'app.tsx'
|
|
1376
|
+
];
|
|
1377
|
+
const loadUserAppFile = async ({ runtimeDir, appDir })=>{
|
|
1378
|
+
for (const file of ADMIN_APP_FILES){
|
|
1379
|
+
const filePath = path$1.join(appDir, 'src', 'admin', file);
|
|
1380
|
+
if (await pathExists(filePath)) {
|
|
1381
|
+
return {
|
|
1382
|
+
path: filePath,
|
|
1383
|
+
modulePath: convertSystemPathToModulePath(path$1.relative(runtimeDir, filePath))
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return undefined;
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
const DEFAULT_BROWSERSLIST = [
|
|
1391
|
+
'last 3 major versions',
|
|
1392
|
+
'Firefox ESR',
|
|
1393
|
+
'last 2 Opera versions',
|
|
1394
|
+
'not dead'
|
|
1395
|
+
];
|
|
1396
|
+
const createBuildContext = async ({ cwd, logger, tsconfig, strapi, options = {} })=>{
|
|
1397
|
+
/**
|
|
1398
|
+
* If you make a new strapi instance when one already exists,
|
|
1399
|
+
* you will overwrite the global and the app will _most likely_
|
|
1400
|
+
* crash and die.
|
|
1401
|
+
*/ const strapiInstance = strapi ?? core.createStrapi({
|
|
1402
|
+
// Directories
|
|
1403
|
+
appDir: cwd,
|
|
1404
|
+
distDir: tsconfig?.config.options.outDir ?? '',
|
|
1405
|
+
// Options
|
|
1406
|
+
autoReload: true,
|
|
1407
|
+
serveAdminPanel: false
|
|
1408
|
+
});
|
|
1409
|
+
const serverAbsoluteUrl = strapiInstance.config.get('server.absoluteUrl');
|
|
1410
|
+
const adminAbsoluteUrl = strapiInstance.config.get('admin.absoluteUrl');
|
|
1411
|
+
const adminPath = strapiInstance.config.get('admin.path');
|
|
1412
|
+
// NOTE: Checks that both the server and admin will be served from the same origin (protocol, host, port)
|
|
1413
|
+
const sameOrigin = new URL(adminAbsoluteUrl).origin === new URL(serverAbsoluteUrl).origin;
|
|
1414
|
+
const adminPublicPath = new URL(adminAbsoluteUrl).pathname;
|
|
1415
|
+
const serverPublicPath = new URL(serverAbsoluteUrl).pathname;
|
|
1416
|
+
const appDir = strapiInstance.dirs.app.root;
|
|
1417
|
+
await loadEnv(cwd);
|
|
1418
|
+
const env = getStrapiAdminEnvVars({
|
|
1419
|
+
ADMIN_PATH: adminPublicPath,
|
|
1420
|
+
STRAPI_ADMIN_BACKEND_URL: sameOrigin ? serverPublicPath : serverAbsoluteUrl,
|
|
1421
|
+
STRAPI_TELEMETRY_DISABLED: String(strapiInstance.telemetry.isDisabled)
|
|
1422
|
+
});
|
|
1423
|
+
const envKeys = Object.keys(env);
|
|
1424
|
+
if (envKeys.length > 0) {
|
|
1425
|
+
logger.info([
|
|
1426
|
+
'Including the following ENV variables as part of the JS bundle:',
|
|
1427
|
+
...envKeys.map((key)=>` - ${key}`)
|
|
1428
|
+
].join(os.EOL));
|
|
1429
|
+
}
|
|
1430
|
+
const distPath = path$1.join(strapiInstance.dirs.dist.root, 'build');
|
|
1431
|
+
const distDir = path$1.relative(cwd, distPath);
|
|
1432
|
+
/**
|
|
1433
|
+
* If the distPath already exists, clean it
|
|
1434
|
+
*/ try {
|
|
1435
|
+
logger.debug(`Cleaning dist folder: ${distPath}`);
|
|
1436
|
+
await fs$1.rm(distPath, {
|
|
1437
|
+
recursive: true,
|
|
1438
|
+
force: true
|
|
1439
|
+
});
|
|
1440
|
+
logger.debug('Cleaned dist folder');
|
|
1441
|
+
} catch {
|
|
1442
|
+
// do nothing, it will fail if the folder does not exist
|
|
1443
|
+
logger.debug('There was no dist folder to clean');
|
|
1444
|
+
}
|
|
1445
|
+
const runtimeDir = path$1.join(cwd, '.strapi', 'client');
|
|
1446
|
+
const entry = path$1.relative(cwd, path$1.join(runtimeDir, 'app.js'));
|
|
1447
|
+
const plugins = await getEnabledPlugins({
|
|
1448
|
+
cwd,
|
|
1449
|
+
logger,
|
|
1450
|
+
runtimeDir,
|
|
1451
|
+
strapi: strapiInstance
|
|
1452
|
+
});
|
|
1453
|
+
logger.debug('Enabled plugins', os.EOL, plugins);
|
|
1454
|
+
const pluginsWithFront = getMapOfPluginsWithAdmin(plugins);
|
|
1455
|
+
logger.debug('Enabled plugins with FE', os.EOL, pluginsWithFront);
|
|
1456
|
+
const target = browserslist.loadConfig({
|
|
1457
|
+
path: cwd
|
|
1458
|
+
}) ?? DEFAULT_BROWSERSLIST;
|
|
1459
|
+
const customisations = await loadUserAppFile({
|
|
1460
|
+
appDir,
|
|
1461
|
+
runtimeDir
|
|
1462
|
+
});
|
|
1463
|
+
const features = strapiInstance.config.get('features', undefined);
|
|
1464
|
+
const { bundler = 'vite', ...restOptions } = options;
|
|
1465
|
+
const buildContext = {
|
|
1466
|
+
appDir,
|
|
1467
|
+
adminPath,
|
|
1468
|
+
basePath: adminPublicPath,
|
|
1469
|
+
bundler,
|
|
1470
|
+
customisations,
|
|
1471
|
+
cwd,
|
|
1472
|
+
distDir,
|
|
1473
|
+
distPath,
|
|
1474
|
+
entry,
|
|
1475
|
+
env,
|
|
1476
|
+
features,
|
|
1477
|
+
logger,
|
|
1478
|
+
options: restOptions,
|
|
1479
|
+
plugins: pluginsWithFront,
|
|
1480
|
+
runtimeDir,
|
|
1481
|
+
strapi: strapiInstance,
|
|
1482
|
+
target,
|
|
1483
|
+
tsconfig
|
|
1484
|
+
};
|
|
1485
|
+
return buildContext;
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
const getEntryModule = (ctx)=>{
|
|
1489
|
+
const pluginsObject = ctx.plugins.map(({ name, importName })=>`'${name}': ${importName}`).join(',\n');
|
|
1490
|
+
const pluginsImport = ctx.plugins.map(({ importName, modulePath })=>`import ${importName} from '${modulePath}';`).join('\n');
|
|
1491
|
+
return outdent`
|
|
1492
|
+
/**
|
|
1493
|
+
* This file was automatically generated by Strapi.
|
|
1494
|
+
* Any modifications made will be discarded.
|
|
1495
|
+
*/
|
|
1496
|
+
${pluginsImport}
|
|
1497
|
+
import { renderAdmin } from "@strapi/strapi/admin"
|
|
1498
|
+
|
|
1499
|
+
${ctx.customisations?.modulePath ? `import customisations from '${ctx.customisations.modulePath}'` : ''}
|
|
1500
|
+
|
|
1501
|
+
renderAdmin(
|
|
1502
|
+
document.getElementById("strapi"),
|
|
1503
|
+
{
|
|
1504
|
+
${ctx.customisations?.modulePath ? 'customisations,' : ''}
|
|
1505
|
+
${ctx.features ? `features: ${JSON.stringify(ctx.features)},` : ''}
|
|
1506
|
+
plugins: {
|
|
1507
|
+
${pluginsObject}
|
|
1508
|
+
}
|
|
1509
|
+
})
|
|
1510
|
+
`;
|
|
1511
|
+
};
|
|
1512
|
+
/**
|
|
1513
|
+
* TODO: Here in the future we could add the ability
|
|
1514
|
+
* to load a user's Document component?
|
|
1515
|
+
*/ const getDocumentHTML = ({ logger, props = {} })=>{
|
|
1516
|
+
const result = server.renderToStaticMarkup(react.createElement(_internal.DefaultDocument, props));
|
|
1517
|
+
logger.debug('Rendered the HTML');
|
|
1518
|
+
return outdent`<!DOCTYPE html>${result}`;
|
|
1519
|
+
};
|
|
1520
|
+
const AUTO_GENERATED_WARNING = `
|
|
1521
|
+
This file was automatically generated by Strapi.
|
|
1522
|
+
Any modifications made will be discarded.
|
|
1523
|
+
`.trim();
|
|
1524
|
+
/**
|
|
1525
|
+
* Because we now auto-generate the index.html file,
|
|
1526
|
+
* we should be clear that people _should not_ modify it.
|
|
1527
|
+
*
|
|
1528
|
+
* @internal
|
|
1529
|
+
*/ const decorateHTMLWithAutoGeneratedWarning = (htmlTemplate)=>htmlTemplate.replace(/<head/, `\n<!--\n${AUTO_GENERATED_WARNING}\n-->\n<head`);
|
|
1530
|
+
const writeStaticClientFiles = async (ctx)=>{
|
|
1531
|
+
const prettier = await import('prettier'); // ESM-only
|
|
1532
|
+
/**
|
|
1533
|
+
* For everything to work effectively we create a client folder in `.strapi` at the cwd level.
|
|
1534
|
+
* We then use the function we need to "createAdmin" as well as generate the Document index.html as well.
|
|
1535
|
+
*
|
|
1536
|
+
* All this links together an imaginary "src/index" that then allows vite to correctly build the admin panel.
|
|
1537
|
+
*/ await fs$1.mkdir(ctx.runtimeDir, {
|
|
1538
|
+
recursive: true
|
|
1539
|
+
});
|
|
1540
|
+
ctx.logger.debug('Created the runtime directory');
|
|
1541
|
+
const indexHtml = decorateHTMLWithAutoGeneratedWarning(await getDocumentHTML({
|
|
1542
|
+
logger: ctx.logger,
|
|
1543
|
+
props: ctx.bundler === 'vite' ? {
|
|
1544
|
+
entryPath: `/${ctx.entry}`
|
|
1545
|
+
} : undefined
|
|
1546
|
+
}));
|
|
1547
|
+
await fs$1.writeFile(path$1.join(ctx.runtimeDir, 'index.html'), await prettier.format(indexHtml, {
|
|
1548
|
+
parser: 'html'
|
|
1549
|
+
}));
|
|
1550
|
+
ctx.logger.debug('Wrote the index.html file');
|
|
1551
|
+
await fs$1.writeFile(path$1.join(ctx.runtimeDir, 'app.js'), await prettier.format(getEntryModule(ctx), {
|
|
1552
|
+
parser: 'babel'
|
|
1553
|
+
}));
|
|
1554
|
+
ctx.logger.debug('Wrote the app.js file');
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* @example `$ strapi build`
|
|
1559
|
+
*
|
|
1560
|
+
* @description Builds the admin panel of the strapi application.
|
|
1561
|
+
*/ const build = async ({ logger, cwd, tsconfig, ...options })=>{
|
|
1562
|
+
const timer = getTimer();
|
|
1563
|
+
const { didInstall } = await checkRequiredDependencies({
|
|
1564
|
+
cwd,
|
|
1565
|
+
logger
|
|
1566
|
+
}).catch((err)=>{
|
|
1567
|
+
logger.error(err.message);
|
|
1568
|
+
process.exit(1);
|
|
1569
|
+
});
|
|
1570
|
+
if (didInstall) {
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
if (tsconfig?.config) {
|
|
1574
|
+
timer.start('compilingTS');
|
|
1575
|
+
const compilingTsSpinner = logger.spinner(`Compiling TS`).start();
|
|
1576
|
+
tsUtils__namespace.compile(cwd, {
|
|
1577
|
+
configOptions: {
|
|
1578
|
+
ignoreDiagnostics: false
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
const compilingDuration = timer.end('compilingTS');
|
|
1582
|
+
compilingTsSpinner.text = `Compiling TS (${prettyTime(compilingDuration)})`;
|
|
1583
|
+
compilingTsSpinner.succeed();
|
|
1584
|
+
}
|
|
1585
|
+
timer.start('createBuildContext');
|
|
1586
|
+
const contextSpinner = logger.spinner(`Building build context`).start();
|
|
1587
|
+
console.log('');
|
|
1588
|
+
const ctx = await createBuildContext({
|
|
1589
|
+
cwd,
|
|
1590
|
+
logger,
|
|
1591
|
+
tsconfig,
|
|
1592
|
+
options
|
|
1593
|
+
});
|
|
1594
|
+
const contextDuration = timer.end('createBuildContext');
|
|
1595
|
+
contextSpinner.text = `Building build context (${prettyTime(contextDuration)})`;
|
|
1596
|
+
contextSpinner.succeed();
|
|
1597
|
+
timer.start('buildAdmin');
|
|
1598
|
+
const buildingSpinner = logger.spinner(`Building admin panel`).start();
|
|
1599
|
+
console.log('');
|
|
1600
|
+
try {
|
|
1601
|
+
await writeStaticClientFiles(ctx);
|
|
1602
|
+
if (ctx.bundler === 'webpack') {
|
|
1603
|
+
const { build: buildWebpack } = await Promise.resolve().then(function () { return require('./build-CWUlYFQo.js'); });
|
|
1604
|
+
await buildWebpack(ctx);
|
|
1605
|
+
} else if (ctx.bundler === 'vite') {
|
|
1606
|
+
const { build: buildVite } = await Promise.resolve().then(function () { return require('./build-3I6A92J1.js'); });
|
|
1607
|
+
await buildVite(ctx);
|
|
1608
|
+
}
|
|
1609
|
+
const buildDuration = timer.end('buildAdmin');
|
|
1610
|
+
buildingSpinner.text = `Building admin panel (${prettyTime(buildDuration)})`;
|
|
1611
|
+
buildingSpinner.succeed();
|
|
1612
|
+
} catch (err) {
|
|
1613
|
+
buildingSpinner.fail();
|
|
1614
|
+
throw err;
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
const action$7 = async (options)=>{
|
|
1619
|
+
try {
|
|
1620
|
+
if (options.bundler === 'webpack') {
|
|
1621
|
+
options.logger.warn('[@strapi/strapi]: Using webpack as a bundler is deprecated. You should migrate to vite.');
|
|
1622
|
+
}
|
|
1623
|
+
await build(options);
|
|
1624
|
+
} catch (err) {
|
|
1625
|
+
handleUnexpectedError(err);
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
/**
|
|
1629
|
+
* `$ strapi build`
|
|
1630
|
+
*/ const command$9 = ({ ctx })=>{
|
|
1631
|
+
return commander.createCommand('build').option('--bundler [bundler]', 'Bundler to use (webpack or vite)', 'vite').option('-d, --debug', 'Enable debugging mode with verbose logs', false).option('--minify', 'Minify the output', true).option('--silent', "Don't log anything", false).option('--sourcemap', 'Produce sourcemaps', false).option('--stats', 'Print build statistics to the console', false).description('Build the strapi admin app').action(async (options)=>{
|
|
1632
|
+
return action$7({
|
|
1633
|
+
...options,
|
|
1634
|
+
...ctx
|
|
1635
|
+
});
|
|
1636
|
+
});
|
|
1637
|
+
};
|
|
1638
|
+
|
|
1639
|
+
const action$6 = async ()=>{
|
|
1640
|
+
const appContext = await core.compileStrapi();
|
|
1641
|
+
const app = await core.createStrapi(appContext).load();
|
|
1642
|
+
app.start().then(()=>{
|
|
1643
|
+
const repl = REPL.start(app.config.info.name + ' > ' || 'strapi > '); // eslint-disable-line prefer-template
|
|
1644
|
+
repl.on('exit', (err)=>{
|
|
1645
|
+
if (err) {
|
|
1646
|
+
app.log.error(err);
|
|
1647
|
+
process.exit(1);
|
|
1648
|
+
}
|
|
1649
|
+
app.server.destroy();
|
|
1650
|
+
process.exit(0);
|
|
1651
|
+
});
|
|
1652
|
+
});
|
|
1653
|
+
};
|
|
1654
|
+
/**
|
|
1655
|
+
* `$ strapi console`
|
|
1656
|
+
*/ const command$8 = ()=>{
|
|
1657
|
+
return commander.createCommand('console').description('Open the Strapi framework console').action(runAction('console', action$6));
|
|
1658
|
+
};
|
|
1659
|
+
|
|
1660
|
+
// This method removes all non-admin build files from the dist directory
|
|
1661
|
+
const cleanupDistDirectory = async ({ tsconfig, logger, timer })=>{
|
|
1662
|
+
const distDir = tsconfig?.config?.options?.outDir;
|
|
1663
|
+
if (!distDir || // we don't have a dist dir
|
|
1664
|
+
await fs$1.access(distDir).then(()=>false).catch(()=>true) // it doesn't exist -- if it does but no access, that will be caught later
|
|
1665
|
+
) {
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
const timerName = `cleaningDist${Date.now()}`;
|
|
1669
|
+
timer.start(timerName);
|
|
1670
|
+
const cleaningSpinner = logger.spinner(`Cleaning dist dir ${distDir}`).start();
|
|
1671
|
+
try {
|
|
1672
|
+
const dirContent = await fs$1.readdir(distDir);
|
|
1673
|
+
const validFilenames = dirContent// Ignore the admin build folder
|
|
1674
|
+
.filter((filename)=>filename !== 'build');
|
|
1675
|
+
for (const filename of validFilenames){
|
|
1676
|
+
await fs$1.rm(path$1.resolve(distDir, filename), {
|
|
1677
|
+
recursive: true
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
} catch (err) {
|
|
1681
|
+
const generatingDuration = timer.end(timerName);
|
|
1682
|
+
cleaningSpinner.text = `Error cleaning dist dir: ${err} (${prettyTime(generatingDuration)})`;
|
|
1683
|
+
cleaningSpinner?.fail();
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
const generatingDuration = timer.end(timerName);
|
|
1687
|
+
cleaningSpinner.text = `Cleaning dist dir (${prettyTime(generatingDuration)})`;
|
|
1688
|
+
cleaningSpinner?.succeed();
|
|
1689
|
+
};
|
|
1690
|
+
const develop = async ({ cwd, polling, logger, tsconfig, watchAdmin, ...options })=>{
|
|
1691
|
+
const timer = getTimer();
|
|
1692
|
+
if (cluster.isPrimary) {
|
|
1693
|
+
const { didInstall } = await checkRequiredDependencies({
|
|
1694
|
+
cwd,
|
|
1695
|
+
logger
|
|
1696
|
+
}).catch((err)=>{
|
|
1697
|
+
logger.error(err.message);
|
|
1698
|
+
process.exit(1);
|
|
1699
|
+
});
|
|
1700
|
+
if (didInstall) {
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
if (tsconfig?.config) {
|
|
1704
|
+
// Build without diagnostics in case schemas have changed
|
|
1705
|
+
await cleanupDistDirectory({
|
|
1706
|
+
tsconfig,
|
|
1707
|
+
logger,
|
|
1708
|
+
timer
|
|
1709
|
+
});
|
|
1710
|
+
await tsUtils__namespace.compile(cwd, {
|
|
1711
|
+
configOptions: {
|
|
1712
|
+
ignoreDiagnostics: true
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* IF we're not watching the admin we're going to build it, this makes
|
|
1718
|
+
* sure that at least the admin is built for users & they can interact
|
|
1719
|
+
* with the application.
|
|
1720
|
+
*/ if (!watchAdmin) {
|
|
1721
|
+
timer.start('createBuildContext');
|
|
1722
|
+
const contextSpinner = logger.spinner(`Building build context`).start();
|
|
1723
|
+
console.log('');
|
|
1724
|
+
const ctx = await createBuildContext({
|
|
1725
|
+
cwd,
|
|
1726
|
+
logger,
|
|
1727
|
+
tsconfig,
|
|
1728
|
+
options
|
|
1729
|
+
});
|
|
1730
|
+
const contextDuration = timer.end('createBuildContext');
|
|
1731
|
+
contextSpinner.text = `Building build context (${prettyTime(contextDuration)})`;
|
|
1732
|
+
contextSpinner.succeed();
|
|
1733
|
+
timer.start('creatingAdmin');
|
|
1734
|
+
const adminSpinner = logger.spinner(`Creating admin`).start();
|
|
1735
|
+
await writeStaticClientFiles(ctx);
|
|
1736
|
+
if (ctx.bundler === 'webpack') {
|
|
1737
|
+
const { build: buildWebpack } = await Promise.resolve().then(function () { return require('./build-CWUlYFQo.js'); });
|
|
1738
|
+
await buildWebpack(ctx);
|
|
1739
|
+
} else if (ctx.bundler === 'vite') {
|
|
1740
|
+
const { build: buildVite } = await Promise.resolve().then(function () { return require('./build-3I6A92J1.js'); });
|
|
1741
|
+
await buildVite(ctx);
|
|
1742
|
+
}
|
|
1743
|
+
const adminDuration = timer.end('creatingAdmin');
|
|
1744
|
+
adminSpinner.text = `Creating admin (${prettyTime(adminDuration)})`;
|
|
1745
|
+
adminSpinner.succeed();
|
|
1746
|
+
}
|
|
1747
|
+
cluster.on('message', async (worker, message)=>{
|
|
1748
|
+
switch(message){
|
|
1749
|
+
case 'reload':
|
|
1750
|
+
{
|
|
1751
|
+
if (tsconfig?.config) {
|
|
1752
|
+
// Build without diagnostics in case schemas have changed
|
|
1753
|
+
await cleanupDistDirectory({
|
|
1754
|
+
tsconfig,
|
|
1755
|
+
logger,
|
|
1756
|
+
timer
|
|
1757
|
+
});
|
|
1758
|
+
await tsUtils__namespace.compile(cwd, {
|
|
1759
|
+
configOptions: {
|
|
1760
|
+
ignoreDiagnostics: true
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
logger.debug('cluster has the reload message, sending the worker kill message');
|
|
1765
|
+
worker.send('kill');
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1768
|
+
case 'killed':
|
|
1769
|
+
{
|
|
1770
|
+
logger.debug('cluster has the killed message, forking the cluster');
|
|
1771
|
+
cluster.fork();
|
|
1772
|
+
break;
|
|
1773
|
+
}
|
|
1774
|
+
case 'stop':
|
|
1775
|
+
{
|
|
1776
|
+
process.exit(1);
|
|
1777
|
+
break;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1781
|
+
cluster.fork();
|
|
1782
|
+
}
|
|
1783
|
+
if (cluster.isWorker) {
|
|
1784
|
+
timer.start('loadStrapi');
|
|
1785
|
+
const loadStrapiSpinner = logger.spinner(`Loading Strapi`).start();
|
|
1786
|
+
const strapi = core.createStrapi({
|
|
1787
|
+
appDir: cwd,
|
|
1788
|
+
distDir: tsconfig?.config.options.outDir ?? '',
|
|
1789
|
+
autoReload: true,
|
|
1790
|
+
serveAdminPanel: !watchAdmin
|
|
1791
|
+
});
|
|
1792
|
+
/**
|
|
1793
|
+
* If we're watching the admin panel then we're going to attach the watcher
|
|
1794
|
+
* as a strapi middleware.
|
|
1795
|
+
*/ let bundleWatcher;
|
|
1796
|
+
const strapiInstance = await strapi.load();
|
|
1797
|
+
if (watchAdmin) {
|
|
1798
|
+
timer.start('createBuildContext');
|
|
1799
|
+
const contextSpinner = logger.spinner(`Building build context`).start();
|
|
1800
|
+
console.log('');
|
|
1801
|
+
const ctx = await createBuildContext({
|
|
1802
|
+
cwd,
|
|
1803
|
+
logger,
|
|
1804
|
+
strapi,
|
|
1805
|
+
tsconfig,
|
|
1806
|
+
options
|
|
1807
|
+
});
|
|
1808
|
+
const contextDuration = timer.end('createBuildContext');
|
|
1809
|
+
contextSpinner.text = `Building build context (${prettyTime(contextDuration)})`;
|
|
1810
|
+
contextSpinner.succeed();
|
|
1811
|
+
timer.start('creatingAdmin');
|
|
1812
|
+
const adminSpinner = logger.spinner(`Creating admin`).start();
|
|
1813
|
+
await writeStaticClientFiles(ctx);
|
|
1814
|
+
if (ctx.bundler === 'webpack') {
|
|
1815
|
+
const { watch: watchWebpack } = await Promise.resolve().then(function () { return require('./watch-B7qfL21s.js'); });
|
|
1816
|
+
bundleWatcher = await watchWebpack(ctx);
|
|
1817
|
+
} else if (ctx.bundler === 'vite') {
|
|
1818
|
+
const { watch: watchVite } = await Promise.resolve().then(function () { return require('./watch-BBDpuCFC.js'); });
|
|
1819
|
+
bundleWatcher = await watchVite(ctx);
|
|
1820
|
+
}
|
|
1821
|
+
const adminDuration = timer.end('creatingAdmin');
|
|
1822
|
+
adminSpinner.text = `Creating admin (${prettyTime(adminDuration)})`;
|
|
1823
|
+
adminSpinner.succeed();
|
|
1824
|
+
}
|
|
1825
|
+
const loadStrapiDuration = timer.end('loadStrapi');
|
|
1826
|
+
loadStrapiSpinner.text = `Loading Strapi (${prettyTime(loadStrapiDuration)})`;
|
|
1827
|
+
loadStrapiSpinner.succeed();
|
|
1828
|
+
// For TS projects, type generation is a requirement for the develop command so that the server can restart
|
|
1829
|
+
// For JS projects, we respect the experimental autogenerate setting
|
|
1830
|
+
if (tsconfig?.config || strapi.config.get('typescript.autogenerate') !== false) {
|
|
1831
|
+
timer.start('generatingTS');
|
|
1832
|
+
const generatingTsSpinner = logger.spinner(`Generating types`).start();
|
|
1833
|
+
await tsUtils__namespace.generators.generate({
|
|
1834
|
+
strapi: strapiInstance,
|
|
1835
|
+
pwd: cwd,
|
|
1836
|
+
rootDir: undefined,
|
|
1837
|
+
logger: {
|
|
1838
|
+
silent: true,
|
|
1839
|
+
debug: false
|
|
1840
|
+
},
|
|
1841
|
+
artifacts: {
|
|
1842
|
+
contentTypes: true,
|
|
1843
|
+
components: true
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
const generatingDuration = timer.end('generatingTS');
|
|
1847
|
+
generatingTsSpinner.text = `Generating types (${prettyTime(generatingDuration)})`;
|
|
1848
|
+
generatingTsSpinner.succeed();
|
|
1849
|
+
}
|
|
1850
|
+
if (tsconfig?.config) {
|
|
1851
|
+
timer.start('compilingTS');
|
|
1852
|
+
const compilingTsSpinner = logger.spinner(`Compiling TS`).start();
|
|
1853
|
+
await cleanupDistDirectory({
|
|
1854
|
+
tsconfig,
|
|
1855
|
+
logger,
|
|
1856
|
+
timer
|
|
1857
|
+
});
|
|
1858
|
+
await tsUtils__namespace.compile(cwd, {
|
|
1859
|
+
configOptions: {
|
|
1860
|
+
ignoreDiagnostics: false
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
const compilingDuration = timer.end('compilingTS');
|
|
1864
|
+
compilingTsSpinner.text = `Compiling TS (${prettyTime(compilingDuration)})`;
|
|
1865
|
+
compilingTsSpinner.succeed();
|
|
1866
|
+
}
|
|
1867
|
+
const restart = async ()=>{
|
|
1868
|
+
if (strapiInstance.reload.isWatching && !strapiInstance.reload.isReloading) {
|
|
1869
|
+
strapiInstance.reload.isReloading = true;
|
|
1870
|
+
strapiInstance.reload();
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
const watcher = chokidar.watch(cwd, {
|
|
1874
|
+
ignoreInitial: true,
|
|
1875
|
+
usePolling: polling,
|
|
1876
|
+
ignored: [
|
|
1877
|
+
/(^|[/\\])\../,
|
|
1878
|
+
/tmp/,
|
|
1879
|
+
'**/src/admin/**',
|
|
1880
|
+
'**/src/plugins/**/admin/**',
|
|
1881
|
+
'**/dist/src/plugins/test/admin/**',
|
|
1882
|
+
'**/documentation',
|
|
1883
|
+
'**/documentation/**',
|
|
1884
|
+
'**/node_modules',
|
|
1885
|
+
'**/node_modules/**',
|
|
1886
|
+
'**/plugins.json',
|
|
1887
|
+
'**/build',
|
|
1888
|
+
'**/build/**',
|
|
1889
|
+
'**/log',
|
|
1890
|
+
'**/log/**',
|
|
1891
|
+
'**/logs',
|
|
1892
|
+
'**/logs/**',
|
|
1893
|
+
'**/*.log',
|
|
1894
|
+
'**/index.html',
|
|
1895
|
+
'**/public',
|
|
1896
|
+
'**/public/**',
|
|
1897
|
+
strapiInstance.dirs.static.public,
|
|
1898
|
+
utils.strings.joinBy('/', strapiInstance.dirs.static.public, '**'),
|
|
1899
|
+
'**/*.db*',
|
|
1900
|
+
'**/exports/**',
|
|
1901
|
+
'**/dist/**',
|
|
1902
|
+
'**/*.d.ts',
|
|
1903
|
+
'**/.yalc/**',
|
|
1904
|
+
'**/yalc.lock',
|
|
1905
|
+
// TODO v6: watch only src folder by default, and flip this to watchIncludeFiles
|
|
1906
|
+
...strapiInstance.config.get('admin.watchIgnoreFiles', [])
|
|
1907
|
+
]
|
|
1908
|
+
}).on('add', (path)=>{
|
|
1909
|
+
strapiInstance.log.info(`File created: ${path}`);
|
|
1910
|
+
restart();
|
|
1911
|
+
}).on('change', (path)=>{
|
|
1912
|
+
strapiInstance.log.info(`File changed: ${path}`);
|
|
1913
|
+
restart();
|
|
1914
|
+
}).on('unlink', (path)=>{
|
|
1915
|
+
strapiInstance.log.info(`File deleted: ${path}`);
|
|
1916
|
+
restart();
|
|
1917
|
+
});
|
|
1918
|
+
process.on('message', async (message)=>{
|
|
1919
|
+
switch(message){
|
|
1920
|
+
case 'kill':
|
|
1921
|
+
{
|
|
1922
|
+
logger.debug('child process has the kill message, destroying the strapi instance and sending the killed process message');
|
|
1923
|
+
await watcher.close();
|
|
1924
|
+
await strapiInstance.destroy();
|
|
1925
|
+
if (bundleWatcher) {
|
|
1926
|
+
bundleWatcher.close();
|
|
1927
|
+
}
|
|
1928
|
+
process.send?.('killed');
|
|
1929
|
+
break;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
});
|
|
1933
|
+
strapiInstance.start();
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1937
|
+
const action$5 = async (options)=>{
|
|
1938
|
+
try {
|
|
1939
|
+
if (cluster.isPrimary) {
|
|
1940
|
+
if (options.bundler === 'webpack') {
|
|
1941
|
+
options.logger.warn('[@strapi/strapi]: Using webpack as a bundler is deprecated. You should migrate to vite.');
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
await develop(options);
|
|
1945
|
+
} catch (err) {
|
|
1946
|
+
handleUnexpectedError(err);
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
/**
|
|
1950
|
+
* `$ strapi develop`
|
|
1951
|
+
*/ const command$7 = ({ ctx })=>{
|
|
1952
|
+
return commander.createCommand('develop').alias('dev').option('--bundler [bundler]', 'Bundler to use (webpack or vite)', 'vite').option('-d, --debug', 'Enable debugging mode with verbose logs', false).option('--silent', "Don't log anything", false).option('--polling', 'Watch for file changes in network directories', false).option('--watch-admin', 'Watch the admin panel for hot changes', true).option('--no-watch-admin', 'Do not watch the admin panel for hot changes').option('--open', 'Open the admin in your browser', true).description('Start your Strapi application in development mode').action(async (options)=>{
|
|
1953
|
+
return action$5({
|
|
1954
|
+
...options,
|
|
1955
|
+
...ctx
|
|
1956
|
+
});
|
|
1957
|
+
});
|
|
1958
|
+
};
|
|
1959
|
+
|
|
1960
|
+
/**
|
|
1961
|
+
* `$ strapi generate`
|
|
1962
|
+
*/ const command$6 = ({ argv })=>{
|
|
1963
|
+
return commander.createCommand('generate').description('Launch the interactive API generator').action(()=>{
|
|
1964
|
+
assertCwdContainsStrapiProject('generate');
|
|
1965
|
+
argv.splice(2, 1);
|
|
1966
|
+
// NOTE: this needs to be lazy loaded in order for plop to work correctly
|
|
1967
|
+
import('@strapi/generators').then((gen)=>gen.runCLI());
|
|
1968
|
+
});
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1971
|
+
const action$4 = async ({ uuid, dependencies, all })=>{
|
|
1972
|
+
const config = {
|
|
1973
|
+
reportUUID: Boolean(all || uuid),
|
|
1974
|
+
reportDependencies: Boolean(all || dependencies)
|
|
1975
|
+
};
|
|
1976
|
+
const appContext = await core.compileStrapi();
|
|
1977
|
+
const app = await core.createStrapi(appContext).register();
|
|
1978
|
+
let debugInfo = `Launched In: ${Date.now() - app.config.launchedAt} ms
|
|
1979
|
+
Environment: ${app.config.environment}
|
|
1980
|
+
OS: ${process.platform}-${process.arch}
|
|
1981
|
+
Strapi Version: ${app.config.info.strapi}
|
|
1982
|
+
Node/Yarn Version: ${process.env.npm_config_user_agent}
|
|
1983
|
+
Edition: ${app.EE ? 'Enterprise' : 'Community'}
|
|
1984
|
+
Database: ${app?.config?.database?.connection?.client ?? 'unknown'}`;
|
|
1985
|
+
if (config.reportUUID) {
|
|
1986
|
+
debugInfo += `${os$1.EOL}UUID: ${app.config.uuid}`;
|
|
1987
|
+
}
|
|
1988
|
+
if (config.reportDependencies) {
|
|
1989
|
+
debugInfo += `${os$1.EOL}Dependencies: ${JSON.stringify(app.config.info.dependencies, null, 2)}
|
|
1990
|
+
Dev Dependencies: ${JSON.stringify(app.config.info.devDependencies, null, 2)}`;
|
|
1991
|
+
}
|
|
1992
|
+
console.log(debugInfo);
|
|
1993
|
+
await app.destroy();
|
|
1994
|
+
};
|
|
1995
|
+
/**
|
|
1996
|
+
* `$ strapi report`
|
|
1997
|
+
*/ const command$5 = ()=>{
|
|
1998
|
+
return commander.createCommand('report').description('Get system stats for debugging and submitting issues').option('-u, --uuid', 'Include Project UUID').option('-d, --dependencies', 'Include Project Dependencies').option('--all', 'Include All Information').action(runAction('report', action$4));
|
|
1999
|
+
};
|
|
2000
|
+
|
|
2001
|
+
const action$3 = async ()=>{
|
|
2002
|
+
const appDir = process.cwd();
|
|
2003
|
+
const isTSProject = await tsUtils.isUsingTypeScript(appDir);
|
|
2004
|
+
const outDir = await tsUtils.resolveOutDir(appDir);
|
|
2005
|
+
const distDir = isTSProject ? outDir : appDir;
|
|
2006
|
+
const buildDirExists = fs.existsSync(outDir);
|
|
2007
|
+
if (isTSProject && !buildDirExists) throw new Error(`${outDir} directory not found. Please run the build command before starting your application`);
|
|
2008
|
+
core.createStrapi({
|
|
2009
|
+
appDir,
|
|
2010
|
+
distDir
|
|
2011
|
+
}).start();
|
|
2012
|
+
};
|
|
2013
|
+
/**
|
|
2014
|
+
* `$ strapi start`
|
|
2015
|
+
*/ const command$4 = ()=>{
|
|
2016
|
+
return commander.createCommand('start').description('Start your Strapi application').action(runAction('start', action$3));
|
|
2017
|
+
};
|
|
2018
|
+
|
|
2019
|
+
var version = "5.9.0";
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
* `$ strapi version`
|
|
2023
|
+
*/ const command$3 = ()=>{
|
|
2024
|
+
// load the Strapi package.json to get version and other information
|
|
2025
|
+
return commander.createCommand('version').description('Output the version of Strapi').action(()=>{
|
|
2026
|
+
process.stdout.write(`${version}\n`);
|
|
2027
|
+
process.exit(0);
|
|
2028
|
+
});
|
|
2029
|
+
};
|
|
2030
|
+
|
|
2031
|
+
/**
|
|
2032
|
+
* argParser: Parse a comma-delimited string as an array
|
|
2033
|
+
*/ const parseList = (value)=>{
|
|
2034
|
+
try {
|
|
2035
|
+
return value.split(',').map((item)=>item.trim()); // trim shouldn't be necessary but might help catch unexpected whitespace characters
|
|
2036
|
+
} catch (e) {
|
|
2037
|
+
exitWith(1, `Unrecognized input: ${value}`);
|
|
2038
|
+
}
|
|
2039
|
+
return [];
|
|
2040
|
+
};
|
|
2041
|
+
/**
|
|
2042
|
+
* Returns an argParser that returns a list
|
|
2043
|
+
*/ const getParseListWithChoices = (choices, errorMessage = 'Invalid options:')=>{
|
|
2044
|
+
return (value)=>{
|
|
2045
|
+
const list = parseList(value);
|
|
2046
|
+
const invalid = list.filter((item)=>{
|
|
2047
|
+
return !choices.includes(item);
|
|
2048
|
+
});
|
|
2049
|
+
if (invalid.length > 0) {
|
|
2050
|
+
exitWith(1, `${errorMessage}: ${invalid.join(',')}`);
|
|
2051
|
+
}
|
|
2052
|
+
return list;
|
|
2053
|
+
};
|
|
2054
|
+
};
|
|
2055
|
+
/**
|
|
2056
|
+
* argParser: Parse a string as an integer
|
|
2057
|
+
*/ const parseInteger = (value)=>{
|
|
2058
|
+
// parseInt takes a string and a radix
|
|
2059
|
+
const parsedValue = parseInt(value, 10);
|
|
2060
|
+
if (fp.isNaN(parsedValue)) {
|
|
2061
|
+
throw new commander.InvalidOptionArgumentError(`Not an integer: ${value}`);
|
|
2062
|
+
}
|
|
2063
|
+
return parsedValue;
|
|
2064
|
+
};
|
|
2065
|
+
/**
|
|
2066
|
+
* argParser: Parse a string as a URL object
|
|
2067
|
+
*/ const parseURL = (value)=>{
|
|
2068
|
+
try {
|
|
2069
|
+
const url = new URL(value);
|
|
2070
|
+
if (!url.host) {
|
|
2071
|
+
throw new commander.InvalidOptionArgumentError(`Could not parse url ${value}`);
|
|
2072
|
+
}
|
|
2073
|
+
return url;
|
|
2074
|
+
} catch (e) {
|
|
2075
|
+
throw new commander.InvalidOptionArgumentError(`Could not parse url ${value}`);
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
/**
|
|
2079
|
+
* hook: if encrypt==true and key not provided, prompt for it
|
|
2080
|
+
*/ const promptEncryptionKey = async (thisCommand)=>{
|
|
2081
|
+
const opts = thisCommand.opts();
|
|
2082
|
+
if (!opts.encrypt && opts.key) {
|
|
2083
|
+
return exitWith(1, 'Key may not be present unless encryption is used');
|
|
2084
|
+
}
|
|
2085
|
+
// if encrypt==true but we have no key, prompt for it
|
|
2086
|
+
if (opts.encrypt && !(opts.key && opts.key.length > 0)) {
|
|
2087
|
+
try {
|
|
2088
|
+
const answers = await inquirer.prompt([
|
|
2089
|
+
{
|
|
2090
|
+
type: 'password',
|
|
2091
|
+
message: 'Please enter an encryption key',
|
|
2092
|
+
name: 'key',
|
|
2093
|
+
validate (key) {
|
|
2094
|
+
if (key.length > 0) return true;
|
|
2095
|
+
return 'Key must be present when using the encrypt option';
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
]);
|
|
2099
|
+
opts.key = answers.key;
|
|
2100
|
+
} catch (e) {
|
|
2101
|
+
return exitWith(1, 'Failed to get encryption key');
|
|
2102
|
+
}
|
|
2103
|
+
if (!opts.key) {
|
|
2104
|
+
return exitWith(1, 'Failed to get encryption key');
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
/**
|
|
2109
|
+
* hook: require a confirmation message to be accepted unless forceOption (-f,--force) is used
|
|
2110
|
+
*/ const getCommanderConfirmMessage = (message, { failMessage } = {})=>{
|
|
2111
|
+
return async (command)=>{
|
|
2112
|
+
const confirmed = await confirmMessage(message, {
|
|
2113
|
+
force: command.opts().force
|
|
2114
|
+
});
|
|
2115
|
+
if (!confirmed) {
|
|
2116
|
+
exitWith(1, failMessage);
|
|
2117
|
+
}
|
|
2118
|
+
};
|
|
2119
|
+
};
|
|
2120
|
+
const confirmMessage = async (message, { force } = {})=>{
|
|
2121
|
+
// if we have a force option, respond yes
|
|
2122
|
+
if (force === true) {
|
|
2123
|
+
// attempt to mimic the inquirer prompt exactly
|
|
2124
|
+
console.log(`${chalk.green('?')} ${chalk.bold(message)} ${chalk.cyan('Yes')}`);
|
|
2125
|
+
return true;
|
|
2126
|
+
}
|
|
2127
|
+
const answers = await inquirer.prompt([
|
|
2128
|
+
{
|
|
2129
|
+
type: 'confirm',
|
|
2130
|
+
message,
|
|
2131
|
+
name: `confirm`,
|
|
2132
|
+
default: false
|
|
2133
|
+
}
|
|
2134
|
+
]);
|
|
2135
|
+
return answers.confirm;
|
|
2136
|
+
};
|
|
2137
|
+
const forceOption = new commander.Option('--force', `Automatically answer "yes" to all prompts, including potentially destructive requests, and run non-interactively.`);
|
|
2138
|
+
|
|
2139
|
+
const { errors: { TransferEngineInitializationError } } = dataTransfer.engine;
|
|
2140
|
+
const exitMessageText = (process1, error = false)=>{
|
|
2141
|
+
const processCapitalized = process1[0].toUpperCase() + process1.slice(1);
|
|
2142
|
+
if (!error) {
|
|
2143
|
+
return chalk.bold(chalk.green(`${processCapitalized} process has been completed successfully!`));
|
|
2144
|
+
}
|
|
2145
|
+
return chalk.bold(chalk.red(`${processCapitalized} process failed.`));
|
|
2146
|
+
};
|
|
2147
|
+
const pad = (n)=>{
|
|
2148
|
+
return (n < 10 ? '0' : '') + String(n);
|
|
2149
|
+
};
|
|
2150
|
+
const yyyymmddHHMMSS = ()=>{
|
|
2151
|
+
const date = new Date();
|
|
2152
|
+
return date.getFullYear() + pad(date.getMonth() + 1) + pad(date.getDate()) + pad(date.getHours()) + pad(date.getMinutes()) + pad(date.getSeconds());
|
|
2153
|
+
};
|
|
2154
|
+
const getDefaultExportName = ()=>{
|
|
2155
|
+
return `export_${yyyymmddHHMMSS()}`;
|
|
2156
|
+
};
|
|
2157
|
+
const buildTransferTable = (resultData)=>{
|
|
2158
|
+
if (!resultData) {
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
// Build pretty table
|
|
2162
|
+
const table = new CLITable({
|
|
2163
|
+
head: [
|
|
2164
|
+
'Type',
|
|
2165
|
+
'Count',
|
|
2166
|
+
'Size'
|
|
2167
|
+
].map((text)=>chalk.bold.blue(text))
|
|
2168
|
+
});
|
|
2169
|
+
let totalBytes = 0;
|
|
2170
|
+
let totalItems = 0;
|
|
2171
|
+
Object.keys(resultData).forEach((stage)=>{
|
|
2172
|
+
const item = resultData[stage];
|
|
2173
|
+
if (!item) {
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
table.push([
|
|
2177
|
+
{
|
|
2178
|
+
hAlign: 'left',
|
|
2179
|
+
content: chalk.bold(stage)
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
hAlign: 'right',
|
|
2183
|
+
content: item.count
|
|
2184
|
+
},
|
|
2185
|
+
{
|
|
2186
|
+
hAlign: 'right',
|
|
2187
|
+
content: `${readableBytes(item.bytes, 1, 11)} `
|
|
2188
|
+
}
|
|
2189
|
+
]);
|
|
2190
|
+
totalBytes += item.bytes;
|
|
2191
|
+
totalItems += item.count;
|
|
2192
|
+
if (item.aggregates) {
|
|
2193
|
+
Object.keys(item.aggregates).sort().forEach((subkey)=>{
|
|
2194
|
+
if (!item.aggregates) {
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const subitem = item.aggregates[subkey];
|
|
2198
|
+
table.push([
|
|
2199
|
+
{
|
|
2200
|
+
hAlign: 'left',
|
|
2201
|
+
content: `-- ${chalk.bold.grey(subkey)}`
|
|
2202
|
+
},
|
|
2203
|
+
{
|
|
2204
|
+
hAlign: 'right',
|
|
2205
|
+
content: chalk.grey(subitem.count)
|
|
2206
|
+
},
|
|
2207
|
+
{
|
|
2208
|
+
hAlign: 'right',
|
|
2209
|
+
content: chalk.grey(`(${readableBytes(subitem.bytes, 1, 11)})`)
|
|
2210
|
+
}
|
|
2211
|
+
]);
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
table.push([
|
|
2216
|
+
{
|
|
2217
|
+
hAlign: 'left',
|
|
2218
|
+
content: chalk.bold.green('Total')
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
hAlign: 'right',
|
|
2222
|
+
content: chalk.bold.green(totalItems)
|
|
2223
|
+
},
|
|
2224
|
+
{
|
|
2225
|
+
hAlign: 'right',
|
|
2226
|
+
content: `${chalk.bold.green(readableBytes(totalBytes, 1, 11))} `
|
|
2227
|
+
}
|
|
2228
|
+
]);
|
|
2229
|
+
return table;
|
|
2230
|
+
};
|
|
2231
|
+
const DEFAULT_IGNORED_CONTENT_TYPES = [
|
|
2232
|
+
'admin::permission',
|
|
2233
|
+
'admin::user',
|
|
2234
|
+
'admin::role',
|
|
2235
|
+
'admin::api-token',
|
|
2236
|
+
'admin::api-token-permission',
|
|
2237
|
+
'admin::transfer-token',
|
|
2238
|
+
'admin::transfer-token-permission',
|
|
2239
|
+
'admin::audit-log',
|
|
2240
|
+
'plugin::content-releases.release',
|
|
2241
|
+
'plugin::content-releases.release-action'
|
|
2242
|
+
];
|
|
2243
|
+
const abortTransfer = async ({ engine, strapi: strapi1 })=>{
|
|
2244
|
+
try {
|
|
2245
|
+
await engine.abortTransfer();
|
|
2246
|
+
await strapi1.destroy();
|
|
2247
|
+
} catch (e) {
|
|
2248
|
+
// ignore because there's not much else we can do
|
|
2249
|
+
return false;
|
|
2250
|
+
}
|
|
2251
|
+
return true;
|
|
2252
|
+
};
|
|
2253
|
+
const setSignalHandler = async (handler, signals = [
|
|
2254
|
+
'SIGINT',
|
|
2255
|
+
'SIGTERM',
|
|
2256
|
+
'SIGQUIT'
|
|
2257
|
+
])=>{
|
|
2258
|
+
signals.forEach((signal)=>{
|
|
2259
|
+
// We specifically remove ALL listeners because we have to clear the one added in Strapi bootstrap that has a process.exit
|
|
2260
|
+
// TODO: Ideally Strapi bootstrap would not add that listener, and then this could be more flexible and add/remove only what it needs to
|
|
2261
|
+
process.removeAllListeners(signal);
|
|
2262
|
+
process.on(signal, handler);
|
|
2263
|
+
});
|
|
2264
|
+
};
|
|
2265
|
+
const createStrapiInstance = async (opts = {})=>{
|
|
2266
|
+
try {
|
|
2267
|
+
const appContext = await core.compileStrapi();
|
|
2268
|
+
const app = core.createStrapi({
|
|
2269
|
+
...opts,
|
|
2270
|
+
...appContext
|
|
2271
|
+
});
|
|
2272
|
+
app.log.level = opts.logLevel || 'error';
|
|
2273
|
+
return await app.load();
|
|
2274
|
+
} catch (error) {
|
|
2275
|
+
if (error instanceof Error && 'code' in error && error.code === 'ECONNREFUSED') {
|
|
2276
|
+
throw new Error('Process failed. Check the database connection with your Strapi project.');
|
|
2277
|
+
}
|
|
2278
|
+
throw error;
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
const transferDataTypes = Object.keys(dataTransfer.engine.TransferGroupPresets);
|
|
2282
|
+
const throttleOption = new commander.Option('--throttle <delay after each entity>', `Add a delay in milliseconds between each transferred entity`).argParser(parseInteger).hideHelp(); // This option is not publicly documented
|
|
2283
|
+
const excludeOption = new commander.Option('--exclude <comma-separated data types>', `Exclude data using comma-separated types. Available types: ${transferDataTypes.join(',')}`).argParser(getParseListWithChoices(transferDataTypes, 'Invalid options for "exclude"'));
|
|
2284
|
+
const onlyOption = new commander.Option('--only <command-separated data types>', `Include only these types of data (plus schemas). Available types: ${transferDataTypes.join(',')}`).argParser(getParseListWithChoices(transferDataTypes, 'Invalid options for "only"'));
|
|
2285
|
+
const validateExcludeOnly = (command)=>{
|
|
2286
|
+
const { exclude, only } = command.opts();
|
|
2287
|
+
if (!only || !exclude) {
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
const choicesInBoth = only.filter((n)=>{
|
|
2291
|
+
return exclude.indexOf(n) !== -1;
|
|
2292
|
+
});
|
|
2293
|
+
if (choicesInBoth.length > 0) {
|
|
2294
|
+
exitWith(1, `Data types may not be used in both "exclude" and "only" in the same command. Found in both: ${choicesInBoth.join(',')}`);
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
const errorColors = {
|
|
2298
|
+
fatal: chalk.red,
|
|
2299
|
+
error: chalk.red,
|
|
2300
|
+
silly: chalk.yellow
|
|
2301
|
+
};
|
|
2302
|
+
const formatDiagnostic = (operation, info)=>{
|
|
2303
|
+
// Create log file for all incoming diagnostics
|
|
2304
|
+
let logger$1;
|
|
2305
|
+
const getLogger = ()=>{
|
|
2306
|
+
if (!logger$1) {
|
|
2307
|
+
logger$1 = logger.createLogger(logger.configs.createOutputFileConfiguration(`${operation}_${Date.now()}.log`, {
|
|
2308
|
+
level: 'info',
|
|
2309
|
+
format: logger.formats?.detailedLogs
|
|
2310
|
+
}));
|
|
2311
|
+
}
|
|
2312
|
+
return logger$1;
|
|
2313
|
+
};
|
|
2314
|
+
// We don't want to write a log file until there is something to be logged
|
|
2315
|
+
return ({ details, kind })=>{
|
|
2316
|
+
try {
|
|
2317
|
+
if (kind === 'error') {
|
|
2318
|
+
const { message, severity = 'fatal' } = details;
|
|
2319
|
+
const colorizeError = errorColors[severity];
|
|
2320
|
+
const errorMessage = colorizeError(`[${severity.toUpperCase()}] ${message}`);
|
|
2321
|
+
getLogger().error(errorMessage);
|
|
2322
|
+
}
|
|
2323
|
+
if (kind === 'info' && info) {
|
|
2324
|
+
const { message, params, origin } = details;
|
|
2325
|
+
const msg = `[${origin ?? 'transfer'}] ${message}\n${params ? JSON.stringify(params, null, 2) : ''}`;
|
|
2326
|
+
getLogger().info(msg);
|
|
2327
|
+
}
|
|
2328
|
+
if (kind === 'warning') {
|
|
2329
|
+
const { origin, message } = details;
|
|
2330
|
+
getLogger().warn(`(${origin ?? 'transfer'}) ${message}`);
|
|
2331
|
+
}
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
getLogger().error(err);
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
};
|
|
2337
|
+
const loadersFactory = (defaultLoaders = {})=>{
|
|
2338
|
+
const loaders = defaultLoaders;
|
|
2339
|
+
const updateLoader = (stage, data)=>{
|
|
2340
|
+
if (!(stage in loaders)) {
|
|
2341
|
+
createLoader(stage);
|
|
2342
|
+
}
|
|
2343
|
+
const stageData = data[stage];
|
|
2344
|
+
const elapsedTime = stageData?.startTime ? (stageData?.endTime || Date.now()) - stageData.startTime : 0;
|
|
2345
|
+
const size = `size: ${readableBytes(stageData?.bytes ?? 0)}`;
|
|
2346
|
+
const elapsed = `elapsed: ${elapsedTime} ms`;
|
|
2347
|
+
const speed = elapsedTime > 0 ? `(${readableBytes((stageData?.bytes ?? 0) * 1000 / elapsedTime)}/s)` : '';
|
|
2348
|
+
loaders[stage].text = `${stage}: ${stageData?.count ?? 0} transfered (${size}) (${elapsed}) ${!stageData?.endTime ? speed : ''}`;
|
|
2349
|
+
return loaders[stage];
|
|
2350
|
+
};
|
|
2351
|
+
const createLoader = (stage)=>{
|
|
2352
|
+
Object.assign(loaders, {
|
|
2353
|
+
[stage]: ora()
|
|
2354
|
+
});
|
|
2355
|
+
return loaders[stage];
|
|
2356
|
+
};
|
|
2357
|
+
const getLoader = (stage)=>{
|
|
2358
|
+
return loaders[stage];
|
|
2359
|
+
};
|
|
2360
|
+
return {
|
|
2361
|
+
updateLoader,
|
|
2362
|
+
createLoader,
|
|
2363
|
+
getLoader
|
|
2364
|
+
};
|
|
2365
|
+
};
|
|
2366
|
+
/**
|
|
2367
|
+
* Get the telemetry data to be sent for a didDEITSProcess* event from an initialized transfer engine object
|
|
2368
|
+
*/ const getTransferTelemetryPayload = (engine)=>{
|
|
2369
|
+
return {
|
|
2370
|
+
eventProperties: {
|
|
2371
|
+
source: engine?.sourceProvider?.name,
|
|
2372
|
+
destination: engine?.destinationProvider?.name
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
};
|
|
2376
|
+
/**
|
|
2377
|
+
* Get a transfer engine schema diff handler that confirms with the user before bypassing a schema check
|
|
2378
|
+
*/ const getDiffHandler = (engine, { force, action })=>{
|
|
2379
|
+
return async (context, next)=>{
|
|
2380
|
+
// if we abort here, we need to actually exit the process because of conflict with inquirer prompt
|
|
2381
|
+
setSignalHandler(async ()=>{
|
|
2382
|
+
await abortTransfer({
|
|
2383
|
+
engine,
|
|
2384
|
+
strapi: strapi
|
|
2385
|
+
});
|
|
2386
|
+
exitWith(1, exitMessageText(action, true));
|
|
2387
|
+
});
|
|
2388
|
+
let workflowsStatus;
|
|
2389
|
+
const source = 'Schema Integrity';
|
|
2390
|
+
Object.entries(context.diffs).forEach(([uid, diffs])=>{
|
|
2391
|
+
for (const diff of diffs){
|
|
2392
|
+
const path = [
|
|
2393
|
+
uid
|
|
2394
|
+
].concat(diff.path).join('.');
|
|
2395
|
+
const endPath = diff.path[diff.path.length - 1];
|
|
2396
|
+
// Catch known features
|
|
2397
|
+
if (uid === 'plugin::review-workflows.workflow' || uid === 'plugin::review-workflows.workflow-stage' || endPath?.startsWith('strapi_stage') || endPath?.startsWith('strapi_assignee')) {
|
|
2398
|
+
workflowsStatus = diff.kind;
|
|
2399
|
+
} else if (diff.kind === 'added') {
|
|
2400
|
+
engine.reportWarning(chalk.red(`${chalk.bold(path)} does not exist on source`), source);
|
|
2401
|
+
} else if (diff.kind === 'deleted') {
|
|
2402
|
+
engine.reportWarning(chalk.red(`${chalk.bold(path)} does not exist on destination`), source);
|
|
2403
|
+
} else if (diff.kind === 'modified') {
|
|
2404
|
+
engine.reportWarning(chalk.red(`${chalk.bold(path)} has a different data type`), source);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
// output the known feature warnings
|
|
2409
|
+
if (workflowsStatus === 'added') {
|
|
2410
|
+
engine.reportWarning(chalk.red(`Review workflows feature does not exist on source`), source);
|
|
2411
|
+
} else if (workflowsStatus === 'deleted') {
|
|
2412
|
+
engine.reportWarning(chalk.red(`Review workflows feature does not exist on destination`), source);
|
|
2413
|
+
} else if (workflowsStatus === 'modified') {
|
|
2414
|
+
engine.panic(new TransferEngineInitializationError('Unresolved differences in schema [review workflows]'));
|
|
2415
|
+
}
|
|
2416
|
+
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?', {
|
|
2417
|
+
force
|
|
2418
|
+
});
|
|
2419
|
+
// reset handler back to normal
|
|
2420
|
+
setSignalHandler(()=>abortTransfer({
|
|
2421
|
+
engine,
|
|
2422
|
+
strapi: strapi
|
|
2423
|
+
}));
|
|
2424
|
+
if (confirmed) {
|
|
2425
|
+
context.ignoredDiffs = fp.merge(context.diffs, context.ignoredDiffs);
|
|
2426
|
+
}
|
|
2427
|
+
return next(context);
|
|
2428
|
+
};
|
|
2429
|
+
};
|
|
2430
|
+
const getAssetsBackupHandler = (engine, { force, action })=>{
|
|
2431
|
+
return async (context, next)=>{
|
|
2432
|
+
// if we abort here, we need to actually exit the process because of conflict with inquirer prompt
|
|
2433
|
+
setSignalHandler(async ()=>{
|
|
2434
|
+
await abortTransfer({
|
|
2435
|
+
engine,
|
|
2436
|
+
strapi: strapi
|
|
2437
|
+
});
|
|
2438
|
+
exitWith(1, exitMessageText(action, true));
|
|
2439
|
+
});
|
|
2440
|
+
console.warn('The backup for the assets could not be created inside the public directory. Ensure Strapi has write permissions on the public directory.');
|
|
2441
|
+
const confirmed = await confirmMessage('Do you want to continue without backing up your public/uploads files?', {
|
|
2442
|
+
force
|
|
2443
|
+
});
|
|
2444
|
+
if (confirmed) {
|
|
2445
|
+
context.ignore = true;
|
|
2446
|
+
}
|
|
2447
|
+
// reset handler back to normal
|
|
2448
|
+
setSignalHandler(()=>abortTransfer({
|
|
2449
|
+
engine,
|
|
2450
|
+
strapi: strapi
|
|
2451
|
+
}));
|
|
2452
|
+
return next(context);
|
|
2453
|
+
};
|
|
2454
|
+
};
|
|
2455
|
+
const shouldSkipStage = (opts, dataKind)=>{
|
|
2456
|
+
if (opts.exclude?.includes(dataKind)) {
|
|
2457
|
+
return true;
|
|
2458
|
+
}
|
|
2459
|
+
if (opts.only) {
|
|
2460
|
+
return !opts.only.includes(dataKind);
|
|
2461
|
+
}
|
|
2462
|
+
return false;
|
|
2463
|
+
};
|
|
2464
|
+
// Based on exclude/only from options, create the restore object to match
|
|
2465
|
+
const parseRestoreFromOptions = (opts)=>{
|
|
2466
|
+
const entitiesOptions = {
|
|
2467
|
+
exclude: DEFAULT_IGNORED_CONTENT_TYPES,
|
|
2468
|
+
include: undefined
|
|
2469
|
+
};
|
|
2470
|
+
// if content is not included, send an empty array for include
|
|
2471
|
+
if (opts.only && !opts.only.includes('content') || opts.exclude?.includes('content')) {
|
|
2472
|
+
entitiesOptions.include = [];
|
|
2473
|
+
}
|
|
2474
|
+
const restoreConfig = {
|
|
2475
|
+
entities: entitiesOptions,
|
|
2476
|
+
assets: !shouldSkipStage(opts, 'files'),
|
|
2477
|
+
configuration: {
|
|
2478
|
+
webhook: !shouldSkipStage(opts, 'config'),
|
|
2479
|
+
coreStore: !shouldSkipStage(opts, 'config')
|
|
2480
|
+
}
|
|
2481
|
+
};
|
|
2482
|
+
return restoreConfig;
|
|
2483
|
+
};
|
|
2484
|
+
|
|
2485
|
+
const { providers: { createLocalFileDestinationProvider } } = dataTransfer.file;
|
|
2486
|
+
const { providers: { createLocalStrapiSourceProvider: createLocalStrapiSourceProvider$1 } } = dataTransfer.strapi;
|
|
2487
|
+
const BYTES_IN_MB = 1024 * 1024;
|
|
2488
|
+
/**
|
|
2489
|
+
* Export command.
|
|
2490
|
+
*
|
|
2491
|
+
* It transfers data from a local Strapi instance to a file
|
|
2492
|
+
*
|
|
2493
|
+
* @param {ExportCommandOptions} opts
|
|
2494
|
+
*/ var action$2 = (async (opts)=>{
|
|
2495
|
+
// Validate inputs from Commander
|
|
2496
|
+
if (!fp.isObject(opts)) {
|
|
2497
|
+
exitWith(1, 'Could not parse command arguments');
|
|
2498
|
+
}
|
|
2499
|
+
const strapi = await createStrapiInstance();
|
|
2500
|
+
const source = createSourceProvider(strapi);
|
|
2501
|
+
const destination = createDestinationProvider(opts);
|
|
2502
|
+
const engine = dataTransfer.engine.createTransferEngine(source, destination, {
|
|
2503
|
+
versionStrategy: 'ignore',
|
|
2504
|
+
schemaStrategy: 'ignore',
|
|
2505
|
+
exclude: opts.exclude,
|
|
2506
|
+
only: opts.only,
|
|
2507
|
+
throttle: opts.throttle,
|
|
2508
|
+
transforms: {
|
|
2509
|
+
links: [
|
|
2510
|
+
{
|
|
2511
|
+
filter (link) {
|
|
2512
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) && !DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
],
|
|
2516
|
+
entities: [
|
|
2517
|
+
{
|
|
2518
|
+
filter (entity) {
|
|
2519
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
]
|
|
2523
|
+
}
|
|
2524
|
+
});
|
|
2525
|
+
engine.diagnostics.onDiagnostic(formatDiagnostic('export', opts.verbose));
|
|
2526
|
+
const progress = engine.progress.stream;
|
|
2527
|
+
const { updateLoader } = loadersFactory();
|
|
2528
|
+
progress.on(`stage::start`, ({ stage, data })=>{
|
|
2529
|
+
updateLoader(stage, data).start();
|
|
2530
|
+
});
|
|
2531
|
+
progress.on('stage::finish', ({ stage, data })=>{
|
|
2532
|
+
updateLoader(stage, data).succeed();
|
|
2533
|
+
});
|
|
2534
|
+
progress.on('stage::progress', ({ stage, data })=>{
|
|
2535
|
+
updateLoader(stage, data);
|
|
2536
|
+
});
|
|
2537
|
+
progress.on('transfer::start', async ()=>{
|
|
2538
|
+
console.log(`Starting export...`);
|
|
2539
|
+
await strapi.telemetry.send('didDEITSProcessStart', getTransferTelemetryPayload(engine));
|
|
2540
|
+
});
|
|
2541
|
+
let results;
|
|
2542
|
+
let outFile;
|
|
2543
|
+
try {
|
|
2544
|
+
// Abort transfer if user interrupts process
|
|
2545
|
+
setSignalHandler(()=>abortTransfer({
|
|
2546
|
+
engine,
|
|
2547
|
+
strapi
|
|
2548
|
+
}));
|
|
2549
|
+
results = await engine.transfer();
|
|
2550
|
+
outFile = results.destination?.file?.path ?? '';
|
|
2551
|
+
const outFileExists = await fse.pathExists(outFile);
|
|
2552
|
+
if (!outFileExists) {
|
|
2553
|
+
throw new dataTransfer.engine.errors.TransferEngineTransferError(`Export file not created "${outFile}"`);
|
|
2554
|
+
}
|
|
2555
|
+
// Note: we need to await telemetry or else the process ends before it is sent
|
|
2556
|
+
await strapi.telemetry.send('didDEITSProcessFinish', getTransferTelemetryPayload(engine));
|
|
2557
|
+
try {
|
|
2558
|
+
const table = buildTransferTable(results.engine);
|
|
2559
|
+
console.log(table?.toString());
|
|
2560
|
+
} catch (e) {
|
|
2561
|
+
console.error('There was an error displaying the results of the transfer.');
|
|
2562
|
+
}
|
|
2563
|
+
console.log(`Export archive is in ${chalk.green(outFile)}`);
|
|
2564
|
+
exitWith(0, exitMessageText('export'));
|
|
2565
|
+
} catch {
|
|
2566
|
+
await strapi.telemetry.send('didDEITSProcessFail', getTransferTelemetryPayload(engine));
|
|
2567
|
+
exitWith(1, exitMessageText('export', true));
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
/**
|
|
2571
|
+
* It creates a local strapi destination provider
|
|
2572
|
+
*/ const createSourceProvider = (strapi)=>{
|
|
2573
|
+
return createLocalStrapiSourceProvider$1({
|
|
2574
|
+
async getStrapi () {
|
|
2575
|
+
return strapi;
|
|
2576
|
+
}
|
|
2577
|
+
});
|
|
2578
|
+
};
|
|
2579
|
+
/**
|
|
2580
|
+
* It creates a local file destination provider based on the given options
|
|
2581
|
+
*/ const createDestinationProvider = (opts)=>{
|
|
2582
|
+
const { file, compress, encrypt, key, maxSizeJsonl } = opts;
|
|
2583
|
+
const filepath = fp.isString(file) && file.length > 0 ? file : getDefaultExportName();
|
|
2584
|
+
const maxSizeJsonlInMb = fp.isFinite(fp.toNumber(maxSizeJsonl)) ? fp.toNumber(maxSizeJsonl) * BYTES_IN_MB : undefined;
|
|
2585
|
+
return createLocalFileDestinationProvider({
|
|
2586
|
+
file: {
|
|
2587
|
+
path: filepath,
|
|
2588
|
+
maxSizeJsonl: maxSizeJsonlInMb
|
|
2589
|
+
},
|
|
2590
|
+
encryption: {
|
|
2591
|
+
enabled: encrypt ?? false,
|
|
2592
|
+
key: encrypt ? key : undefined
|
|
2593
|
+
},
|
|
2594
|
+
compression: {
|
|
2595
|
+
enabled: compress ?? false
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
};
|
|
2599
|
+
|
|
2600
|
+
/**
|
|
2601
|
+
* `$ strapi export`
|
|
2602
|
+
*/ const command$2 = ()=>{
|
|
2603
|
+
return commander.createCommand('export').description('Export data from Strapi to file').allowExcessArguments(false).addOption(new commander.Option('--no-encrypt', `Disables 'aes-128-ecb' encryption of the output file`).default(true)).addOption(new commander.Option('--no-compress', 'Disables gzip compression of output file').default(true)).addOption(new commander.Option('--verbose', 'Enable verbose logs')).addOption(new commander.Option('-k, --key <string>', 'Provide encryption key in command instead of using the prompt')).addOption(new commander.Option('-f, --file <file>', 'name to use for exported file (without extensions)')).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook('preAction', validateExcludeOnly).hook('preAction', promptEncryptionKey).action(action$2);
|
|
2604
|
+
};
|
|
2605
|
+
|
|
2606
|
+
const { providers: { createLocalFileSourceProvider } } = dataTransfer.file;
|
|
2607
|
+
const { providers: { createLocalStrapiDestinationProvider: createLocalStrapiDestinationProvider$1, DEFAULT_CONFLICT_STRATEGY } } = dataTransfer.strapi;
|
|
2608
|
+
const { createTransferEngine: createTransferEngine$1, DEFAULT_VERSION_STRATEGY, DEFAULT_SCHEMA_STRATEGY } = dataTransfer.engine;
|
|
2609
|
+
/**
|
|
2610
|
+
* Import command.
|
|
2611
|
+
*
|
|
2612
|
+
* It transfers data from a file to a local Strapi instance
|
|
2613
|
+
*/ var action$1 = (async (opts)=>{
|
|
2614
|
+
// validate inputs from Commander
|
|
2615
|
+
if (!fp.isObject(opts)) {
|
|
2616
|
+
exitWith(1, 'Could not parse arguments');
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* From strapi backup file
|
|
2620
|
+
*/ const sourceOptions = getLocalFileSourceOptions(opts);
|
|
2621
|
+
const source = createLocalFileSourceProvider(sourceOptions);
|
|
2622
|
+
/**
|
|
2623
|
+
* To local Strapi instance
|
|
2624
|
+
*/ const strapiInstance = await createStrapiInstance();
|
|
2625
|
+
/**
|
|
2626
|
+
* Configure and run the transfer engine
|
|
2627
|
+
*/ const engineOptions = {
|
|
2628
|
+
versionStrategy: DEFAULT_VERSION_STRATEGY,
|
|
2629
|
+
schemaStrategy: DEFAULT_SCHEMA_STRATEGY,
|
|
2630
|
+
exclude: opts.exclude,
|
|
2631
|
+
only: opts.only,
|
|
2632
|
+
throttle: opts.throttle,
|
|
2633
|
+
transforms: {
|
|
2634
|
+
links: [
|
|
2635
|
+
{
|
|
2636
|
+
filter (link) {
|
|
2637
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) && !DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
],
|
|
2641
|
+
entities: [
|
|
2642
|
+
{
|
|
2643
|
+
filter: (entity)=>!DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type)
|
|
2644
|
+
}
|
|
2645
|
+
]
|
|
2646
|
+
}
|
|
2647
|
+
};
|
|
2648
|
+
const destinationOptions = {
|
|
2649
|
+
async getStrapi () {
|
|
2650
|
+
return strapiInstance;
|
|
2651
|
+
},
|
|
2652
|
+
autoDestroy: false,
|
|
2653
|
+
strategy: opts.conflictStrategy || DEFAULT_CONFLICT_STRATEGY,
|
|
2654
|
+
restore: parseRestoreFromOptions(engineOptions)
|
|
2655
|
+
};
|
|
2656
|
+
const destination = createLocalStrapiDestinationProvider$1(destinationOptions);
|
|
2657
|
+
destination.onWarning = (message)=>console.warn(`\n${chalk.yellow('warn')}: ${message}`);
|
|
2658
|
+
const engine = createTransferEngine$1(source, destination, engineOptions);
|
|
2659
|
+
engine.diagnostics.onDiagnostic(formatDiagnostic('import', opts.verbose));
|
|
2660
|
+
const progress = engine.progress.stream;
|
|
2661
|
+
const { updateLoader } = loadersFactory();
|
|
2662
|
+
engine.onSchemaDiff(getDiffHandler(engine, {
|
|
2663
|
+
force: opts.force,
|
|
2664
|
+
action: 'import'
|
|
2665
|
+
}));
|
|
2666
|
+
progress.on(`stage::start`, ({ stage, data })=>{
|
|
2667
|
+
updateLoader(stage, data).start();
|
|
2668
|
+
});
|
|
2669
|
+
progress.on('stage::finish', ({ stage, data })=>{
|
|
2670
|
+
updateLoader(stage, data).succeed();
|
|
2671
|
+
});
|
|
2672
|
+
progress.on('stage::progress', ({ stage, data })=>{
|
|
2673
|
+
updateLoader(stage, data);
|
|
2674
|
+
});
|
|
2675
|
+
progress.on('transfer::start', async ()=>{
|
|
2676
|
+
console.log('Starting import...');
|
|
2677
|
+
await strapiInstance.telemetry.send('didDEITSProcessStart', getTransferTelemetryPayload(engine));
|
|
2678
|
+
});
|
|
2679
|
+
let results;
|
|
2680
|
+
try {
|
|
2681
|
+
// Abort transfer if user interrupts process
|
|
2682
|
+
setSignalHandler(()=>abortTransfer({
|
|
2683
|
+
engine,
|
|
2684
|
+
strapi: strapi
|
|
2685
|
+
}));
|
|
2686
|
+
results = await engine.transfer();
|
|
2687
|
+
try {
|
|
2688
|
+
const table = buildTransferTable(results.engine);
|
|
2689
|
+
console.log(table?.toString());
|
|
2690
|
+
} catch (e) {
|
|
2691
|
+
console.error('There was an error displaying the results of the transfer.');
|
|
2692
|
+
}
|
|
2693
|
+
// Note: we need to await telemetry or else the process ends before it is sent
|
|
2694
|
+
await strapiInstance.telemetry.send('didDEITSProcessFinish', getTransferTelemetryPayload(engine));
|
|
2695
|
+
await strapiInstance.destroy();
|
|
2696
|
+
exitWith(0, exitMessageText('import'));
|
|
2697
|
+
} catch (e) {
|
|
2698
|
+
await strapiInstance.telemetry.send('didDEITSProcessFail', getTransferTelemetryPayload(engine));
|
|
2699
|
+
exitWith(1, exitMessageText('import', true));
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
/**
|
|
2703
|
+
* Infer local file source provider options based on a given filename
|
|
2704
|
+
*/ const getLocalFileSourceOptions = (opts)=>{
|
|
2705
|
+
const options = {
|
|
2706
|
+
file: {
|
|
2707
|
+
path: opts.file ?? ''
|
|
2708
|
+
},
|
|
2709
|
+
compression: {
|
|
2710
|
+
enabled: !!opts.decompress
|
|
2711
|
+
},
|
|
2712
|
+
encryption: {
|
|
2713
|
+
enabled: !!opts.decrypt,
|
|
2714
|
+
key: opts.key
|
|
2715
|
+
}
|
|
2716
|
+
};
|
|
2717
|
+
return options;
|
|
2718
|
+
};
|
|
2719
|
+
|
|
2720
|
+
/**
|
|
2721
|
+
* `$ strapi import`
|
|
2722
|
+
*/ const command$1 = ()=>{
|
|
2723
|
+
return commander.createCommand('import').description('Import data from file to Strapi').allowExcessArguments(false).requiredOption('-f, --file <file>', 'path and filename for the Strapi export file you want to import').addOption(new commander.Option('-k, --key <string>', 'Provide encryption key in command instead of using the prompt')).addOption(new commander.Option('--verbose', 'Enable verbose logs')).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook('preAction', validateExcludeOnly).hook('preAction', async (thisCommand)=>{
|
|
2724
|
+
const opts = thisCommand.opts();
|
|
2725
|
+
const ext = path.extname(String(opts.file));
|
|
2726
|
+
// check extension to guess if we should prompt for key
|
|
2727
|
+
if (ext === '.enc') {
|
|
2728
|
+
if (!opts.key) {
|
|
2729
|
+
const answers = await inquirer.prompt([
|
|
2730
|
+
{
|
|
2731
|
+
type: 'password',
|
|
2732
|
+
message: 'Please enter your decryption key',
|
|
2733
|
+
name: 'key'
|
|
2734
|
+
}
|
|
2735
|
+
]);
|
|
2736
|
+
if (!answers.key?.length) {
|
|
2737
|
+
exitWith(1, 'No key entered, aborting import.');
|
|
2738
|
+
}
|
|
2739
|
+
opts.key = answers.key;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
})// set decrypt and decompress options based on filename
|
|
2743
|
+
.hook('preAction', (thisCommand)=>{
|
|
2744
|
+
const opts = thisCommand.opts();
|
|
2745
|
+
const { extname, parse } = path;
|
|
2746
|
+
let file = opts.file;
|
|
2747
|
+
if (extname(file) === '.enc') {
|
|
2748
|
+
file = parse(file).name; // trim the .enc extension
|
|
2749
|
+
thisCommand.opts().decrypt = true;
|
|
2750
|
+
} else {
|
|
2751
|
+
thisCommand.opts().decrypt = false;
|
|
2752
|
+
}
|
|
2753
|
+
if (extname(file) === '.gz') {
|
|
2754
|
+
file = parse(file).name; // trim the .gz extension
|
|
2755
|
+
thisCommand.opts().decompress = true;
|
|
2756
|
+
} else {
|
|
2757
|
+
thisCommand.opts().decompress = false;
|
|
2758
|
+
}
|
|
2759
|
+
if (extname(file) !== '.tar') {
|
|
2760
|
+
exitWith(1, `The file '${opts.file}' does not appear to be a valid Strapi data file. It must have an extension ending in .tar[.gz][.enc]`);
|
|
2761
|
+
}
|
|
2762
|
+
}).hook('preAction', getCommanderConfirmMessage('The import will delete your existing data! Are you sure you want to proceed?', {
|
|
2763
|
+
failMessage: 'Import process aborted'
|
|
2764
|
+
})).action(action$1);
|
|
2765
|
+
};
|
|
2766
|
+
|
|
2767
|
+
const { createTransferEngine } = dataTransfer.engine;
|
|
2768
|
+
const { providers: { createRemoteStrapiDestinationProvider, createLocalStrapiSourceProvider, createLocalStrapiDestinationProvider, createRemoteStrapiSourceProvider } } = dataTransfer.strapi;
|
|
2769
|
+
/**
|
|
2770
|
+
* Transfer command.
|
|
2771
|
+
*
|
|
2772
|
+
* Transfers data between local Strapi and remote Strapi instances
|
|
2773
|
+
*/ var action = (async (opts)=>{
|
|
2774
|
+
// Validate inputs from Commander
|
|
2775
|
+
if (!fp.isObject(opts)) {
|
|
2776
|
+
exitWith(1, 'Could not parse command arguments');
|
|
2777
|
+
}
|
|
2778
|
+
if (!(opts.from || opts.to) || opts.from && opts.to) {
|
|
2779
|
+
exitWith(1, 'Exactly one source (from) or destination (to) option must be provided');
|
|
2780
|
+
}
|
|
2781
|
+
const strapi = await createStrapiInstance();
|
|
2782
|
+
let source;
|
|
2783
|
+
let destination;
|
|
2784
|
+
// if no URL provided, use local Strapi
|
|
2785
|
+
if (!opts.from) {
|
|
2786
|
+
source = createLocalStrapiSourceProvider({
|
|
2787
|
+
getStrapi: ()=>strapi
|
|
2788
|
+
});
|
|
2789
|
+
} else {
|
|
2790
|
+
if (!opts.fromToken) {
|
|
2791
|
+
exitWith(1, 'Missing token for remote destination');
|
|
2792
|
+
}
|
|
2793
|
+
source = createRemoteStrapiSourceProvider({
|
|
2794
|
+
getStrapi: ()=>strapi,
|
|
2795
|
+
url: opts.from,
|
|
2796
|
+
auth: {
|
|
2797
|
+
type: 'token',
|
|
2798
|
+
token: opts.fromToken
|
|
2799
|
+
}
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
// if no URL provided, use local Strapi
|
|
2803
|
+
if (!opts.to) {
|
|
2804
|
+
destination = createLocalStrapiDestinationProvider({
|
|
2805
|
+
getStrapi: ()=>strapi,
|
|
2806
|
+
strategy: 'restore',
|
|
2807
|
+
restore: parseRestoreFromOptions(opts)
|
|
2808
|
+
});
|
|
2809
|
+
} else {
|
|
2810
|
+
if (!opts.toToken) {
|
|
2811
|
+
exitWith(1, 'Missing token for remote destination');
|
|
2812
|
+
}
|
|
2813
|
+
destination = createRemoteStrapiDestinationProvider({
|
|
2814
|
+
url: opts.to,
|
|
2815
|
+
auth: {
|
|
2816
|
+
type: 'token',
|
|
2817
|
+
token: opts.toToken
|
|
2818
|
+
},
|
|
2819
|
+
strategy: 'restore',
|
|
2820
|
+
restore: parseRestoreFromOptions(opts)
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
if (!source || !destination) {
|
|
2824
|
+
exitWith(1, 'Could not create providers');
|
|
2825
|
+
}
|
|
2826
|
+
const engine = createTransferEngine(source, destination, {
|
|
2827
|
+
versionStrategy: 'exact',
|
|
2828
|
+
schemaStrategy: 'strict',
|
|
2829
|
+
exclude: opts.exclude,
|
|
2830
|
+
only: opts.only,
|
|
2831
|
+
throttle: opts.throttle,
|
|
2832
|
+
transforms: {
|
|
2833
|
+
links: [
|
|
2834
|
+
{
|
|
2835
|
+
filter (link) {
|
|
2836
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) && !DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
],
|
|
2840
|
+
entities: [
|
|
2841
|
+
{
|
|
2842
|
+
filter (entity) {
|
|
2843
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
]
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
engine.diagnostics.onDiagnostic(formatDiagnostic('transfer', opts.verbose));
|
|
2850
|
+
const progress = engine.progress.stream;
|
|
2851
|
+
const { updateLoader } = loadersFactory();
|
|
2852
|
+
engine.onSchemaDiff(getDiffHandler(engine, {
|
|
2853
|
+
force: opts.force,
|
|
2854
|
+
action: 'transfer'
|
|
2855
|
+
}));
|
|
2856
|
+
engine.addErrorHandler('ASSETS_DIRECTORY_ERR', getAssetsBackupHandler(engine, {
|
|
2857
|
+
force: opts.force,
|
|
2858
|
+
action: 'transfer'
|
|
2859
|
+
}));
|
|
2860
|
+
progress.on(`stage::start`, ({ stage, data })=>{
|
|
2861
|
+
updateLoader(stage, data).start();
|
|
2862
|
+
});
|
|
2863
|
+
progress.on('stage::finish', ({ stage, data })=>{
|
|
2864
|
+
updateLoader(stage, data).succeed();
|
|
2865
|
+
});
|
|
2866
|
+
progress.on('stage::progress', ({ stage, data })=>{
|
|
2867
|
+
updateLoader(stage, data);
|
|
2868
|
+
});
|
|
2869
|
+
progress.on('stage::error', ({ stage, data })=>{
|
|
2870
|
+
updateLoader(stage, data).fail();
|
|
2871
|
+
});
|
|
2872
|
+
progress.on('transfer::start', async ()=>{
|
|
2873
|
+
console.log(`Starting transfer...`);
|
|
2874
|
+
await strapi.telemetry.send('didDEITSProcessStart', getTransferTelemetryPayload(engine));
|
|
2875
|
+
});
|
|
2876
|
+
let results;
|
|
2877
|
+
try {
|
|
2878
|
+
// Abort transfer if user interrupts process
|
|
2879
|
+
setSignalHandler(()=>abortTransfer({
|
|
2880
|
+
engine,
|
|
2881
|
+
strapi
|
|
2882
|
+
}));
|
|
2883
|
+
results = await engine.transfer();
|
|
2884
|
+
// Note: we need to await telemetry or else the process ends before it is sent
|
|
2885
|
+
await strapi.telemetry.send('didDEITSProcessFinish', getTransferTelemetryPayload(engine));
|
|
2886
|
+
try {
|
|
2887
|
+
const table = buildTransferTable(results.engine);
|
|
2888
|
+
console.log(table?.toString());
|
|
2889
|
+
} catch (e) {
|
|
2890
|
+
console.error('There was an error displaying the results of the transfer.');
|
|
2891
|
+
}
|
|
2892
|
+
exitWith(0, exitMessageText('transfer'));
|
|
2893
|
+
} catch (e) {
|
|
2894
|
+
await strapi.telemetry.send('didDEITSProcessFail', getTransferTelemetryPayload(engine));
|
|
2895
|
+
exitWith(1, exitMessageText('transfer', true));
|
|
2896
|
+
}
|
|
2897
|
+
});
|
|
2898
|
+
|
|
2899
|
+
/**
|
|
2900
|
+
* `$ strapi transfer`
|
|
2901
|
+
*/ const command = ()=>{
|
|
2902
|
+
return commander.createCommand('transfer').description('Transfer data from one source to another').allowExcessArguments(false).addOption(new commander.Option('--from <sourceURL>', `URL of the remote Strapi instance to get data from`).argParser(parseURL)).addOption(new commander.Option('--from-token <token>', `Transfer token for the remote Strapi source`)).addOption(new commander.Option('--to <destinationURL>', `URL of the remote Strapi instance to send data to`).argParser(parseURL)).addOption(new commander.Option('--to-token <token>', `Transfer token for the remote Strapi destination`)).addOption(new commander.Option('--verbose', 'Enable verbose logs')).addOption(forceOption).addOption(excludeOption).addOption(onlyOption).addOption(throttleOption).hook('preAction', validateExcludeOnly).hook('preAction', ifOptions((opts)=>!(opts.from || opts.to) || opts.from && opts.to, async ()=>exitWith(1, 'Exactly one remote source (from) or destination (to) option must be provided')))// If --from is used, validate the URL and token
|
|
2903
|
+
.hook('preAction', ifOptions((opts)=>opts.from, async (thisCommand)=>{
|
|
2904
|
+
assertUrlHasProtocol(thisCommand.opts().from, [
|
|
2905
|
+
'https:',
|
|
2906
|
+
'http:'
|
|
2907
|
+
]);
|
|
2908
|
+
if (!thisCommand.opts().fromToken) {
|
|
2909
|
+
const answers = await inquirer.prompt([
|
|
2910
|
+
{
|
|
2911
|
+
type: 'password',
|
|
2912
|
+
message: 'Please enter your transfer token for the remote Strapi source',
|
|
2913
|
+
name: 'fromToken'
|
|
2914
|
+
}
|
|
2915
|
+
]);
|
|
2916
|
+
if (!answers.fromToken?.length) {
|
|
2917
|
+
exitWith(1, 'No token provided for remote source, aborting transfer.');
|
|
2918
|
+
}
|
|
2919
|
+
thisCommand.opts().fromToken = answers.fromToken;
|
|
2920
|
+
}
|
|
2921
|
+
await getCommanderConfirmMessage('The transfer will delete all the local Strapi assets and its database. Are you sure you want to proceed?', {
|
|
2922
|
+
failMessage: 'Transfer process aborted'
|
|
2923
|
+
})(thisCommand);
|
|
2924
|
+
}))// If --to is used, validate the URL, token, and confirm restore
|
|
2925
|
+
.hook('preAction', ifOptions((opts)=>opts.to, async (thisCommand)=>{
|
|
2926
|
+
assertUrlHasProtocol(thisCommand.opts().to, [
|
|
2927
|
+
'https:',
|
|
2928
|
+
'http:'
|
|
2929
|
+
]);
|
|
2930
|
+
if (!thisCommand.opts().toToken) {
|
|
2931
|
+
const answers = await inquirer.prompt([
|
|
2932
|
+
{
|
|
2933
|
+
type: 'password',
|
|
2934
|
+
message: 'Please enter your transfer token for the remote Strapi destination',
|
|
2935
|
+
name: 'toToken'
|
|
2936
|
+
}
|
|
2937
|
+
]);
|
|
2938
|
+
if (!answers.toToken?.length) {
|
|
2939
|
+
exitWith(1, 'No token provided for remote destination, aborting transfer.');
|
|
2940
|
+
}
|
|
2941
|
+
thisCommand.opts().toToken = answers.toToken;
|
|
2942
|
+
}
|
|
2943
|
+
await getCommanderConfirmMessage('The transfer will delete existing data from the remote Strapi! Are you sure you want to proceed?', {
|
|
2944
|
+
failMessage: 'Transfer process aborted'
|
|
2945
|
+
})(thisCommand);
|
|
2946
|
+
})).action(action);
|
|
2947
|
+
};
|
|
2948
|
+
|
|
2949
|
+
const commands = [
|
|
2950
|
+
command$p,
|
|
2951
|
+
command$o,
|
|
2952
|
+
command$n,
|
|
2953
|
+
command$m,
|
|
2954
|
+
command$l,
|
|
2955
|
+
command$8,
|
|
2956
|
+
command$k,
|
|
2957
|
+
command$j,
|
|
2958
|
+
command$6,
|
|
2959
|
+
command$i,
|
|
2960
|
+
command$h,
|
|
2961
|
+
command$g,
|
|
2962
|
+
command$5,
|
|
2963
|
+
command$f,
|
|
2964
|
+
command$e,
|
|
2965
|
+
command$4,
|
|
2966
|
+
command$d,
|
|
2967
|
+
command$c,
|
|
2968
|
+
command$b,
|
|
2969
|
+
command$a,
|
|
2970
|
+
command$3,
|
|
2971
|
+
command$9,
|
|
2972
|
+
command$7,
|
|
2973
|
+
command$2,
|
|
2974
|
+
command$1,
|
|
2975
|
+
command,
|
|
2976
|
+
/**
|
|
2977
|
+
* Cloud
|
|
2978
|
+
*/ cloudCli.buildStrapiCloudCommands
|
|
2979
|
+
];
|
|
2980
|
+
|
|
2981
|
+
const silentSpinner = {
|
|
2982
|
+
succeed () {
|
|
2983
|
+
return this;
|
|
2984
|
+
},
|
|
2985
|
+
fail () {
|
|
2986
|
+
return this;
|
|
2987
|
+
},
|
|
2988
|
+
start () {
|
|
2989
|
+
return this;
|
|
2990
|
+
},
|
|
2991
|
+
text: '',
|
|
2992
|
+
isSpinning: false
|
|
2993
|
+
};
|
|
2994
|
+
const silentProgressBar = {
|
|
2995
|
+
start () {
|
|
2996
|
+
return this;
|
|
2997
|
+
},
|
|
2998
|
+
stop () {
|
|
2999
|
+
return this;
|
|
3000
|
+
},
|
|
3001
|
+
update () {
|
|
3002
|
+
return this;
|
|
3003
|
+
}
|
|
3004
|
+
};
|
|
3005
|
+
const createLogger = (options = {})=>{
|
|
3006
|
+
const { silent = false, debug = false, timestamp = true } = options;
|
|
3007
|
+
const state = {
|
|
3008
|
+
errors: 0,
|
|
3009
|
+
warning: 0
|
|
3010
|
+
};
|
|
3011
|
+
return {
|
|
3012
|
+
get warnings () {
|
|
3013
|
+
return state.warning;
|
|
3014
|
+
},
|
|
3015
|
+
get errors () {
|
|
3016
|
+
return state.errors;
|
|
3017
|
+
},
|
|
3018
|
+
debug (...args) {
|
|
3019
|
+
if (silent || !debug) {
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
console.log(chalk.cyan(`[DEBUG]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`), ...args);
|
|
3023
|
+
},
|
|
3024
|
+
info (...args) {
|
|
3025
|
+
if (silent) {
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
console.info(chalk.blue(`[INFO]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`), ...args);
|
|
3029
|
+
},
|
|
3030
|
+
log (...args) {
|
|
3031
|
+
if (silent) {
|
|
3032
|
+
return;
|
|
3033
|
+
}
|
|
3034
|
+
console.info(chalk.blue(`${timestamp ? `\t[${new Date().toISOString()}]` : ''}`), ...args);
|
|
3035
|
+
},
|
|
3036
|
+
success (...args) {
|
|
3037
|
+
if (silent) {
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
console.info(chalk.green(`[SUCCESS]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`), ...args);
|
|
3041
|
+
},
|
|
3042
|
+
warn (...args) {
|
|
3043
|
+
state.warning += 1;
|
|
3044
|
+
if (silent) {
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
console.warn(chalk.yellow(`[WARN]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`), ...args);
|
|
3048
|
+
},
|
|
3049
|
+
error (...args) {
|
|
3050
|
+
state.errors += 1;
|
|
3051
|
+
if (silent) {
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
console.error(chalk.red(`[ERROR]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`), ...args);
|
|
3055
|
+
},
|
|
3056
|
+
spinner (text) {
|
|
3057
|
+
if (silent) {
|
|
3058
|
+
return silentSpinner;
|
|
3059
|
+
}
|
|
3060
|
+
return ora(text);
|
|
3061
|
+
},
|
|
3062
|
+
progressBar (totalSize, text) {
|
|
3063
|
+
if (silent) {
|
|
3064
|
+
return silentProgressBar;
|
|
3065
|
+
}
|
|
3066
|
+
const progressBar = new cliProgress__namespace.SingleBar({
|
|
3067
|
+
format: `${text ? `${text} |` : ''}${chalk.green('{bar}')}| {percentage}%`,
|
|
3068
|
+
barCompleteChar: '\u2588',
|
|
3069
|
+
barIncompleteChar: '\u2591',
|
|
3070
|
+
hideCursor: true,
|
|
3071
|
+
forceRedraw: true
|
|
3072
|
+
});
|
|
3073
|
+
progressBar.start(totalSize, 0);
|
|
3074
|
+
return progressBar;
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
};
|
|
3078
|
+
|
|
3079
|
+
/**
|
|
3080
|
+
* @description Load a tsconfig.json file and return the parsed config.
|
|
3081
|
+
*
|
|
3082
|
+
* @internal
|
|
3083
|
+
*/ const loadTsConfig = ({ cwd, path, logger })=>{
|
|
3084
|
+
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, path);
|
|
3085
|
+
if (!configPath) {
|
|
3086
|
+
return undefined;
|
|
3087
|
+
}
|
|
3088
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
3089
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd);
|
|
3090
|
+
logger.debug(`Loaded user TS config:`, os$1.EOL, parsedConfig);
|
|
3091
|
+
return {
|
|
3092
|
+
config: parsedConfig,
|
|
3093
|
+
path: configPath
|
|
3094
|
+
};
|
|
3095
|
+
};
|
|
3096
|
+
|
|
3097
|
+
const createCLI = async (argv, command = new commander.Command())=>{
|
|
3098
|
+
// Initial program setup
|
|
3099
|
+
command.storeOptionsAsProperties(false).allowUnknownOption(true);
|
|
3100
|
+
// Help command
|
|
3101
|
+
command.helpOption('-h, --help', 'Display help for command');
|
|
3102
|
+
command.addHelpCommand('help [command]', 'Display help for command');
|
|
3103
|
+
command.version(version, '-v, --version', 'Output the version number');
|
|
3104
|
+
const cwd = process.cwd();
|
|
3105
|
+
const hasDebug = argv.includes('--debug');
|
|
3106
|
+
const hasSilent = argv.includes('--silent');
|
|
3107
|
+
const logger = createLogger({
|
|
3108
|
+
debug: hasDebug,
|
|
3109
|
+
silent: hasSilent,
|
|
3110
|
+
timestamp: false
|
|
3111
|
+
});
|
|
3112
|
+
const tsconfig = loadTsConfig({
|
|
3113
|
+
cwd,
|
|
3114
|
+
path: 'tsconfig.json',
|
|
3115
|
+
logger
|
|
3116
|
+
});
|
|
3117
|
+
const ctx = {
|
|
3118
|
+
cwd,
|
|
3119
|
+
logger,
|
|
3120
|
+
tsconfig
|
|
3121
|
+
};
|
|
3122
|
+
// Load all commands
|
|
3123
|
+
for (const commandFactory of commands){
|
|
3124
|
+
try {
|
|
3125
|
+
const subCommand = await commandFactory({
|
|
3126
|
+
command,
|
|
3127
|
+
argv,
|
|
3128
|
+
ctx
|
|
3129
|
+
});
|
|
3130
|
+
// Add this command to the Commander command object
|
|
3131
|
+
if (subCommand) {
|
|
3132
|
+
command.addCommand(subCommand);
|
|
3133
|
+
}
|
|
3134
|
+
} catch (e) {
|
|
3135
|
+
console.error(`Failed to load command`, e);
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
// TODO v6: remove these deprecation notices
|
|
3139
|
+
const deprecatedCommands = [
|
|
3140
|
+
{
|
|
3141
|
+
name: 'plugin:init',
|
|
3142
|
+
message: 'Please use `npx @strapi/sdk-plugin init` instead.'
|
|
3143
|
+
},
|
|
3144
|
+
{
|
|
3145
|
+
name: 'plugin:verify',
|
|
3146
|
+
message: 'After migrating your plugin to v5, use `strapi-plugin verify`'
|
|
3147
|
+
},
|
|
3148
|
+
{
|
|
3149
|
+
name: 'plugin:watch',
|
|
3150
|
+
message: 'After migrating your plugin to v5, use `strapi-plugin watch`'
|
|
3151
|
+
},
|
|
3152
|
+
{
|
|
3153
|
+
name: 'plugin:watch:link',
|
|
3154
|
+
message: 'After migrating your plugin to v5, use `strapi-plugin watch:link`'
|
|
3155
|
+
},
|
|
3156
|
+
{
|
|
3157
|
+
name: 'plugin:build',
|
|
3158
|
+
message: 'After migrating your plugin to v5, use `strapi-plugin build`'
|
|
3159
|
+
}
|
|
3160
|
+
];
|
|
3161
|
+
// Add hidden commands for deprecatedCommands that output a warning that the command has been removed.
|
|
3162
|
+
deprecatedCommands.forEach(({ name, message })=>{
|
|
3163
|
+
const deprecated = new commander.Command(name).command(name).description('(deprecated)').action(()=>{
|
|
3164
|
+
console.warn(`The command ${name} has been deprecated. See the Strapi 5 migration guide for more information.`);
|
|
3165
|
+
if (message) {
|
|
3166
|
+
console.warn(message);
|
|
3167
|
+
}
|
|
3168
|
+
});
|
|
3169
|
+
command.addCommand(deprecated, {
|
|
3170
|
+
hidden: true
|
|
3171
|
+
});
|
|
3172
|
+
});
|
|
3173
|
+
return command;
|
|
3174
|
+
};
|
|
3175
|
+
const runCLI = async (argv = process.argv, command = new commander.Command())=>{
|
|
3176
|
+
const commands = await createCLI(argv, command);
|
|
3177
|
+
await commands.parseAsync(argv);
|
|
3178
|
+
};
|
|
3179
|
+
|
|
3180
|
+
exports.createCLI = createCLI;
|
|
3181
|
+
exports.getDocumentHTML = getDocumentHTML;
|
|
3182
|
+
exports.isError = isError;
|
|
3183
|
+
exports.loadFile = loadFile;
|
|
3184
|
+
exports.runCLI = runCLI;
|
|
3185
|
+
//# sourceMappingURL=index-kv_K5f7_.js.map
|