@strapi/strapi 4.6.0-beta.2 → 4.6.1
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/README.md +6 -6
- package/bin/strapi.js +87 -55
- package/ee/ee-store.js +18 -0
- package/ee/index.js +173 -0
- package/ee/license.js +102 -0
- package/{lib/utils → ee}/resources/key.pub +0 -0
- package/lib/Strapi.js +14 -4
- package/lib/commands/builders/admin.js +1 -1
- package/lib/commands/transfer/export.js +12 -8
- package/lib/commands/transfer/import.js +12 -22
- package/lib/commands/transfer/transfer.js +4 -2
- package/lib/commands/transfer/utils.js +59 -10
- package/lib/commands/utils/commander.js +1 -1
- package/lib/core/loaders/plugins/index.js +9 -1
- package/lib/services/entity-service/components.js +44 -24
- package/lib/services/entity-service/index.js +1 -0
- package/lib/services/metrics/index.js +3 -54
- package/lib/services/metrics/sender.js +13 -3
- package/lib/types/core/attributes/common.d.ts +4 -1
- package/lib/utils/cron.js +56 -0
- package/lib/utils/ee.js +1 -121
- package/lib/utils/startup-logger.js +2 -4
- package/package.json +19 -18
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
buildTransferTable,
|
|
16
16
|
createStrapiInstance,
|
|
17
17
|
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
18
|
+
formatDiagnostic,
|
|
18
19
|
} = require('./utils');
|
|
19
20
|
|
|
20
21
|
const logger = console;
|
|
@@ -109,6 +110,8 @@ module.exports = async (opts) => {
|
|
|
109
110
|
},
|
|
110
111
|
});
|
|
111
112
|
|
|
113
|
+
engine.diagnostics.onDiagnostic(formatDiagnostic('transfer'));
|
|
114
|
+
|
|
112
115
|
try {
|
|
113
116
|
logger.log(`Starting transfer...`);
|
|
114
117
|
|
|
@@ -120,8 +123,7 @@ module.exports = async (opts) => {
|
|
|
120
123
|
logger.log(`${chalk.bold('Transfer process has been completed successfully!')}`);
|
|
121
124
|
process.exit(0);
|
|
122
125
|
} catch (e) {
|
|
123
|
-
logger.error('Transfer process failed
|
|
124
|
-
logger.error(e);
|
|
126
|
+
logger.error('Transfer process failed.');
|
|
125
127
|
process.exit(1);
|
|
126
128
|
}
|
|
127
129
|
};
|
|
@@ -4,6 +4,11 @@ const chalk = require('chalk');
|
|
|
4
4
|
const Table = require('cli-table3');
|
|
5
5
|
const { Option } = require('commander');
|
|
6
6
|
const { TransferGroupPresets } = require('@strapi/data-transfer/lib/engine');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
configs: { createOutputFileConfiguration },
|
|
10
|
+
createLogger,
|
|
11
|
+
} = require('@strapi/logger');
|
|
7
12
|
const { readableBytes, exitWith } = require('../utils/helpers');
|
|
8
13
|
const strapi = require('../../index');
|
|
9
14
|
const { getParseListWithChoices } = require('../utils/commander');
|
|
@@ -77,29 +82,34 @@ const DEFAULT_IGNORED_CONTENT_TYPES = [
|
|
|
77
82
|
'admin::role',
|
|
78
83
|
'admin::api-token',
|
|
79
84
|
'admin::api-token-permission',
|
|
85
|
+
'admin::audit-log',
|
|
80
86
|
];
|
|
81
87
|
|
|
82
88
|
const createStrapiInstance = async (logLevel = 'error') => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
try {
|
|
90
|
+
const appContext = await strapi.compile();
|
|
91
|
+
const app = strapi(appContext);
|
|
92
|
+
|
|
93
|
+
app.log.level = logLevel;
|
|
94
|
+
return await app.load();
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err.code === 'ECONNREFUSED') {
|
|
97
|
+
throw new Error('Process failed. Check the database connection with your Strapi project.');
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
89
101
|
};
|
|
90
102
|
|
|
91
103
|
const transferDataTypes = Object.keys(TransferGroupPresets);
|
|
92
104
|
|
|
93
105
|
const excludeOption = new Option(
|
|
94
106
|
'--exclude <comma-separated data types>',
|
|
95
|
-
`Exclude
|
|
96
|
-
','
|
|
97
|
-
)}`
|
|
107
|
+
`Exclude data using comma-separated types. Available types: ${transferDataTypes.join(',')}`
|
|
98
108
|
).argParser(getParseListWithChoices(transferDataTypes, 'Invalid options for "exclude"'));
|
|
99
109
|
|
|
100
110
|
const onlyOption = new Option(
|
|
101
111
|
'--only <command-separated data types>',
|
|
102
|
-
`Include only
|
|
112
|
+
`Include only these types of data (plus schemas). Available types: ${transferDataTypes.join(',')}`
|
|
103
113
|
).argParser(getParseListWithChoices(transferDataTypes, 'Invalid options for "only"'));
|
|
104
114
|
|
|
105
115
|
const validateExcludeOnly = (command) => {
|
|
@@ -121,6 +131,44 @@ const validateExcludeOnly = (command) => {
|
|
|
121
131
|
}
|
|
122
132
|
};
|
|
123
133
|
|
|
134
|
+
const errorColors = {
|
|
135
|
+
fatal: chalk.red,
|
|
136
|
+
error: chalk.red,
|
|
137
|
+
silly: chalk.yellow,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const formatDiagnostic =
|
|
141
|
+
(operation) =>
|
|
142
|
+
({ details, kind }) => {
|
|
143
|
+
const logger = createLogger(
|
|
144
|
+
createOutputFileConfiguration(`${operation}_error_log_${Date.now()}.log`)
|
|
145
|
+
);
|
|
146
|
+
try {
|
|
147
|
+
if (kind === 'error') {
|
|
148
|
+
const { message, severity = 'fatal' } = details;
|
|
149
|
+
|
|
150
|
+
const colorizeError = errorColors[severity];
|
|
151
|
+
const errorMessage = colorizeError(`[${severity.toUpperCase()}] ${message}`);
|
|
152
|
+
|
|
153
|
+
logger.error(errorMessage);
|
|
154
|
+
}
|
|
155
|
+
if (kind === 'info') {
|
|
156
|
+
const { message, params } = details;
|
|
157
|
+
|
|
158
|
+
const msg = `${message}\n${params ? JSON.stringify(params, null, 2) : ''}`;
|
|
159
|
+
|
|
160
|
+
logger.info(msg);
|
|
161
|
+
}
|
|
162
|
+
if (kind === 'warning') {
|
|
163
|
+
const { origin, message } = details;
|
|
164
|
+
|
|
165
|
+
logger.warn(`(${origin ?? 'transfer'}) ${message}`);
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
logger.error(err);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
124
172
|
module.exports = {
|
|
125
173
|
buildTransferTable,
|
|
126
174
|
getDefaultExportName,
|
|
@@ -129,4 +177,5 @@ module.exports = {
|
|
|
129
177
|
excludeOption,
|
|
130
178
|
onlyOption,
|
|
131
179
|
validateExcludeOnly,
|
|
180
|
+
formatDiagnostic,
|
|
132
181
|
};
|
|
@@ -85,7 +85,15 @@ const loadPlugins = async (strapi) => {
|
|
|
85
85
|
for (const pluginName of Object.keys(enabledPlugins)) {
|
|
86
86
|
const enabledPlugin = enabledPlugins[pluginName];
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
let serverEntrypointPath;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
serverEntrypointPath = join(enabledPlugin.pathToPlugin, 'strapi-server.js');
|
|
92
|
+
} catch (e) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Error loading the plugin ${pluginName} because ${pluginName} is not installed. Please either install the plugin or remove it's configuration.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
89
97
|
|
|
90
98
|
// only load plugins with a server entrypoint
|
|
91
99
|
if (!(await fse.pathExists(serverEntrypointPath))) {
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const { has, prop, omit, toString, pipe, assign } = require('lodash/fp');
|
|
5
5
|
|
|
6
|
-
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
|
6
|
+
const { contentTypes: contentTypesUtils, mapAsync } = require('@strapi/utils');
|
|
7
7
|
const { ApplicationError } = require('@strapi/utils').errors;
|
|
8
8
|
const { getComponentAttributes } = require('@strapi/utils').contentTypes;
|
|
9
9
|
|
|
10
|
+
const isDialectMySQL = () => strapi.db.dialect.client === 'mysql';
|
|
11
|
+
|
|
10
12
|
const omitComponentData = (contentType, data) => {
|
|
11
13
|
const { attributes } = contentType;
|
|
12
14
|
const componentAttributes = Object.keys(attributes).filter((attributeName) =>
|
|
@@ -43,8 +45,11 @@ const createComponents = async (uid, data) => {
|
|
|
43
45
|
throw new Error('Expected an array to create repeatable component');
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
49
|
+
const components = await mapAsync(
|
|
50
|
+
componentValue,
|
|
51
|
+
(value) => createComponent(componentUID, value),
|
|
52
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
48
53
|
);
|
|
49
54
|
|
|
50
55
|
componentBody[attributeName] = components.map(({ id }) => {
|
|
@@ -77,17 +82,22 @@ const createComponents = async (uid, data) => {
|
|
|
77
82
|
throw new Error('Expected an array to create repeatable component');
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const createDynamicZoneComponents = async (value) => {
|
|
86
|
+
const { id } = await createComponent(value.__component, value);
|
|
87
|
+
return {
|
|
88
|
+
id,
|
|
89
|
+
__component: value.__component,
|
|
90
|
+
__pivot: {
|
|
91
|
+
field: attributeName,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
97
|
+
componentBody[attributeName] = await mapAsync(
|
|
98
|
+
dynamiczoneValues,
|
|
99
|
+
createDynamicZoneComponents,
|
|
100
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
91
101
|
);
|
|
92
102
|
|
|
93
103
|
continue;
|
|
@@ -137,8 +147,11 @@ const updateComponents = async (uid, entityToUpdate, data) => {
|
|
|
137
147
|
throw new Error('Expected an array to create repeatable component');
|
|
138
148
|
}
|
|
139
149
|
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
151
|
+
const components = await mapAsync(
|
|
152
|
+
componentValue,
|
|
153
|
+
(value) => updateOrCreateComponent(componentUID, value),
|
|
154
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
142
155
|
);
|
|
143
156
|
|
|
144
157
|
componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => {
|
|
@@ -173,8 +186,10 @@ const updateComponents = async (uid, entityToUpdate, data) => {
|
|
|
173
186
|
throw new Error('Expected an array to create repeatable component');
|
|
174
187
|
}
|
|
175
188
|
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
190
|
+
componentBody[attributeName] = await mapAsync(
|
|
191
|
+
dynamiczoneValues,
|
|
192
|
+
async (value) => {
|
|
178
193
|
const { id } = await updateOrCreateComponent(value.__component, value);
|
|
179
194
|
|
|
180
195
|
return {
|
|
@@ -184,7 +199,8 @@ const updateComponents = async (uid, entityToUpdate, data) => {
|
|
|
184
199
|
field: attributeName,
|
|
185
200
|
},
|
|
186
201
|
};
|
|
187
|
-
}
|
|
202
|
+
},
|
|
203
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
188
204
|
);
|
|
189
205
|
|
|
190
206
|
continue;
|
|
@@ -287,13 +303,17 @@ const deleteComponents = async (uid, entityToDelete, { loadComponents = true } =
|
|
|
287
303
|
|
|
288
304
|
if (attribute.type === 'component') {
|
|
289
305
|
const { component: componentUID } = attribute;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
306
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
307
|
+
await mapAsync(_.castArray(value), (subValue) => deleteComponent(componentUID, subValue), {
|
|
308
|
+
concurrency: isDialectMySQL() ? 1 : Infinity,
|
|
309
|
+
});
|
|
293
310
|
} else {
|
|
294
311
|
// delete dynamic zone components
|
|
295
|
-
|
|
296
|
-
|
|
312
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
313
|
+
await mapAsync(
|
|
314
|
+
_.castArray(value),
|
|
315
|
+
(subValue) => deleteComponent(subValue.__component, subValue),
|
|
316
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
297
317
|
);
|
|
298
318
|
}
|
|
299
319
|
|
|
@@ -5,12 +5,8 @@
|
|
|
5
5
|
* You can learn more at https://docs.strapi.io/developer-docs/latest/getting-started/usage-information.html
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const crypto = require('crypto');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
8
|
const { scheduleJob } = require('node-schedule');
|
|
12
9
|
|
|
13
|
-
const ee = require('../../utils/ee');
|
|
14
10
|
const wrapWithRateLimit = require('./rate-limiter');
|
|
15
11
|
const createSender = require('./sender');
|
|
16
12
|
const createMiddleware = require('./middleware');
|
|
@@ -46,41 +42,14 @@ const createTelemetryInstance = (strapi) => {
|
|
|
46
42
|
strapi.server.use(createMiddleware({ sendEvent }));
|
|
47
43
|
}
|
|
48
44
|
},
|
|
49
|
-
bootstrap() {
|
|
50
|
-
if (strapi.EE === true && ee.isEE === true) {
|
|
51
|
-
const pingDisabled =
|
|
52
|
-
isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
return sendEvent(
|
|
56
|
-
'didCheckLicense',
|
|
57
|
-
{
|
|
58
|
-
groupProperties: {
|
|
59
|
-
licenseInfo: {
|
|
60
|
-
...ee.licenseInfo,
|
|
61
|
-
projectHash: hashProject(strapi),
|
|
62
|
-
dependencyHash: hashDep(strapi),
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
headers: { 'x-strapi-project': 'enterprise' },
|
|
68
|
-
}
|
|
69
|
-
);
|
|
70
|
-
};
|
|
46
|
+
bootstrap() {},
|
|
71
47
|
|
|
72
|
-
if (!pingDisabled) {
|
|
73
|
-
const licenseCron = scheduleJob('0 0 0 * * 7', () => sendLicenseCheck());
|
|
74
|
-
crons.push(licenseCron);
|
|
75
|
-
|
|
76
|
-
sendLicenseCheck();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
48
|
destroy() {
|
|
81
|
-
//
|
|
49
|
+
// Clear open handles
|
|
82
50
|
crons.forEach((cron) => cron.cancel());
|
|
83
51
|
},
|
|
52
|
+
|
|
84
53
|
async send(event, payload) {
|
|
85
54
|
if (isDisabled) return true;
|
|
86
55
|
return sendEvent(event, payload);
|
|
@@ -88,24 +57,4 @@ const createTelemetryInstance = (strapi) => {
|
|
|
88
57
|
};
|
|
89
58
|
};
|
|
90
59
|
|
|
91
|
-
const hash = (str) => crypto.createHash('sha256').update(str).digest('hex');
|
|
92
|
-
|
|
93
|
-
const hashProject = (strapi) =>
|
|
94
|
-
hash(`${strapi.config.get('info.name')}${strapi.config.get('info.description')}`);
|
|
95
|
-
|
|
96
|
-
const hashDep = (strapi) => {
|
|
97
|
-
const depStr = JSON.stringify(strapi.config.info.dependencies);
|
|
98
|
-
const readmePath = path.join(strapi.dirs.app.root, 'README.md');
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
if (fs.existsSync(readmePath)) {
|
|
102
|
-
return hash(`${depStr}${fs.readFileSync(readmePath)}`);
|
|
103
|
-
}
|
|
104
|
-
} catch (err) {
|
|
105
|
-
return hash(`${depStr}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return hash(`${depStr}`);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
60
|
module.exports = createTelemetryInstance;
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const _ = require('lodash');
|
|
6
|
+
const { map, values, sumBy, pipe, flatMap, propEq } = require('lodash/fp');
|
|
6
7
|
const isDocker = require('is-docker');
|
|
7
8
|
const fetch = require('node-fetch');
|
|
8
9
|
const ciEnv = require('ci-info');
|
|
9
10
|
const { isUsingTypeScriptSync } = require('@strapi/typescript-utils');
|
|
10
11
|
const { env } = require('@strapi/utils');
|
|
11
|
-
const ee = require('../../utils/ee');
|
|
12
12
|
const machineID = require('../../utils/machine-id');
|
|
13
13
|
const { generateAdminUserHash } = require('./admin-user-hash');
|
|
14
14
|
|
|
@@ -37,11 +37,18 @@ const addPackageJsonStrapiMetadata = (metadata, strapi) => {
|
|
|
37
37
|
module.exports = (strapi) => {
|
|
38
38
|
const { uuid } = strapi.config;
|
|
39
39
|
const deviceId = machineID();
|
|
40
|
-
const isEE = strapi.EE === true && ee.isEE === true;
|
|
41
40
|
|
|
42
41
|
const serverRootPath = strapi.dirs.app.root;
|
|
43
42
|
const adminRootPath = path.join(strapi.dirs.app.root, 'src', 'admin');
|
|
44
43
|
|
|
44
|
+
const getNumberOfDynamicZones = () => {
|
|
45
|
+
return pipe(
|
|
46
|
+
map('attributes'),
|
|
47
|
+
flatMap(values),
|
|
48
|
+
sumBy(propEq('type', 'dynamiczone'))
|
|
49
|
+
)(strapi.contentTypes);
|
|
50
|
+
};
|
|
51
|
+
|
|
45
52
|
const anonymousUserProperties = {
|
|
46
53
|
environment: strapi.config.environment,
|
|
47
54
|
os: os.type(),
|
|
@@ -55,11 +62,13 @@ module.exports = (strapi) => {
|
|
|
55
62
|
docker: process.env.DOCKER || isDocker(),
|
|
56
63
|
isCI: ciEnv.isCI,
|
|
57
64
|
version: strapi.config.get('info.strapi'),
|
|
58
|
-
projectType: isEE ? 'Enterprise' : 'Community',
|
|
59
65
|
useTypescriptOnServer: isUsingTypeScriptSync(serverRootPath),
|
|
60
66
|
useTypescriptOnAdmin: isUsingTypeScriptSync(adminRootPath),
|
|
61
67
|
projectId: uuid,
|
|
62
68
|
isHostedOnStrapiCloud: env('STRAPI_HOSTING', null) === 'strapi.cloud',
|
|
69
|
+
numberOfAllContentTypes: _.size(strapi.contentTypes), // TODO: V5: This event should be renamed numberOfContentTypes in V5 as the name is already taken to describe the number of content types using i18n.
|
|
70
|
+
numberOfComponents: _.size(strapi.components),
|
|
71
|
+
numberOfDynamicZones: getNumberOfDynamicZones(),
|
|
63
72
|
};
|
|
64
73
|
|
|
65
74
|
addPackageJsonStrapiMetadata(anonymousGroupProperties, strapi);
|
|
@@ -77,6 +86,7 @@ module.exports = (strapi) => {
|
|
|
77
86
|
userProperties: userId ? { ...anonymousUserProperties, ...payload.userProperties } : {},
|
|
78
87
|
groupProperties: {
|
|
79
88
|
...anonymousGroupProperties,
|
|
89
|
+
projectType: strapi.EE ? 'Enterprise' : 'Community',
|
|
80
90
|
...payload.groupProperties,
|
|
81
91
|
},
|
|
82
92
|
}),
|
|
@@ -30,7 +30,10 @@ export type ConfigurableAttribute = { configurable: true };
|
|
|
30
30
|
export type NonConfigurableAttribute = { configurable: false };
|
|
31
31
|
|
|
32
32
|
// custom field
|
|
33
|
-
export type CustomField<T extends string, P extends object = undefined> = {
|
|
33
|
+
export type CustomField<T extends string, P extends object = undefined> = {
|
|
34
|
+
customField: T;
|
|
35
|
+
options?: P;
|
|
36
|
+
};
|
|
34
37
|
|
|
35
38
|
// min/max
|
|
36
39
|
export type SetMinMax<T extends MinMaxOption<U>, U = number> = T;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isEmpty, negate } = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
const INTEGER_REGEX = /^\d+$/;
|
|
6
|
+
const STEP_REGEX = /^\*\/\d+$/;
|
|
7
|
+
const COMPONENTS = [
|
|
8
|
+
{ limit: 60, zeroBasedIndices: true, functionName: 'getSeconds' },
|
|
9
|
+
{ limit: 60, zeroBasedIndices: true, functionName: 'getMinutes' },
|
|
10
|
+
{ limit: 24, zeroBasedIndices: true, functionName: 'getHours' },
|
|
11
|
+
{ limit: 31, zeroBasedIndices: false, functionName: 'getDate' },
|
|
12
|
+
{ limit: 12, zeroBasedIndices: false, functionName: 'getMonth' },
|
|
13
|
+
{ limit: 7, zeroBasedIndices: true, functionName: 'getDay' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const shift = (component, index, date) => {
|
|
17
|
+
if (component === '*') {
|
|
18
|
+
return '*';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { limit, zeroBasedIndices, functionName } = COMPONENTS[index];
|
|
22
|
+
const offset = +!zeroBasedIndices;
|
|
23
|
+
const currentValue = date[functionName]();
|
|
24
|
+
|
|
25
|
+
if (INTEGER_REGEX.test(component)) {
|
|
26
|
+
return ((Number.parseInt(component, 10) + currentValue) % limit) + offset;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (STEP_REGEX.test(component)) {
|
|
30
|
+
const [, step] = component.split('/');
|
|
31
|
+
const frequency = Math.floor(limit / step);
|
|
32
|
+
const list = Array.from({ length: frequency }, (_, index) => index * step);
|
|
33
|
+
return list.map((value) => ((value + currentValue) % limit) + offset).sort((a, b) => a - b);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Unsupported syntax
|
|
37
|
+
return component;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Simulate an interval by shifting a cron expression using the specified date.
|
|
42
|
+
* @param {string} rule A cron expression you want to shift.
|
|
43
|
+
* @param {Date} date The date that's gonna be used as the start of the "interval", it defaults to now.
|
|
44
|
+
* @returns The shifted cron expression.
|
|
45
|
+
*/
|
|
46
|
+
const shiftCronExpression = (rule, date = new Date()) => {
|
|
47
|
+
const components = rule.trim().split(' ').filter(negate(isEmpty));
|
|
48
|
+
const secondsIncluded = components.length === 6;
|
|
49
|
+
return components
|
|
50
|
+
.map((component, index) => shift(component, secondsIncluded ? index : index + 1, date))
|
|
51
|
+
.join(' ');
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
shiftCronExpression,
|
|
56
|
+
};
|
package/lib/utils/ee.js
CHANGED
|
@@ -1,123 +1,3 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const crypto = require('crypto');
|
|
6
|
-
const _ = require('lodash');
|
|
7
|
-
|
|
8
|
-
const publicKey = fs.readFileSync(path.join(__dirname, '../utils/resources/key.pub'));
|
|
9
|
-
|
|
10
|
-
const noop = () => {};
|
|
11
|
-
|
|
12
|
-
const noLog = {
|
|
13
|
-
warn: noop,
|
|
14
|
-
info: noop,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const internals = {};
|
|
18
|
-
const features = {
|
|
19
|
-
bronze: [],
|
|
20
|
-
silver: [],
|
|
21
|
-
gold: ['sso', 'audit-logs'],
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
module.exports = ({ dir, logger = noLog }) => {
|
|
25
|
-
if (_.has(internals, 'isEE')) return internals.isEE;
|
|
26
|
-
|
|
27
|
-
const warnAndReturn = (msg = 'Invalid license. Starting in CE.') => {
|
|
28
|
-
logger.warn(msg);
|
|
29
|
-
internals.isEE = false;
|
|
30
|
-
return false;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
if (process.env.STRAPI_DISABLE_EE === 'true') {
|
|
34
|
-
internals.isEE = false;
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const licensePath = path.join(dir, 'license.txt');
|
|
39
|
-
|
|
40
|
-
let license;
|
|
41
|
-
if (_.has(process.env, 'STRAPI_LICENSE')) {
|
|
42
|
-
license = process.env.STRAPI_LICENSE;
|
|
43
|
-
} else if (fs.existsSync(licensePath)) {
|
|
44
|
-
license = fs.readFileSync(licensePath).toString();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (_.isNil(license)) {
|
|
48
|
-
internals.isEE = false;
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const plainLicense = Buffer.from(license, 'base64').toString();
|
|
54
|
-
const [signatureb64, contentb64] = plainLicense.split('\n');
|
|
55
|
-
|
|
56
|
-
const signature = Buffer.from(signatureb64, 'base64');
|
|
57
|
-
const content = Buffer.from(contentb64, 'base64').toString();
|
|
58
|
-
|
|
59
|
-
const verifier = crypto.createVerify('RSA-SHA256');
|
|
60
|
-
verifier.update(content);
|
|
61
|
-
verifier.end();
|
|
62
|
-
|
|
63
|
-
const isValid = verifier.verify(publicKey, signature);
|
|
64
|
-
if (!isValid) return warnAndReturn();
|
|
65
|
-
|
|
66
|
-
internals.licenseInfo = JSON.parse(content);
|
|
67
|
-
|
|
68
|
-
const expirationTime = new Date(internals.licenseInfo.expireAt).getTime();
|
|
69
|
-
if (expirationTime < new Date().getTime()) {
|
|
70
|
-
return warnAndReturn('License expired. Starting in CE');
|
|
71
|
-
}
|
|
72
|
-
} catch (err) {
|
|
73
|
-
return warnAndReturn();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
internals.isEE = true;
|
|
77
|
-
return true;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
Object.defineProperty(module.exports, 'licenseInfo', {
|
|
81
|
-
get() {
|
|
82
|
-
mustHaveKey('licenseInfo');
|
|
83
|
-
return internals.licenseInfo;
|
|
84
|
-
},
|
|
85
|
-
configurable: false,
|
|
86
|
-
enumerable: false,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
Object.defineProperty(module.exports, 'isEE', {
|
|
90
|
-
get() {
|
|
91
|
-
mustHaveKey('isEE');
|
|
92
|
-
return internals.isEE;
|
|
93
|
-
},
|
|
94
|
-
configurable: false,
|
|
95
|
-
enumerable: false,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
Object.defineProperty(module.exports, 'features', {
|
|
99
|
-
get() {
|
|
100
|
-
mustHaveKey('licenseInfo');
|
|
101
|
-
|
|
102
|
-
const { type: licenseType } = module.exports.licenseInfo;
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
isEnabled(feature) {
|
|
106
|
-
return features[licenseType].includes(feature);
|
|
107
|
-
},
|
|
108
|
-
getEnabled() {
|
|
109
|
-
return features[licenseType];
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
},
|
|
113
|
-
configurable: false,
|
|
114
|
-
enumerable: false,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const mustHaveKey = (key) => {
|
|
118
|
-
if (!_.has(internals, key)) {
|
|
119
|
-
const err = new Error('Tampering with license');
|
|
120
|
-
// err.stack = null;
|
|
121
|
-
throw err;
|
|
122
|
-
}
|
|
123
|
-
};
|
|
3
|
+
module.exports = require('../../ee');
|
|
@@ -4,7 +4,6 @@ const chalk = require('chalk');
|
|
|
4
4
|
const CLITable = require('cli-table3');
|
|
5
5
|
const _ = require('lodash/fp');
|
|
6
6
|
const { getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('@strapi/utils');
|
|
7
|
-
const ee = require('./ee');
|
|
8
7
|
|
|
9
8
|
module.exports = (app) => {
|
|
10
9
|
return {
|
|
@@ -19,15 +18,14 @@ module.exports = (app) => {
|
|
|
19
18
|
chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
|
|
20
19
|
});
|
|
21
20
|
|
|
22
|
-
const isEE = app.EE === true && ee.isEE === true;
|
|
23
|
-
|
|
24
21
|
infoTable.push(
|
|
25
22
|
[chalk.blue('Time'), `${new Date()}`],
|
|
26
23
|
[chalk.blue('Launched in'), `${Date.now() - app.config.launchedAt} ms`],
|
|
27
24
|
[chalk.blue('Environment'), app.config.environment],
|
|
28
25
|
[chalk.blue('Process PID'), process.pid],
|
|
29
26
|
[chalk.blue('Version'), `${app.config.info.strapi} (node ${process.version})`],
|
|
30
|
-
[chalk.blue('Edition'),
|
|
27
|
+
[chalk.blue('Edition'), app.EE ? 'Enterprise' : 'Community'],
|
|
28
|
+
[chalk.blue('Database'), app.db.dialect.client]
|
|
31
29
|
);
|
|
32
30
|
|
|
33
31
|
console.log(infoTable.toString());
|