@strapi/strapi 4.13.5 → 4.13.7

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 CHANGED
@@ -1,23 +1,15 @@
1
1
  <p align="center">
2
- <a href="https://strapi.io">
3
- <picture>
4
- <source
5
- srcset="https://strapi.io/assets/strapi-logo-dark.svg"
6
- media="(prefers-color-scheme: dark)"
7
- width="318px"
8
- />
9
- <img
10
- src="https://strapi.io/assets/strapi-logo-light.svg"
11
- alt="Strapi logo"
12
- width="318px"
13
- />
14
- </picture>
2
+ <a href="https://strapi.io/#gh-light-mode-only">
3
+ <img src="https://strapi.io/assets/strapi-logo-dark.svg" width="318px" alt="Strapi logo" />
4
+ </a>
5
+ <a href="https://strapi.io/#gh-dark-mode-only">
6
+ <img src="https://strapi.io/assets/strapi-logo-light.svg" width="318px" alt="Strapi logo" />
15
7
  </a>
16
8
  </p>
17
9
 
18
- <h3 align="center">API creation made simple, secure and fast.</h3>
19
- <p align="center">The most advanced open-source headless CMS to build powerful APIs with no effort.</p>
20
- <p align="center"><a href="https://strapi.io/demo">Try live demo</a></p>
10
+ <h3 align="center">Open-source headless CMS, self-hosted or Cloud you’re in control.</h3>
11
+ <p align="center">The leading open-source headless CMS, 100% JavaScript/TypeScript, flexible and fully customizable.</p>
12
+ <p align="center"><a href="https://cloud.strapi.io/signups?source=github1">Cloud</a> · <a href="https://strapi.io/demo">Try live demo</a></p>
21
13
  <br />
22
14
 
23
15
  <p align="center">
@@ -39,18 +31,22 @@
39
31
 
40
32
  <p align="center">
41
33
  <a href="https://strapi.io">
42
- <img src="https://raw.githubusercontent.com/strapi/strapi/0bcebf77b37182fe021cb59cc19be8f5db4a18ac/public/assets/administration_panel.png" alt="Administration panel" />
34
+ <img src="https://raw.githubusercontent.com/strapi/strapi/main/public/assets/admin-demo.gif" alt="Administration panel" />
43
35
  </a>
44
36
  </p>
45
37
 
46
38
  <br>
47
39
 
48
- Strapi is a free and open-source headless CMS delivering your content anywhere you need.
40
+ Strapi Community Edition is a free and open-source headless CMS enabling you to manage any content, anywhere.
49
41
 
50
- - **Keep control over your data**. With Strapi, you know where your data is stored, and you keep full control at all times.
51
- - **Self-hosted**. You can host and scale Strapi projects the way you want. You can choose any hosting platform you want: AWS, Render, Netlify, Heroku, a VPS, or a dedicated server. You can scale as you grow, 100% independent.
52
- - **Database agnostic**. Strapi works with SQL databases. You can choose the database you prefer: PostgreSQL, MySQL, MariaDB, and SQLite.
53
- - **Customizable**. You can quickly build your logic by fully customizing APIs, routes, or plugins to fit your needs perfectly.
42
+ - **Self-hosted or Cloud**: You can host and scale Strapi projects the way you want. You can save time by deploying to [Strapi Cloud](https://cloud.strapi.io/signups?source=github1) or deploy to the hosting platform you want**: AWS, Azure, Google Cloud, DigitalOcean.
43
+ - **Modern Admin Pane**: Elegant, entirely customizable and a fully extensible admin panel.
44
+ - **Multi-database support**: You can choose the database you prefer: PostgreSQL, MySQL, MariaDB, and SQLite.
45
+ - **Customizable**: You can quickly build your logic by fully customizing APIs, routes, or plugins to fit your needs perfectly.
46
+ - **Blazing Fast and Robust**: Built on top of Node.js and TypeScript, Strapi delivers reliable and solid performance.
47
+ - **Front-end Agnostic**: Use any front-end framework (React, Next.js, Vue, Angular, etc.), mobile apps or even IoT.
48
+ - **Secure by default**: Reusable policies, CORS, CSP, P3P, Xframe, XSS, and more.
49
+ - **Powerful CLI**: Scaffold projects and APIs on the fly.
54
50
 
55
51
  ## Getting Started
56
52
 
@@ -115,13 +111,13 @@ Strapi only supports maintenance and LTS versions of Node.js. Please refer to th
115
111
 
116
112
  ## Features
117
113
 
118
- - **Modern Admin Panel:** Elegant, entirely customizable and a fully extensible admin panel.
119
- - **Secure by default:** Reusable policies, CORS, CSP, P3P, Xframe, XSS, and more.
120
- - **Plugins Oriented:** Install the auth system, content management, custom plugins, and more, in seconds.
121
- - **Blazing Fast:** Built on top of Node.js, Strapi delivers amazing performance.
122
- - **Front-end Agnostic:** Use any front-end framework (React, Vue, Angular, etc.), mobile apps or even IoT.
123
- - **Powerful CLI:** Scaffold projects and APIs on the fly.
124
- - **SQL databases:** Works with PostgreSQL, MySQL, MariaDB, and SQLite.
114
+ - **Content Types Builder**: Build the most flexible publishing experience for your content managers, by giving them the freedom to create any page on the go with [fields](https://docs.strapi.io/user-docs/content-manager/writing-content#filling-up-fields), components and [Dynamic Zones](https://docs.strapi.io/user-docs/content-manager/writing-content#dynamic-zones).
115
+ - **Media Library**: Upload your images, videos, audio or documents to the media library. Easily find the right asset, edit and reuse it.
116
+ - **Internationalization**: The Internationalization (i18n) plugin allows Strapi users to create, manage and distribute localized content in different languages, called "locales
117
+ - **Role Based Access Control**: Create an unlimited number of custom roles and permissions for admin and end users.
118
+ - **GraphQL or REST**: Consume the API using REST or GraphQL
119
+
120
+ You can unlock additional features such as SSO, Audit Logs, Review Workflows in [Strapi Cloud](https://cloud.strapi.io/login?source=github1) or [Strapi Enterprise](https://strapi.io/enterprise?source=github1).
125
121
 
126
122
  **[See more on our website](https://strapi.io/overview)**.
127
123
 
@@ -48,7 +48,7 @@ module.exports = async ({ build, watchAdmin, polling, browser }) => {
48
48
 
49
49
  const primaryProcess = async ({ distDir, appDir, build, isTSProject, watchAdmin, browser }) => {
50
50
  if (isTSProject) {
51
- await buildTypeScript({ srcDir: appDir, distDir, watch: false });
51
+ await buildTypeScript({ srcDir: appDir, distDir, ignoreDiagnostics: true });
52
52
  }
53
53
 
54
54
  const config = loadConfiguration({ appDir, distDir });
@@ -84,7 +84,7 @@ const primaryProcess = async ({ distDir, appDir, build, isTSProject, watchAdmin,
84
84
  switch (message) {
85
85
  case 'reload':
86
86
  if (isTSProject) {
87
- await buildTypeScript({ srcDir: appDir, distDir, watch: false });
87
+ await buildTypeScript({ srcDir: appDir, distDir, ignoreDiagnostics: true });
88
88
  }
89
89
 
90
90
  console.info('The server is restarting\n');
@@ -113,21 +113,16 @@ const workerProcess = async ({ appDir, distDir, watchAdmin, polling, isTSProject
113
113
  serveAdminPanel: !watchAdmin,
114
114
  }).load();
115
115
 
116
- /**
117
- * TypeScript automatic type generation upon dev server restart
118
- * Its implementation, configuration and behavior can change in future releases
119
- * @experimental
120
- */
121
- const shouldGenerateTypeScriptTypes = strapiInstance.config.get('typescript.autogenerate', false);
122
-
123
- if (shouldGenerateTypeScriptTypes) {
124
- await tsUtils.generators.generate({
125
- strapi: strapiInstance,
126
- pwd: appDir,
127
- rootDir: undefined,
128
- logger: { silent: true, debug: false },
129
- artifacts: { contentTypes: true, components: true },
130
- });
116
+ await tsUtils.generators.generate({
117
+ strapi: strapiInstance,
118
+ pwd: appDir,
119
+ rootDir: undefined,
120
+ logger: { silent: true, debug: false },
121
+ artifacts: { contentTypes: true, components: true },
122
+ });
123
+
124
+ if (isTSProject) {
125
+ await buildTypeScript({ srcDir: appDir, distDir, ignoreDiagnostics: false });
131
126
  }
132
127
 
133
128
  const adminWatchIgnoreFiles = strapiInstance.config.get('admin.watchIgnoreFiles', []);
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs/promises');
4
+ const boxen = require('boxen');
5
+ const chalk = require('chalk');
6
+ const ora = require('ora');
7
+ const { createLogger } = require('../../../utils/logger');
8
+ const { notifyExperimentalCommand } = require('../../../utils/helpers');
9
+ const {
10
+ loadPkg,
11
+ validatePkg,
12
+ validateExportsOrdering,
13
+ getExportExtensionMap,
14
+ } = require('../../../utils/pkg');
15
+ const { createBuildContext, createBuildTasks } = require('../../../builders/packages');
16
+ const { buildTaskHandlers } = require('../../../builders/tasks');
17
+
18
+ /**
19
+ *
20
+ * @param {object} args
21
+ * @param {boolean} args.force
22
+ * @param {boolean} args.debug
23
+ */
24
+ module.exports = async ({ force, debug }) => {
25
+ const logger = createLogger({ debug, timestamp: false });
26
+ try {
27
+ /**
28
+ * Notify users this is an experimental command and get them to approve first
29
+ * this can be opted out by setting the argument --yes
30
+ */
31
+ await notifyExperimentalCommand({ force });
32
+
33
+ const cwd = process.cwd();
34
+
35
+ /**
36
+ * Load the closest package.json and then verify the structure against what we expect.
37
+ */
38
+ const packageJsonLoader = ora('Verifying package.json \n').start();
39
+
40
+ const rawPkg = await loadPkg({ cwd, logger }).catch((err) => {
41
+ packageJsonLoader.fail();
42
+ logger.error(err.message);
43
+ logger.debug(`Path checked – ${cwd}`);
44
+ process.exit(1);
45
+ });
46
+
47
+ const validatedPkg = await validatePkg({
48
+ pkg: rawPkg,
49
+ }).catch((err) => {
50
+ packageJsonLoader.fail();
51
+ logger.error(err.message);
52
+ process.exit(1);
53
+ });
54
+
55
+ /**
56
+ * Validate the exports of the package incl. the order of the
57
+ * exports within the exports map if applicable
58
+ */
59
+ const packageJson = await validateExportsOrdering({ pkg: validatedPkg, logger }).catch(
60
+ (err) => {
61
+ packageJsonLoader.fail();
62
+ logger.error(err.message);
63
+ process.exit(1);
64
+ }
65
+ );
66
+
67
+ packageJsonLoader.succeed('Verified package.json');
68
+
69
+ /**
70
+ * We create tasks based on the exports of the package.json
71
+ * their handlers are then ran in the order of the exports map
72
+ * and results are logged to see gradual progress.
73
+ */
74
+
75
+ const buildContextLoader = ora('Creating build context \n').start();
76
+
77
+ const extMap = getExportExtensionMap();
78
+
79
+ const ctx = await createBuildContext({
80
+ cwd,
81
+ extMap,
82
+ logger,
83
+ pkg: packageJson,
84
+ }).catch((err) => {
85
+ buildContextLoader.fail();
86
+ logger.error(err.message);
87
+ process.exit(1);
88
+ });
89
+
90
+ logger.debug('Build context: \n', ctx);
91
+
92
+ const buildTasks = await createBuildTasks(ctx);
93
+
94
+ buildContextLoader.succeed('Created build context');
95
+
96
+ /**
97
+ * If the distPath already exists, clean it
98
+ */
99
+ try {
100
+ logger.debug(`Cleaning dist folder: ${ctx.distPath}`);
101
+ await fs.rm(ctx.distPath, { recursive: true, force: true });
102
+ logger.debug('Cleaned dist folder');
103
+ } catch {
104
+ // do nothing, it will fail if the folder does not exist
105
+ logger.debug('There was no dist folder to clean');
106
+ }
107
+
108
+ for (const task of buildTasks) {
109
+ /**
110
+ * @type {import('../../../builders/tasks').TaskHandler<any>}
111
+ */
112
+ const handler = buildTaskHandlers[task.type];
113
+ handler.print(ctx, task);
114
+
115
+ await handler.run(ctx, task).catch((err) => {
116
+ if (err instanceof Error) {
117
+ logger.error(err.message);
118
+ }
119
+
120
+ process.exit(1);
121
+ });
122
+ }
123
+ } catch (err) {
124
+ logger.error(
125
+ 'There seems to be an unexpected error, try again with --debug for more information \n'
126
+ );
127
+ console.log(
128
+ chalk.red(
129
+ boxen(err.stack, {
130
+ padding: 1,
131
+ align: 'left',
132
+ })
133
+ )
134
+ );
135
+ process.exit(1);
136
+ }
137
+ };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ const { forceOption } = require('../../../utils/commander');
4
+ const { getLocalScript } = require('../../../utils/helpers');
5
+
6
+ /**
7
+ * `$ strapi plugin:build`
8
+ * @param {import('../../../../types/core/commands').AddCommandOptions} options
9
+ */
10
+ module.exports = ({ command }) => {
11
+ command
12
+ .command('plugin:build')
13
+ .description('Bundle your strapi plugin for publishing.')
14
+ .addOption(forceOption)
15
+ .option('-d, --debug', 'Enable debugging mode with verbose logs', false)
16
+ .action(getLocalScript('plugin/build-command'));
17
+ };
@@ -10,7 +10,7 @@ module.exports = async ({ debug, silent, verbose, outDir }) => {
10
10
  process.exit(1);
11
11
  }
12
12
 
13
- const appContext = await strapi.compile();
13
+ const appContext = await strapi.compile({ ignoreDiagnostics: true });
14
14
  const app = await strapi(appContext).register();
15
15
 
16
16
  await tsUtils.generators.generate({
@@ -25,5 +25,5 @@ module.exports = async ({ debug, silent, verbose, outDir }) => {
25
25
  artifacts: { contentTypes: true, components: true },
26
26
  });
27
27
 
28
- app.destroy();
28
+ await app.destroy();
29
29
  };
@@ -0,0 +1,252 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const browserslistToEsbuild = require('browserslist-to-esbuild');
5
+
6
+ const { parseExports } = require('../utils/pkg');
7
+
8
+ /**
9
+ * @typedef {Object} BuildContextArgs
10
+ * @property {string} cwd
11
+ * @property {import('../utils/pkg').ExtMap} extMap
12
+ * @property {import('../utils/logger').Logger} logger
13
+ * @property {import('../utils/pkg').PackageJson} pkg
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} Targets
18
+ * @property {string[]} node
19
+ * @property {string[]} web
20
+ * @property {string[]} *
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} BuildContext
25
+ * @property {string} cwd
26
+ * @property {import('../utils/pkg').Export[]} exports
27
+ * @property {string[]} external
28
+ * @property {import('../utils/pkg').ExtMap} extMap
29
+ * @property {import('../utils/logger').Logger} logger
30
+ * @property {import('../utils/pkg').PackageJson} pkg
31
+ * @property {Targets} targets
32
+ */
33
+
34
+ const DEFAULT_BROWSERS_LIST_CONFIG = [
35
+ 'last 3 major versions',
36
+ 'Firefox ESR',
37
+ 'last 2 Opera versions',
38
+ 'not dead',
39
+ 'node 16.0.0',
40
+ ];
41
+
42
+ /**
43
+ * @description Create a build context for the pipeline we're creating,
44
+ * this is shared among tasks so they all use the same settings for core pieces
45
+ * such as a target, distPath, externals etc.
46
+ *
47
+ * @type {(args: BuildContextArgs) => Promise<BuildContext>}
48
+ */
49
+ const createBuildContext = async ({ cwd, extMap, logger, pkg }) => {
50
+ const targets = {
51
+ '*': browserslistToEsbuild(pkg.browserslist ?? DEFAULT_BROWSERS_LIST_CONFIG),
52
+ node: browserslistToEsbuild(['node 16.0.0']),
53
+ web: ['esnext'],
54
+ };
55
+
56
+ const exports = parseExports({ extMap, pkg }).reduce((acc, x) => {
57
+ const { _path: exportPath, ...exportEntry } = x;
58
+
59
+ return { ...acc, [exportPath]: exportEntry };
60
+ }, {});
61
+
62
+ const external = [
63
+ ...(pkg.dependencies ? Object.keys(pkg.dependencies) : []),
64
+ ...(pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : []),
65
+ ];
66
+
67
+ const outputPaths = Object.values(exports)
68
+ .flatMap((exportEntry) => {
69
+ return [exportEntry.import, exportEntry.require].filter(Boolean);
70
+ })
71
+ .map((p) => path.resolve(cwd, p));
72
+
73
+ const distPath = findCommonDirPath(outputPaths);
74
+
75
+ if (distPath === cwd) {
76
+ throw new Error(
77
+ 'all output files must share a common parent directory which is not the root package directory'
78
+ );
79
+ }
80
+
81
+ if (!distPath) {
82
+ throw new Error("could not detect 'dist' path");
83
+ }
84
+
85
+ return {
86
+ logger,
87
+ cwd,
88
+ pkg,
89
+ exports,
90
+ external,
91
+ distPath,
92
+ targets,
93
+ extMap,
94
+ };
95
+ };
96
+
97
+ /**
98
+ * @type {(containerPath: string, itemPath: string) => boolean}
99
+ */
100
+ const pathContains = (containerPath, itemPath) => {
101
+ return !path.relative(containerPath, itemPath).startsWith('..');
102
+ };
103
+
104
+ /**
105
+ * @type {(filePaths: string[]) => string | undefined}
106
+ */
107
+ const findCommonDirPath = (filePaths) => {
108
+ /**
109
+ * @type {string | undefined}
110
+ */
111
+ let commonPath;
112
+
113
+ for (const filePath of filePaths) {
114
+ let dirPath = path.dirname(filePath);
115
+
116
+ if (!commonPath) {
117
+ commonPath = dirPath;
118
+ // eslint-disable-next-line no-continue
119
+ continue;
120
+ }
121
+
122
+ while (dirPath !== commonPath) {
123
+ dirPath = path.dirname(dirPath);
124
+
125
+ if (dirPath === commonPath) {
126
+ break;
127
+ }
128
+
129
+ if (pathContains(dirPath, commonPath)) {
130
+ commonPath = dirPath;
131
+ break;
132
+ }
133
+
134
+ if (dirPath === '.') return undefined;
135
+ }
136
+ }
137
+
138
+ return commonPath;
139
+ };
140
+
141
+ /**
142
+ * @typedef {import('./tasks/vite').ViteTask | import('./tasks/dts').DtsTask} BuildTask
143
+ */
144
+
145
+ /**
146
+ * @description Create the build tasks for the pipeline, this
147
+ * comes from the exports map we've created in the build context.
148
+ * But handles each export line uniquely with space to add more
149
+ * as the standard develops.
150
+ *
151
+ * @type {(args: BuildContext) => Promise<BuildTask[]>}
152
+ */
153
+ const createBuildTasks = async (ctx) => {
154
+ /**
155
+ * @type {BuildTask[]}
156
+ */
157
+ const tasks = [];
158
+
159
+ /**
160
+ * @type {import('./tasks/dts').DtsTask}
161
+ */
162
+ const dtsTask = {
163
+ type: 'build:dts',
164
+ entries: [],
165
+ };
166
+
167
+ /**
168
+ * @type {Record<string, import('./tasks/vite').ViteTask>}
169
+ */
170
+ const viteTasks = {};
171
+
172
+ const createViteTask = (format, runtime, { output, ...restEntry }) => {
173
+ const buildId = `${format}:${output}`;
174
+
175
+ if (viteTasks[buildId]) {
176
+ viteTasks[buildId].entries.push(restEntry);
177
+
178
+ if (output !== viteTasks[buildId].output) {
179
+ ctx.logger.warn(
180
+ 'Multiple entries with different outputs for the same format are not supported. The first output will be used.'
181
+ );
182
+ }
183
+ } else {
184
+ viteTasks[buildId] = {
185
+ type: 'build:js',
186
+ format,
187
+ output,
188
+ runtime,
189
+ entries: [restEntry],
190
+ };
191
+ }
192
+ };
193
+
194
+ const exps = Object.entries(ctx.exports).map(([exportPath, exportEntry]) => ({
195
+ ...exportEntry,
196
+ _path: exportPath,
197
+ }));
198
+
199
+ for (const exp of exps) {
200
+ if (exp.types) {
201
+ const importId = path.join(ctx.pkg.name, exp._path);
202
+
203
+ dtsTask.entries.push({
204
+ importId,
205
+ exportPath: exp._path,
206
+ sourcePath: exp.source,
207
+ targetPath: exp.types,
208
+ });
209
+ }
210
+
211
+ /**
212
+ * @type {keyof Target}
213
+ */
214
+ // eslint-disable-next-line no-nested-ternary
215
+ const runtime = exp._path.includes('strapi-admin')
216
+ ? 'web'
217
+ : exp._path.includes('strapi-server')
218
+ ? 'node'
219
+ : '*';
220
+
221
+ if (exp.require) {
222
+ /**
223
+ * register CJS task
224
+ */
225
+ createViteTask('cjs', runtime, {
226
+ path: exp._path,
227
+ entry: exp.source,
228
+ output: exp.require,
229
+ });
230
+ }
231
+
232
+ if (exp.import) {
233
+ /**
234
+ * register ESM task
235
+ */
236
+ createViteTask('es', runtime, {
237
+ path: exp._path,
238
+ entry: exp.source,
239
+ output: exp.import,
240
+ });
241
+ }
242
+ }
243
+
244
+ tasks.push(dtsTask, ...Object.values(viteTasks));
245
+
246
+ return tasks;
247
+ };
248
+
249
+ module.exports = {
250
+ createBuildContext,
251
+ createBuildTasks,
252
+ };