@strapi/strapi 4.13.2 → 4.14.0-alpha.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.
@@ -0,0 +1,421 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs/promises');
4
+ const path = require('path');
5
+ const chalk = require('chalk');
6
+ const yup = require('yup');
7
+
8
+ /**
9
+ * Utility functions for loading and validating package.json
10
+ * this includes the specific validation of specific parts of
11
+ * the package.json.
12
+ */
13
+
14
+ /**
15
+ * The schema for the package.json that we expect,
16
+ * currently pretty loose.
17
+ */
18
+ const packageJsonSchema = yup.object({
19
+ name: yup.string().required(),
20
+ version: yup.string().required(),
21
+ type: yup.string().matches(/(commonjs|module)/),
22
+ license: yup.string(),
23
+ bin: yup.mixed().oneOf([
24
+ yup.string(),
25
+ yup.object({
26
+ [yup.string()]: yup.string(),
27
+ }),
28
+ ]),
29
+ main: yup.string(),
30
+ module: yup.string(),
31
+ source: yup.string(),
32
+ types: yup.string(),
33
+ exports: yup.lazy((value) =>
34
+ yup.object(
35
+ typeof value === 'object'
36
+ ? Object.entries(value).reduce((acc, [key, value]) => {
37
+ if (typeof value === 'object') {
38
+ acc[key] = yup
39
+ .object({
40
+ types: yup.string(),
41
+ source: yup.string(),
42
+ import: yup.string(),
43
+ require: yup.string(),
44
+ default: yup.string(),
45
+ })
46
+ .noUnknown(true);
47
+ } else {
48
+ acc[key] = yup
49
+ .string()
50
+ .matches(/^\.\/.*\.json$/)
51
+ .required();
52
+ }
53
+
54
+ return acc;
55
+ }, {})
56
+ : undefined
57
+ )
58
+ ),
59
+ files: yup.array(yup.string()),
60
+ scripts: yup.object(),
61
+ dependencies: yup.object(),
62
+ devDependencies: yup.object(),
63
+ peerDependencies: yup.object(),
64
+ engines: yup.object(),
65
+ });
66
+
67
+ /**
68
+ * @typedef {import('yup').Asserts<typeof packageJsonSchema>} PackageJson
69
+ */
70
+
71
+ /**
72
+ * @description being a task to load the package.json starting from the current working directory
73
+ * using a shallow find for the package.json and `fs` to read the file. If no package.json is found,
74
+ * the process will throw with an appropriate error message.
75
+ *
76
+ * @type {(args: { cwd: string, logger: import('./logger').Logger }) => Promise<object>}
77
+ */
78
+ const loadPkg = async ({ cwd, logger }) => {
79
+ const directory = path.resolve(cwd);
80
+
81
+ const pkgPath = path.join(directory, 'package.json');
82
+
83
+ const buffer = await fs.readFile(pkgPath).catch((err) => {
84
+ logger.debug(err);
85
+ throw new Error('Could not find a package.json in the current directory');
86
+ });
87
+
88
+ const pkg = JSON.parse(buffer.toString());
89
+
90
+ logger.debug('Loaded package.json: \n', pkg);
91
+
92
+ return pkg;
93
+ };
94
+
95
+ /**
96
+ * @description validate the package.json against a standardised schema using `yup`.
97
+ * If the validation fails, the process will throw with an appropriate error message.
98
+ *
99
+ * @type {(args: { pkg: object }) => Promise<PackageJson | null>}
100
+ */
101
+ const validatePkg = async ({ pkg }) => {
102
+ try {
103
+ const validatedPkg = await packageJsonSchema.validate(pkg, {
104
+ strict: true,
105
+ });
106
+
107
+ return validatedPkg;
108
+ } catch (err) {
109
+ if (err instanceof yup.ValidationError) {
110
+ switch (err.type) {
111
+ case 'required':
112
+ throw new Error(
113
+ `'${err.path}' in 'package.json' is required as type '${chalk.magenta(
114
+ yup.reach(packageJsonSchema, err.path).type
115
+ )}'`
116
+ );
117
+ case 'matches':
118
+ throw new Error(
119
+ `'${err.path}' in 'package.json' must be of type '${chalk.magenta(
120
+ err.params.regex
121
+ )}' (recieved the value '${chalk.magenta(err.params.value)}')`
122
+ );
123
+ /**
124
+ * This will only be thrown if there are keys in the export map
125
+ * that we don't expect so we can therefore make some assumptions
126
+ */
127
+ case 'noUnknown':
128
+ throw new Error(
129
+ `'${err.path}' in 'package.json' contains the unknown key ${chalk.magenta(
130
+ err.params.unknown
131
+ )}, for compatability only the following keys are allowed: ${chalk.magenta(
132
+ "['types', 'source', 'import', 'require', 'default']"
133
+ )}`
134
+ );
135
+ default:
136
+ throw new Error(
137
+ `'${err.path}' in 'package.json' must be of type '${chalk.magenta(
138
+ err.params.type
139
+ )}' (recieved '${chalk.magenta(typeof err.params.value)}')`
140
+ );
141
+ }
142
+ }
143
+
144
+ throw err;
145
+ }
146
+ };
147
+
148
+ /**
149
+ * @description validate the `exports` property of the package.json against a set of rules.
150
+ * If the validation fails, the process will throw with an appropriate error message. If
151
+ * there is no `exports` property we check the standard export-like properties on the root
152
+ * of the package.json.
153
+ *
154
+ * @type {(args: { pkg: object, logger: import('./logger').Logger }) => Promise<PackageJson>}
155
+ */
156
+ const validateExportsOrdering = async ({ pkg, logger }) => {
157
+ if (pkg.exports) {
158
+ const exports = Object.entries(pkg.exports);
159
+
160
+ for (const [expPath, exp] of exports) {
161
+ if (typeof exp === 'string') {
162
+ // eslint-disable-next-line no-continue
163
+ continue;
164
+ }
165
+
166
+ const keys = Object.keys(exp);
167
+
168
+ if (!assertFirst('types', keys)) {
169
+ throw new Error(`exports["${expPath}"]: the 'types' property should be the first property`);
170
+ }
171
+
172
+ if (!assertOrder('import', 'require', keys)) {
173
+ logger.warn(
174
+ `exports["${expPath}"]: the 'import' property should come before the 'require' property`
175
+ );
176
+ }
177
+
178
+ if (!assertOrder('module', 'import', keys)) {
179
+ logger.warn(
180
+ `exports["${expPath}"]: the 'module' property should come before 'import' property`
181
+ );
182
+ }
183
+
184
+ if (!assertLast('default', keys)) {
185
+ throw new Error(
186
+ `exports["${expPath}"]: the 'default' property should be the last property`
187
+ );
188
+ }
189
+ }
190
+ } else if (!['main', 'module'].some((key) => Object.prototype.hasOwnProperty.call(pkg, key))) {
191
+ throw new Error(`'package.json' must contain a 'main' and 'module' property`);
192
+ }
193
+
194
+ return pkg;
195
+ };
196
+
197
+ /** @internal */
198
+ function assertFirst(key, arr) {
199
+ const aIdx = arr.indexOf(key);
200
+
201
+ // if not found, then we don't care
202
+ if (aIdx === -1) {
203
+ return true;
204
+ }
205
+
206
+ return aIdx === 0;
207
+ }
208
+
209
+ /** @internal */
210
+ function assertLast(key, arr) {
211
+ const aIdx = arr.indexOf(key);
212
+
213
+ // if not found, then we don't care
214
+ if (aIdx === -1) {
215
+ return true;
216
+ }
217
+
218
+ return aIdx === arr.length - 1;
219
+ }
220
+
221
+ /** @internal */
222
+ function assertOrder(keyA, keyB, arr) {
223
+ const aIdx = arr.indexOf(keyA);
224
+ const bIdx = arr.indexOf(keyB);
225
+
226
+ // if either is not found, then we don't care
227
+ if (aIdx === -1 || bIdx === -1) {
228
+ return true;
229
+ }
230
+
231
+ return aIdx < bIdx;
232
+ }
233
+
234
+ /**
235
+ * @typedef {Object} Extensions
236
+ * @property {string} commonjs
237
+ * @property {string} esm
238
+ */
239
+
240
+ /**
241
+ * @typedef {Object} ExtMap
242
+ * @property {Extensions} commonjs
243
+ * @property {Extensions} esm
244
+ */
245
+
246
+ /**
247
+ * @internal
248
+ *
249
+ * @type {ExtMap}
250
+ */
251
+ const DEFAULT_PKG_EXT_MAP = {
252
+ // pkg.type: "commonjs"
253
+ commonjs: {
254
+ cjs: '.js',
255
+ es: '.mjs',
256
+ },
257
+
258
+ // pkg.type: "module"
259
+ module: {
260
+ cjs: '.cjs',
261
+ es: '.js',
262
+ },
263
+ };
264
+
265
+ /**
266
+ * We potentially might need to support legacy exports or as package
267
+ * development continues we have space to tweak this.
268
+ *
269
+ * @type {() => ExtMap}
270
+ */
271
+ const getExportExtensionMap = () => {
272
+ return DEFAULT_PKG_EXT_MAP;
273
+ };
274
+
275
+ /**
276
+ * @internal
277
+ *
278
+ * @description validate the `require` and `import` properties of a given exports maps from the package.json
279
+ * returning if any errors are found.
280
+ *
281
+ * @type {(_exports: unknown, options: {extMap: ExtMap; pkg: PackageJson}) => string[]}
282
+ */
283
+ const validateExports = (_exports, options) => {
284
+ const { extMap, pkg } = options;
285
+ const ext = extMap[pkg.type || 'commonjs'];
286
+
287
+ const errors = [];
288
+
289
+ for (const exp of _exports) {
290
+ if (exp.require && !exp.require.endsWith(ext.cjs)) {
291
+ errors.push(
292
+ `package.json with \`type: "${pkg.type}"\` - \`exports["${exp._path}"].require\` must end with "${ext.cjs}"`
293
+ );
294
+ }
295
+
296
+ if (exp.import && !exp.import.endsWith(ext.es)) {
297
+ errors.push(
298
+ `package.json with \`type: "${pkg.type}"\` - \`exports["${exp._path}"].import\` must end with "${ext.es}"`
299
+ );
300
+ }
301
+ }
302
+
303
+ return errors;
304
+ };
305
+
306
+ /**
307
+ * @typedef {Object} Export
308
+ * @property {string} _path the path of the export, `.` for the root.
309
+ * @property {string=} types the path to the types file
310
+ * @property {string} source the path to the source file
311
+ * @property {string=} require the path to the commonjs require file
312
+ * @property {string=} import the path to the esm import file
313
+ * @property {string=} default the path to the default file
314
+ */
315
+
316
+ /**
317
+ * @description parse the exports map from the package.json into a standardised
318
+ * format that we can use to generate build tasks from.
319
+ *
320
+ * @type {(args: { extMap: ExtMap, pkg: PackageJson }) => Export[]}
321
+ */
322
+ const parseExports = ({ extMap, pkg }) => {
323
+ /**
324
+ * @type {Export}
325
+ */
326
+ const rootExport = {
327
+ _path: '.',
328
+ types: pkg.types,
329
+ source: pkg.source,
330
+ require: pkg.main,
331
+ import: pkg.module,
332
+ default: pkg.module || pkg.main,
333
+ };
334
+
335
+ /**
336
+ * @type {Export[]}
337
+ */
338
+ const extraExports = [];
339
+
340
+ /**
341
+ * @type {string[]}
342
+ */
343
+ const errors = [];
344
+
345
+ if (pkg.exports) {
346
+ if (!pkg.exports['./package.json']) {
347
+ errors.push('package.json: `exports["./package.json"] must be declared.');
348
+ }
349
+
350
+ Object.entries(pkg.exports).forEach(([path, entry]) => {
351
+ if (path.endsWith('.json')) {
352
+ if (path === './package.json' && entry !== './package.json') {
353
+ errors.push(`package.json: 'exports["./package.json"]' must be './package.json'.`);
354
+ }
355
+ } else if (Boolean(entry) && typeof entry === 'object' && !Array.isArray(entry)) {
356
+ if (path === '.') {
357
+ if (entry.require && rootExport.require && entry.require !== rootExport.require) {
358
+ errors.push(
359
+ `package.json: mismatch between 'main' and 'exports.require'. These must be equal.`
360
+ );
361
+ }
362
+
363
+ if (entry.import && rootExport.import && entry.import !== rootExport.import) {
364
+ errors.push(
365
+ `package.json: mismatch between 'module' and 'exports.import' These must be equal.`
366
+ );
367
+ }
368
+
369
+ if (entry.types && rootExport.types && entry.types !== rootExport.types) {
370
+ errors.push(
371
+ `package.json: mismatch between 'types' and 'exports.types'. These must be equal.`
372
+ );
373
+ }
374
+
375
+ if (entry.source && rootExport.source && entry.source !== rootExport.source) {
376
+ errors.push(
377
+ `package.json: mismatch between 'source' and 'exports.source'. These must be equal.`
378
+ );
379
+ }
380
+
381
+ Object.assign(rootExport, entry);
382
+ } else {
383
+ const extraExport = {
384
+ _exported: true,
385
+ _path: path,
386
+ ...entry,
387
+ };
388
+
389
+ extraExports.push(extraExport);
390
+ }
391
+ } else {
392
+ errors.push('package.json: exports must be an object');
393
+ }
394
+ });
395
+ }
396
+
397
+ const _exports = [
398
+ /**
399
+ * In the case of strapi plugins, we don't have a root export because we
400
+ * ship a server side and client side package. So this can be completely omitted.
401
+ */
402
+ Object.values(rootExport).some((exp) => exp !== rootExport._path && Boolean(exp)) && rootExport,
403
+ ...extraExports,
404
+ ].filter(Boolean);
405
+
406
+ errors.push(...validateExports(_exports, { extMap, pkg }));
407
+
408
+ if (errors.length) {
409
+ throw new Error(`\n- ${errors.join('\n- ')}`);
410
+ }
411
+
412
+ return _exports;
413
+ };
414
+
415
+ module.exports = {
416
+ loadPkg,
417
+ validatePkg,
418
+ validateExportsOrdering,
419
+ getExportExtensionMap,
420
+ parseExports,
421
+ };
@@ -8,7 +8,7 @@ export type Timestamp = Attribute.OfType<'timestamp'> &
8
8
  Attribute.RequiredOption &
9
9
  Attribute.UniqueOption;
10
10
 
11
- export type TimestampValue = globalThis.Date | number | string;
11
+ export type TimestampValue = globalThis.Date | number | string;
12
12
 
13
13
  export type GetTimestampValue<T extends Attribute.Attribute> = T extends Timestamp
14
14
  ? TimestampValue
@@ -11,7 +11,7 @@ interface CustomFieldServerOptions {
11
11
  /**
12
12
  * The name of the plugin creating the custom field
13
13
  */
14
- plugin?: string;
14
+ pluginId?: string;
15
15
 
16
16
  /**
17
17
  * The existing Strapi data type the custom field uses
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/strapi",
3
- "version": "4.13.2",
3
+ "version": "4.14.0-alpha.0",
4
4
  "description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
5
5
  "keywords": [
6
6
  "strapi",
@@ -81,21 +81,23 @@
81
81
  "dependencies": {
82
82
  "@koa/cors": "3.4.3",
83
83
  "@koa/router": "10.1.1",
84
- "@strapi/admin": "4.13.2",
85
- "@strapi/data-transfer": "4.13.2",
86
- "@strapi/database": "4.13.2",
87
- "@strapi/generate-new": "4.13.2",
88
- "@strapi/generators": "4.13.2",
89
- "@strapi/logger": "4.13.2",
90
- "@strapi/permissions": "4.13.2",
91
- "@strapi/plugin-content-manager": "4.13.2",
92
- "@strapi/plugin-content-type-builder": "4.13.2",
93
- "@strapi/plugin-email": "4.13.2",
94
- "@strapi/plugin-upload": "4.13.2",
95
- "@strapi/typescript-utils": "4.13.2",
96
- "@strapi/utils": "4.13.2",
84
+ "@strapi/admin": "4.14.0-alpha.0",
85
+ "@strapi/data-transfer": "4.14.0-alpha.0",
86
+ "@strapi/database": "4.14.0-alpha.0",
87
+ "@strapi/generate-new": "4.14.0-alpha.0",
88
+ "@strapi/generators": "4.14.0-alpha.0",
89
+ "@strapi/logger": "4.14.0-alpha.0",
90
+ "@strapi/permissions": "4.14.0-alpha.0",
91
+ "@strapi/plugin-content-manager": "4.14.0-alpha.0",
92
+ "@strapi/plugin-content-type-builder": "4.14.0-alpha.0",
93
+ "@strapi/plugin-email": "4.14.0-alpha.0",
94
+ "@strapi/plugin-upload": "4.14.0-alpha.0",
95
+ "@strapi/typescript-utils": "4.14.0-alpha.0",
96
+ "@strapi/utils": "4.14.0-alpha.0",
97
+ "@vitejs/plugin-react": "4.0.4",
97
98
  "bcryptjs": "2.4.3",
98
99
  "boxen": "5.1.2",
100
+ "browserslist-to-esbuild": "1.2.0",
99
101
  "chalk": "4.1.2",
100
102
  "chokidar": "3.5.3",
101
103
  "ci-info": "3.8.0",
@@ -132,7 +134,10 @@
132
134
  "qs": "6.11.1",
133
135
  "resolve-cwd": "3.0.0",
134
136
  "semver": "7.5.4",
135
- "statuses": "2.0.1"
137
+ "statuses": "2.0.1",
138
+ "typescript": "5.1.3",
139
+ "vite": "4.4.9",
140
+ "yup": "0.32.9"
136
141
  },
137
142
  "devDependencies": {
138
143
  "supertest": "6.3.3",
@@ -143,5 +148,5 @@
143
148
  "node": ">=16.0.0 <=20.x.x",
144
149
  "npm": ">=6.0.0"
145
150
  },
146
- "gitHead": "989fc49fa863563de26161544d21716c9ee805b5"
151
+ "gitHead": "1c3c286e2481cd8cbbb50e0e0bb8fbd0b824d2e4"
147
152
  }