@strapi/strapi 4.0.0-beta.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/LICENSE +22 -0
- package/README.md +144 -0
- package/bin/strapi.js +186 -0
- package/lib/Strapi.js +470 -0
- package/lib/commands/admin-reset.js +51 -0
- package/lib/commands/build.js +56 -0
- package/lib/commands/configurationDump.js +50 -0
- package/lib/commands/configurationRestore.js +169 -0
- package/lib/commands/console.js +26 -0
- package/lib/commands/develop.js +157 -0
- package/lib/commands/generate-template.js +97 -0
- package/lib/commands/install.js +48 -0
- package/lib/commands/new.js +11 -0
- package/lib/commands/start.js +8 -0
- package/lib/commands/uninstall.js +68 -0
- package/lib/commands/watchAdmin.js +45 -0
- package/lib/container.js +45 -0
- package/lib/core/app-configuration/config-loader.js +20 -0
- package/lib/core/app-configuration/index.js +75 -0
- package/lib/core/app-configuration/load-config-file.js +43 -0
- package/lib/core/app-configuration/load-functions.js +28 -0
- package/lib/core/bootstrap.js +60 -0
- package/lib/core/domain/component/index.js +24 -0
- package/lib/core/domain/component/validator.js +29 -0
- package/lib/core/domain/content-type/index.js +140 -0
- package/lib/core/domain/content-type/validator.js +64 -0
- package/lib/core/domain/module/index.js +106 -0
- package/lib/core/domain/module/validation.js +36 -0
- package/lib/core/loaders/admin.js +16 -0
- package/lib/core/loaders/apis.js +157 -0
- package/lib/core/loaders/components.js +41 -0
- package/lib/core/loaders/index.js +11 -0
- package/lib/core/loaders/middlewares.js +86 -0
- package/lib/core/loaders/plugins/get-enabled-plugins.js +100 -0
- package/lib/core/loaders/plugins/index.js +109 -0
- package/lib/core/loaders/policies.js +28 -0
- package/lib/core/loaders/src-index.js +38 -0
- package/lib/core/registries/apis.js +43 -0
- package/lib/core/registries/config.js +21 -0
- package/lib/core/registries/content-types.js +53 -0
- package/lib/core/registries/controllers.js +43 -0
- package/lib/core/registries/hooks.js +37 -0
- package/lib/core/registries/middlewares.js +30 -0
- package/lib/core/registries/modules.js +44 -0
- package/lib/core/registries/plugins.js +28 -0
- package/lib/core/registries/policies.js +38 -0
- package/lib/core/registries/services.js +58 -0
- package/lib/core/utils.js +35 -0
- package/lib/core-api/controller/collection-type.js +84 -0
- package/lib/core-api/controller/index.js +26 -0
- package/lib/core-api/controller/single-type.js +44 -0
- package/lib/core-api/controller/transform.js +97 -0
- package/lib/core-api/index.js +39 -0
- package/lib/core-api/service/collection-type.js +84 -0
- package/lib/core-api/service/index.js +55 -0
- package/lib/core-api/service/pagination.js +125 -0
- package/lib/core-api/service/single-type.js +58 -0
- package/lib/index.d.ts +26 -0
- package/lib/index.js +3 -0
- package/lib/load/filepath-to-prop-path.js +22 -0
- package/lib/load/glob.js +15 -0
- package/lib/load/index.js +9 -0
- package/lib/load/load-files.js +56 -0
- package/lib/load/package-path.js +9 -0
- package/lib/middlewares/cors/index.js +66 -0
- package/lib/middlewares/error/defaults.json +5 -0
- package/lib/middlewares/error/index.js +147 -0
- package/lib/middlewares/favicon/defaults.json +7 -0
- package/lib/middlewares/favicon/index.js +31 -0
- package/lib/middlewares/gzip/defaults.json +6 -0
- package/lib/middlewares/gzip/index.js +19 -0
- package/lib/middlewares/helmet/defaults.json +18 -0
- package/lib/middlewares/helmet/index.js +9 -0
- package/lib/middlewares/index.js +120 -0
- package/lib/middlewares/ip/defaults.json +7 -0
- package/lib/middlewares/ip/index.js +25 -0
- package/lib/middlewares/logger/defaults.json +5 -0
- package/lib/middlewares/logger/index.js +37 -0
- package/lib/middlewares/parser/defaults.json +11 -0
- package/lib/middlewares/parser/index.js +75 -0
- package/lib/middlewares/poweredBy/defaults.json +5 -0
- package/lib/middlewares/poweredBy/index.js +16 -0
- package/lib/middlewares/public/assets/images/group_people_1.png +0 -0
- package/lib/middlewares/public/assets/images/group_people_2.png +0 -0
- package/lib/middlewares/public/assets/images/group_people_3.png +0 -0
- package/lib/middlewares/public/assets/images/logo_login.png +0 -0
- package/lib/middlewares/public/defaults.json +8 -0
- package/lib/middlewares/public/index.html +66 -0
- package/lib/middlewares/public/index.js +130 -0
- package/lib/middlewares/public/serve-static.js +23 -0
- package/lib/middlewares/responseTime/defaults.json +5 -0
- package/lib/middlewares/responseTime/index.js +25 -0
- package/lib/middlewares/responses/defaults.json +5 -0
- package/lib/middlewares/responses/index.js +19 -0
- package/lib/middlewares/router/defaults.json +7 -0
- package/lib/middlewares/router/index.js +97 -0
- package/lib/middlewares/session/defaults.json +18 -0
- package/lib/middlewares/session/index.js +140 -0
- package/lib/migrations/draft-publish.js +57 -0
- package/lib/services/auth/index.js +92 -0
- package/lib/services/core-store.js +145 -0
- package/lib/services/cron.js +54 -0
- package/lib/services/entity-service/components.js +365 -0
- package/lib/services/entity-service/index.d.ts +91 -0
- package/lib/services/entity-service/index.js +244 -0
- package/lib/services/entity-service/params.js +145 -0
- package/lib/services/entity-validator/index.js +187 -0
- package/lib/services/entity-validator/validators.js +123 -0
- package/lib/services/event-hub.js +15 -0
- package/lib/services/fs.js +58 -0
- package/lib/services/metrics/index.js +104 -0
- package/lib/services/metrics/is-truthy.js +9 -0
- package/lib/services/metrics/middleware.js +33 -0
- package/lib/services/metrics/rate-limiter.js +27 -0
- package/lib/services/metrics/sender.js +76 -0
- package/lib/services/metrics/stringify-deep.js +22 -0
- package/lib/services/server/admin-api.js +14 -0
- package/lib/services/server/api.js +32 -0
- package/lib/services/server/compose-endpoint.js +112 -0
- package/lib/services/server/content-api.js +16 -0
- package/lib/services/server/http-server.js +64 -0
- package/lib/services/server/index.js +108 -0
- package/lib/services/server/middleware.js +28 -0
- package/lib/services/server/policy.js +34 -0
- package/lib/services/server/routing.js +107 -0
- package/lib/services/utils/upload-files.js +79 -0
- package/lib/services/webhook-runner.js +155 -0
- package/lib/services/webhook-store.js +91 -0
- package/lib/services/worker-queue.js +58 -0
- package/lib/utils/addSlash.js +10 -0
- package/lib/utils/ee.js +123 -0
- package/lib/utils/get-dirs.js +15 -0
- package/lib/utils/get-prefixed-dependencies.js +7 -0
- package/lib/utils/index.js +11 -0
- package/lib/utils/is-initialized.js +23 -0
- package/lib/utils/open-browser.js +12 -0
- package/lib/utils/resources/key.pub +9 -0
- package/lib/utils/run-checks.js +22 -0
- package/lib/utils/startup-logger.js +90 -0
- package/lib/utils/success.js +31 -0
- package/lib/utils/update-notifier/index.js +97 -0
- package/lib/utils/url-from-segments.js +12 -0
- package/package.json +133 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { join, extname, basename } = require('path');
|
|
4
|
+
const { existsSync } = require('fs-extra');
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
const fse = require('fs-extra');
|
|
7
|
+
|
|
8
|
+
const normalizeName = _.kebabCase;
|
|
9
|
+
|
|
10
|
+
const DEFAULT_CONTENT_TYPE = {
|
|
11
|
+
schema: {},
|
|
12
|
+
actions: {},
|
|
13
|
+
lifecycles: {},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = async strapi => {
|
|
17
|
+
if (!existsSync(strapi.dirs.api)) {
|
|
18
|
+
throw new Error('Missing api folder. Please create one at `./src/api`');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const apisFDs = await fse.readdir(strapi.dirs.api, { withFileTypes: true });
|
|
22
|
+
const apis = {};
|
|
23
|
+
|
|
24
|
+
// only load folders
|
|
25
|
+
for (const apiFD of apisFDs) {
|
|
26
|
+
if (apiFD.isDirectory()) {
|
|
27
|
+
const apiName = normalizeName(apiFD.name);
|
|
28
|
+
const api = await loadAPI(join(strapi.dirs.api, apiFD.name));
|
|
29
|
+
|
|
30
|
+
apis[apiName] = api;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
validateContentTypesUnicity(apis);
|
|
35
|
+
|
|
36
|
+
for (const apiName in apis) {
|
|
37
|
+
strapi.container.get('apis').add(apiName, apis[apiName]);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const validateContentTypesUnicity = apis => {
|
|
42
|
+
const allApisSchemas = Object.values(apis).flatMap(api => Object.values(api.contentTypes));
|
|
43
|
+
|
|
44
|
+
const names = [];
|
|
45
|
+
allApisSchemas.forEach(({ schema }) => {
|
|
46
|
+
if (schema.info.singularName) {
|
|
47
|
+
const singularName = _.kebabCase(schema.info.singularName);
|
|
48
|
+
if (names.includes(singularName)) {
|
|
49
|
+
throw new Error(`The singular name "${schema.info.singularName}" should be unique`);
|
|
50
|
+
}
|
|
51
|
+
names.push(singularName);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (schema.info.pluralName) {
|
|
55
|
+
const pluralName = _.kebabCase(schema.info.pluralName);
|
|
56
|
+
if (names.includes(pluralName)) {
|
|
57
|
+
throw new Error(`The plural name "${schema.info.pluralName}" should be unique`);
|
|
58
|
+
}
|
|
59
|
+
names.push(pluralName);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const loadAPI = async dir => {
|
|
65
|
+
const [
|
|
66
|
+
index,
|
|
67
|
+
config,
|
|
68
|
+
routes,
|
|
69
|
+
controllers,
|
|
70
|
+
services,
|
|
71
|
+
policies,
|
|
72
|
+
middlewares,
|
|
73
|
+
contentTypes,
|
|
74
|
+
] = await Promise.all([
|
|
75
|
+
loadIndex(dir),
|
|
76
|
+
loadDir(join(dir, 'config')),
|
|
77
|
+
loadDir(join(dir, 'routes')),
|
|
78
|
+
loadDir(join(dir, 'controllers')),
|
|
79
|
+
loadDir(join(dir, 'services')),
|
|
80
|
+
loadDir(join(dir, 'policies')),
|
|
81
|
+
loadDir(join(dir, 'middlewares')),
|
|
82
|
+
loadContentTypes(join(dir, 'content-types')),
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
...(index || {}),
|
|
87
|
+
config: config || {},
|
|
88
|
+
routes: routes || [],
|
|
89
|
+
controllers: controllers || {},
|
|
90
|
+
services: services || {},
|
|
91
|
+
policies: policies || {},
|
|
92
|
+
middlewares: middlewares || {},
|
|
93
|
+
contentTypes: contentTypes || {},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const loadIndex = async dir => {
|
|
98
|
+
if (await fse.pathExists(join(dir, 'index.js'))) {
|
|
99
|
+
return loadFile(join(dir, 'index.js'));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const loadContentTypes = async dir => {
|
|
104
|
+
if (!(await fse.pathExists(dir))) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const fds = await fse.readdir(dir, { withFileTypes: true });
|
|
109
|
+
const contentTypes = {};
|
|
110
|
+
|
|
111
|
+
// only load folders
|
|
112
|
+
for (const fd of fds) {
|
|
113
|
+
if (fd.isFile()) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const contentTypeName = normalizeName(fd.name);
|
|
118
|
+
const contentType = await loadDir(join(dir, fd.name));
|
|
119
|
+
|
|
120
|
+
contentTypes[normalizeName(contentTypeName)] = _.defaults(contentType, DEFAULT_CONTENT_TYPE);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return contentTypes;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const loadDir = async dir => {
|
|
127
|
+
if (!(await fse.pathExists(dir))) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const fds = await fse.readdir(dir, { withFileTypes: true });
|
|
132
|
+
|
|
133
|
+
const root = {};
|
|
134
|
+
for (const fd of fds) {
|
|
135
|
+
if (!fd.isFile()) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const key = basename(fd.name, extname(fd.name));
|
|
140
|
+
root[normalizeName(key)] = await loadFile(join(dir, fd.name));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return root;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const loadFile = file => {
|
|
147
|
+
const ext = extname(file);
|
|
148
|
+
|
|
149
|
+
switch (ext) {
|
|
150
|
+
case '.js':
|
|
151
|
+
return require(file);
|
|
152
|
+
case '.json':
|
|
153
|
+
return fse.readJSON(file);
|
|
154
|
+
default:
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { join } = require('path');
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
const { pathExists } = require('fs-extra');
|
|
6
|
+
const loadFiles = require('../../load/load-files');
|
|
7
|
+
|
|
8
|
+
module.exports = async strapi => {
|
|
9
|
+
if (!(await pathExists(strapi.dirs.components))) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const map = await loadFiles(strapi.dirs.components, '*/*.*(js|json)');
|
|
14
|
+
|
|
15
|
+
return Object.keys(map).reduce((acc, category) => {
|
|
16
|
+
Object.keys(map[category]).forEach(key => {
|
|
17
|
+
const schema = map[category][key];
|
|
18
|
+
|
|
19
|
+
const filePath = join(strapi.dirs.components, category, schema.__filename__);
|
|
20
|
+
|
|
21
|
+
if (!schema.collectionName) {
|
|
22
|
+
return strapi.stopWithError(
|
|
23
|
+
`Component ${key} is missing a "collectionName" property.\nVerify file ${filePath}.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const uid = `${category}.${key}`;
|
|
28
|
+
|
|
29
|
+
acc[uid] = Object.assign(schema, {
|
|
30
|
+
__schema__: _.cloneDeep(schema),
|
|
31
|
+
uid,
|
|
32
|
+
category,
|
|
33
|
+
modelType: 'component',
|
|
34
|
+
modelName: key,
|
|
35
|
+
globalId: schema.globalId || _.upperFirst(_.camelCase(`component_${uid}`)),
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return acc;
|
|
40
|
+
}, {});
|
|
41
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
loadSrcIndex: require('./src-index'),
|
|
5
|
+
loadAPIs: require('./apis'),
|
|
6
|
+
loadMiddlewares: require('./middlewares'),
|
|
7
|
+
loadComponents: require('./components'),
|
|
8
|
+
loadPolicies: require('./policies'),
|
|
9
|
+
loadPlugins: require('./plugins'),
|
|
10
|
+
loadAdmin: require('./admin'),
|
|
11
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Dependencies.
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
const glob = require('../../load/glob');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load middlewares
|
|
10
|
+
*/
|
|
11
|
+
module.exports = async function(strapi) {
|
|
12
|
+
const installedMiddlewares = strapi.config.get('installedMiddlewares');
|
|
13
|
+
const appPath = strapi.config.get('appPath');
|
|
14
|
+
|
|
15
|
+
let middlewares = {};
|
|
16
|
+
|
|
17
|
+
const loaders = createLoaders(strapi);
|
|
18
|
+
|
|
19
|
+
await loaders.loadMiddlewareDependencies(installedMiddlewares, middlewares);
|
|
20
|
+
// internal middlewares
|
|
21
|
+
await loaders.loadInternalMiddlewares(middlewares);
|
|
22
|
+
// local middleware
|
|
23
|
+
await loaders.loadLocalMiddlewares(appPath, middlewares);
|
|
24
|
+
|
|
25
|
+
return middlewares;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build loader functions
|
|
30
|
+
* @param {*} strapi - strapi instance
|
|
31
|
+
*/
|
|
32
|
+
const createLoaders = strapi => {
|
|
33
|
+
const loadMiddlewaresInDir = async (dir, middlewares) => {
|
|
34
|
+
const files = await glob('*/*(index|defaults).*(js|json)', {
|
|
35
|
+
cwd: dir,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
files.forEach(f => {
|
|
39
|
+
const name = f.split('/')[0];
|
|
40
|
+
mountMiddleware(name, [path.resolve(dir, f)], middlewares);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const loadInternalMiddlewares = middlewares =>
|
|
45
|
+
loadMiddlewaresInDir(path.resolve(__dirname, '..', '..', 'middlewares'), middlewares);
|
|
46
|
+
|
|
47
|
+
const loadLocalMiddlewares = (appPath, middlewares) =>
|
|
48
|
+
loadMiddlewaresInDir(path.resolve(appPath, 'src', 'middlewares'), middlewares);
|
|
49
|
+
|
|
50
|
+
const loadMiddlewareDependencies = async (packages, middlewares) => {
|
|
51
|
+
for (let packageName of packages) {
|
|
52
|
+
const baseDir = path.dirname(require.resolve(`@strapi/middleware-${packageName}`));
|
|
53
|
+
const files = await glob('*(index|defaults).*(js|json)', {
|
|
54
|
+
cwd: baseDir,
|
|
55
|
+
absolute: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
mountMiddleware(packageName, files, middlewares);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const mountMiddleware = (name, files, middlewares) => {
|
|
63
|
+
files.forEach(file => {
|
|
64
|
+
middlewares[name] = middlewares[name] || { loaded: false };
|
|
65
|
+
|
|
66
|
+
if (_.endsWith(file, 'index.js') && !middlewares[name].load) {
|
|
67
|
+
return Object.defineProperty(middlewares[name], 'load', {
|
|
68
|
+
configurable: false,
|
|
69
|
+
enumerable: true,
|
|
70
|
+
get: () => require(file)(strapi),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (_.endsWith(file, 'defaults.json')) {
|
|
75
|
+
middlewares[name].defaults = require(file);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
loadInternalMiddlewares,
|
|
83
|
+
loadLocalMiddlewares,
|
|
84
|
+
loadMiddlewareDependencies,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { dirname, join, resolve } = require('path');
|
|
4
|
+
const { statSync, existsSync } = require('fs');
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
const { get, has, pick, pickBy, defaultsDeep, map, prop, pipe } = require('lodash/fp');
|
|
7
|
+
const { isKebabCase } = require('@strapi/utils');
|
|
8
|
+
const loadConfigFile = require('../../app-configuration/load-config-file');
|
|
9
|
+
|
|
10
|
+
const isStrapiPlugin = info => get('strapi.kind', info) === 'plugin';
|
|
11
|
+
const INTERNAL_PLUGINS = [
|
|
12
|
+
'@strapi/plugin-content-manager',
|
|
13
|
+
'@strapi/plugin-content-type-builder',
|
|
14
|
+
'@strapi/plugin-email',
|
|
15
|
+
'@strapi/plugin-upload',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const validatePluginName = pluginName => {
|
|
19
|
+
if (!isKebabCase(pluginName)) {
|
|
20
|
+
throw new Error(`Plugin name "${pluginName}" is not in kebab (an-example-of-kebab-case)`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const toDetailedDeclaration = declaration => {
|
|
25
|
+
if (typeof declaration === 'boolean') {
|
|
26
|
+
return { enabled: declaration };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let detailedDeclaration = pick(['enabled'], declaration);
|
|
30
|
+
if (has('resolve', declaration)) {
|
|
31
|
+
let pathToPlugin = '';
|
|
32
|
+
try {
|
|
33
|
+
pathToPlugin = dirname(require.resolve(declaration.resolve));
|
|
34
|
+
} catch (e) {
|
|
35
|
+
pathToPlugin = resolve(strapi.dirs.root, declaration.resolve);
|
|
36
|
+
|
|
37
|
+
if (!existsSync(pathToPlugin) || !statSync(pathToPlugin).isDirectory()) {
|
|
38
|
+
throw new Error(`${declaration.resolve} couldn't be resolved`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
detailedDeclaration.pathToPlugin = pathToPlugin;
|
|
43
|
+
}
|
|
44
|
+
return detailedDeclaration;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const getEnabledPlugins = async strapi => {
|
|
48
|
+
const internalPlugins = {};
|
|
49
|
+
for (const dep of INTERNAL_PLUGINS) {
|
|
50
|
+
const packagePath = join(dep, 'package.json');
|
|
51
|
+
const packageInfo = require(packagePath);
|
|
52
|
+
|
|
53
|
+
validatePluginName(packageInfo.strapi.name);
|
|
54
|
+
internalPlugins[packageInfo.strapi.name] = toDetailedDeclaration({
|
|
55
|
+
enabled: true,
|
|
56
|
+
resolve: packagePath,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const installedPlugins = {};
|
|
61
|
+
for (const dep in strapi.config.get('info.dependencies', {})) {
|
|
62
|
+
const packagePath = join(dep, 'package.json');
|
|
63
|
+
const packageInfo = require(packagePath);
|
|
64
|
+
|
|
65
|
+
if (isStrapiPlugin(packageInfo)) {
|
|
66
|
+
validatePluginName(packageInfo.strapi.name);
|
|
67
|
+
installedPlugins[packageInfo.strapi.name] = toDetailedDeclaration({
|
|
68
|
+
enabled: true,
|
|
69
|
+
resolve: packagePath,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const declaredPlugins = {};
|
|
75
|
+
const userPluginConfigPath = join(strapi.dirs.config, 'plugins.js');
|
|
76
|
+
const userPluginsConfig = existsSync(userPluginConfigPath)
|
|
77
|
+
? loadConfigFile(userPluginConfigPath)
|
|
78
|
+
: {};
|
|
79
|
+
|
|
80
|
+
_.forEach(userPluginsConfig, (declaration, pluginName) => {
|
|
81
|
+
validatePluginName(pluginName);
|
|
82
|
+
declaredPlugins[pluginName] = toDetailedDeclaration(declaration);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const declaredPluginsResolves = map(prop('pathToPlugin'), declaredPlugins);
|
|
86
|
+
const installedPluginsNotAlreadyUsed = pickBy(
|
|
87
|
+
p => !declaredPluginsResolves.includes(p.pathToPlugin),
|
|
88
|
+
installedPlugins
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const enabledPlugins = pipe(
|
|
92
|
+
defaultsDeep(declaredPlugins),
|
|
93
|
+
defaultsDeep(installedPluginsNotAlreadyUsed),
|
|
94
|
+
pickBy(p => p.enabled)
|
|
95
|
+
)(internalPlugins);
|
|
96
|
+
|
|
97
|
+
return enabledPlugins;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
module.exports = getEnabledPlugins;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { join } = require('path');
|
|
4
|
+
const { existsSync } = require('fs');
|
|
5
|
+
const { defaultsDeep, getOr, get } = require('lodash/fp');
|
|
6
|
+
const { env } = require('@strapi/utils');
|
|
7
|
+
const loadConfigFile = require('../../app-configuration/load-config-file');
|
|
8
|
+
const loadFiles = require('../../../load/load-files');
|
|
9
|
+
const getEnabledPlugins = require('./get-enabled-plugins');
|
|
10
|
+
|
|
11
|
+
const defaultPlugin = {
|
|
12
|
+
bootstrap() {},
|
|
13
|
+
destroy() {},
|
|
14
|
+
register() {},
|
|
15
|
+
config: {
|
|
16
|
+
default: {},
|
|
17
|
+
validator() {},
|
|
18
|
+
},
|
|
19
|
+
routes: [],
|
|
20
|
+
controllers: {},
|
|
21
|
+
services: {},
|
|
22
|
+
policies: {},
|
|
23
|
+
middlewares: {},
|
|
24
|
+
contentTypes: {},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const applyUserExtension = async plugins => {
|
|
28
|
+
const extensionsDir = strapi.dirs.extensions;
|
|
29
|
+
if (!existsSync(extensionsDir)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const extendedSchemas = await loadFiles(extensionsDir, '**/content-types/**/schema.json');
|
|
34
|
+
const strapiServers = await loadFiles(extensionsDir, '**/strapi-server.js');
|
|
35
|
+
|
|
36
|
+
for (const pluginName in plugins) {
|
|
37
|
+
const plugin = plugins[pluginName];
|
|
38
|
+
// first: load json schema
|
|
39
|
+
for (const ctName in plugin.contentTypes) {
|
|
40
|
+
const extendedSchema = get([pluginName, 'content-types', ctName, 'schema'], extendedSchemas);
|
|
41
|
+
if (extendedSchema) {
|
|
42
|
+
plugin.contentTypes[ctName].schema = extendedSchema;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// second: execute strapi-server extension
|
|
46
|
+
const strapiServer = get([pluginName, 'strapi-server'], strapiServers);
|
|
47
|
+
if (strapiServer) {
|
|
48
|
+
plugins[pluginName] = await strapiServer(plugin);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const formatContentTypes = plugins => {
|
|
54
|
+
for (const pluginName in plugins) {
|
|
55
|
+
const plugin = plugins[pluginName];
|
|
56
|
+
for (const contentTypeName in plugin.contentTypes) {
|
|
57
|
+
const ctSchema = plugin.contentTypes[contentTypeName].schema;
|
|
58
|
+
ctSchema.plugin = pluginName;
|
|
59
|
+
ctSchema.uid = `plugin::${pluginName}.${ctSchema.singularName}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const applyUserConfig = plugins => {
|
|
65
|
+
const userPluginConfigPath = join(strapi.dirs.config, 'plugins.js');
|
|
66
|
+
const userPluginsConfig = existsSync(userPluginConfigPath)
|
|
67
|
+
? loadConfigFile(userPluginConfigPath)
|
|
68
|
+
: {};
|
|
69
|
+
|
|
70
|
+
for (const pluginName in plugins) {
|
|
71
|
+
const plugin = plugins[pluginName];
|
|
72
|
+
const userPluginConfig = getOr({}, `${pluginName}.config`, userPluginsConfig);
|
|
73
|
+
const defaultConfig =
|
|
74
|
+
typeof plugin.config.default === 'function'
|
|
75
|
+
? plugin.config.default({ env })
|
|
76
|
+
: plugin.config.default;
|
|
77
|
+
|
|
78
|
+
const config = defaultsDeep(defaultConfig, userPluginConfig);
|
|
79
|
+
try {
|
|
80
|
+
plugin.config.validator(config);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
throw new Error(`Error regarding ${pluginName} config: ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
plugin.config = config;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const loadPlugins = async strapi => {
|
|
89
|
+
const plugins = {};
|
|
90
|
+
|
|
91
|
+
const enabledPlugins = await getEnabledPlugins(strapi);
|
|
92
|
+
|
|
93
|
+
for (const pluginName in enabledPlugins) {
|
|
94
|
+
const enabledPlugin = enabledPlugins[pluginName];
|
|
95
|
+
const pluginServer = loadConfigFile(join(enabledPlugin.pathToPlugin, 'strapi-server.js'));
|
|
96
|
+
plugins[pluginName] = defaultsDeep(defaultPlugin, pluginServer);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// TODO: validate plugin format
|
|
100
|
+
applyUserConfig(plugins);
|
|
101
|
+
await applyUserExtension(plugins);
|
|
102
|
+
formatContentTypes(plugins);
|
|
103
|
+
|
|
104
|
+
for (const pluginName in plugins) {
|
|
105
|
+
strapi.container.get('plugins').add(pluginName, plugins[pluginName]);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
module.exports = loadPlugins;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { join, extname, basename } = require('path');
|
|
4
|
+
const fse = require('fs-extra');
|
|
5
|
+
|
|
6
|
+
// TODO:: allow folders with index.js inside for bigger policies
|
|
7
|
+
module.exports = async function loadPolicies(strapi) {
|
|
8
|
+
const dir = strapi.dirs.policies;
|
|
9
|
+
|
|
10
|
+
if (!(await fse.pathExists(dir))) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const policies = {};
|
|
15
|
+
const paths = await fse.readdir(dir, { withFileTypes: true });
|
|
16
|
+
|
|
17
|
+
for (const fd of paths) {
|
|
18
|
+
const { name } = fd;
|
|
19
|
+
const fullPath = join(dir, name);
|
|
20
|
+
|
|
21
|
+
if (fd.isFile() && extname(name) === '.js') {
|
|
22
|
+
const key = basename(name, '.js');
|
|
23
|
+
policies[key] = require(fullPath);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
strapi.container.get('policies').add(`global::`, policies);
|
|
28
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { resolve } = require('path');
|
|
4
|
+
const { statSync, existsSync } = require('fs');
|
|
5
|
+
const { yup } = require('@strapi/utils');
|
|
6
|
+
|
|
7
|
+
const srcSchema = yup
|
|
8
|
+
.object()
|
|
9
|
+
.shape({
|
|
10
|
+
bootstrap: yup.mixed().isFunction(),
|
|
11
|
+
register: yup.mixed().isFunction(),
|
|
12
|
+
})
|
|
13
|
+
.noUnknown();
|
|
14
|
+
|
|
15
|
+
const validateSrcIndex = srcIndex => {
|
|
16
|
+
return srcSchema.validateSync(srcIndex, { strict: true, abortEarly: false });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = strapi => {
|
|
20
|
+
if (!existsSync(strapi.dirs.src)) {
|
|
21
|
+
throw new Error('Missing src folder. Please create one at `./src`');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const pathToSrcIndex = resolve(strapi.dirs.src, 'index.js');
|
|
25
|
+
if (!existsSync(pathToSrcIndex) || statSync(pathToSrcIndex).isDirectory()) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const srcIndex = require(pathToSrcIndex);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
validateSrcIndex(srcIndex);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
strapi.stopWithError({ message: `Invalid file \`./src/index.js\`: ${e.message}` });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return srcIndex;
|
|
38
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { has } = require('lodash/fp');
|
|
4
|
+
const { createCoreApi } = require('../../core-api');
|
|
5
|
+
|
|
6
|
+
const apisRegistry = strapi => {
|
|
7
|
+
const apis = {};
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
get(name) {
|
|
11
|
+
return apis[name];
|
|
12
|
+
},
|
|
13
|
+
getAll() {
|
|
14
|
+
return apis;
|
|
15
|
+
},
|
|
16
|
+
add(apiName, apiConfig) {
|
|
17
|
+
if (has(apiName, apis)) {
|
|
18
|
+
throw new Error(`API ${apiName} has already been registered.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const apiInstance = strapi.container.get('modules').add(`api::${apiName}`, apiConfig);
|
|
22
|
+
|
|
23
|
+
for (const ctName in apiInstance.contentTypes || {}) {
|
|
24
|
+
const contentType = apiInstance.contentTypes[ctName];
|
|
25
|
+
|
|
26
|
+
const { service, controller } = createCoreApi({
|
|
27
|
+
model: contentType,
|
|
28
|
+
api: apiInstance,
|
|
29
|
+
strapi,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
strapi.container.get('services').set(`api::${apiName}.${ctName}`, service);
|
|
33
|
+
strapi.container.get('controllers').set(`api::${apiName}.${ctName}`, controller);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
apis[apiName] = apiInstance;
|
|
37
|
+
|
|
38
|
+
return apis[apiName];
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
module.exports = apisRegistry;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
module.exports = (initialConfig = {}) => {
|
|
6
|
+
const _config = Object.assign({}, initialConfig); // not deep clone because it would break some config
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
..._config, // TODO: to remove
|
|
10
|
+
get(path, defaultValue) {
|
|
11
|
+
return _.get(_config, path, defaultValue);
|
|
12
|
+
},
|
|
13
|
+
set(path, val) {
|
|
14
|
+
_.set(_config, path, val);
|
|
15
|
+
return this;
|
|
16
|
+
},
|
|
17
|
+
has(path) {
|
|
18
|
+
return _.has(_config, path);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { pickBy, has } = require('lodash/fp');
|
|
4
|
+
const { createContentType } = require('../domain/content-type');
|
|
5
|
+
const { addNamespace, hasNamespace } = require('../utils');
|
|
6
|
+
|
|
7
|
+
const validateKeySameToSingularName = contentTypes => {
|
|
8
|
+
for (const ctName in contentTypes) {
|
|
9
|
+
const contentType = contentTypes[ctName];
|
|
10
|
+
|
|
11
|
+
if (ctName !== contentType.schema.info.singularName) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`The key of the content-type should be the same as its singularName. Found ${ctName} and ${contentType.schema.info.singularName}.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const contentTypesRegistry = () => {
|
|
20
|
+
const contentTypes = {};
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
get(ctUID) {
|
|
24
|
+
return contentTypes[ctUID];
|
|
25
|
+
},
|
|
26
|
+
getAll(namespace) {
|
|
27
|
+
return pickBy((_, uid) => hasNamespace(uid, namespace))(contentTypes);
|
|
28
|
+
},
|
|
29
|
+
add(namespace, rawContentTypes) {
|
|
30
|
+
validateKeySameToSingularName(rawContentTypes);
|
|
31
|
+
|
|
32
|
+
for (const rawCtName in rawContentTypes) {
|
|
33
|
+
const uid = addNamespace(rawCtName, namespace);
|
|
34
|
+
|
|
35
|
+
if (has(uid, contentTypes)) {
|
|
36
|
+
throw new Error(`Content-type ${uid} has already been registered.`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
contentTypes[uid] = createContentType(uid, rawContentTypes[rawCtName]);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
extend(ctUID, extendFn) {
|
|
43
|
+
const currentContentType = this.get(ctUID);
|
|
44
|
+
if (!currentContentType) {
|
|
45
|
+
throw new Error(`Content-Type ${ctUID} doesn't exist`);
|
|
46
|
+
}
|
|
47
|
+
const newContentType = extendFn(currentContentType);
|
|
48
|
+
contentTypes[ctUID] = newContentType;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
module.exports = contentTypesRegistry;
|