@strapi/strapi 4.2.0-beta.1 → 4.2.0-beta.4

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/bin/strapi.js CHANGED
@@ -95,6 +95,7 @@ program
95
95
  .option('--dbssl <dbssl>', 'Database SSL')
96
96
  .option('--dbfile <dbfile>', 'Database file path for sqlite')
97
97
  .option('--dbforce', 'Allow overwriting existing database content')
98
+ .option('-ts, --typescript', 'Create a typescript project')
98
99
  .description('Create a new application')
99
100
  .action(require('../lib/commands/new'));
100
101
 
package/lib/Strapi.js CHANGED
@@ -38,10 +38,15 @@ const apisRegistry = require('./core/registries/apis');
38
38
  const bootstrap = require('./core/bootstrap');
39
39
  const loaders = require('./core/loaders');
40
40
  const { destroyOnSignal } = require('./utils/signals');
41
+ const sanitizersRegistry = require('./core/registries/sanitizers');
41
42
 
42
43
  // TODO: move somewhere else
43
44
  const draftAndPublishSync = require('./migrations/draft-publish');
44
45
 
46
+ /**
47
+ * A map of all the available Strapi lifecycles
48
+ * @type {import('@strapi/strapi').Core.Lifecycles}
49
+ */
45
50
  const LIFECYCLES = {
46
51
  REGISTER: 'register',
47
52
  BOOTSTRAP: 'bootstrap',
@@ -68,6 +73,7 @@ const resolveWorkingDirectories = opts => {
68
73
  return { app: appDir, dist: distDir };
69
74
  };
70
75
 
76
+ /** @implements {import('@strapi/strapi').Strapi} */
71
77
  class Strapi {
72
78
  constructor(opts = {}) {
73
79
  destroyOnSignal(this);
@@ -92,6 +98,7 @@ class Strapi {
92
98
  this.container.register('plugins', pluginsRegistry(this));
93
99
  this.container.register('apis', apisRegistry(this));
94
100
  this.container.register('auth', createAuth(this));
101
+ this.container.register('sanitizers', sanitizersRegistry(this));
95
102
 
96
103
  // Create a mapping of every useful directory (for the app, dist and static directories)
97
104
  this.dirs = utils.getDirs(rootDirs, { strapi: this });
@@ -190,6 +197,10 @@ class Strapi {
190
197
  return this.container.get('auth');
191
198
  }
192
199
 
200
+ get sanitizers() {
201
+ return this.container.get('sanitizers');
202
+ }
203
+
193
204
  async start() {
194
205
  try {
195
206
  if (!this.isLoaded) {
@@ -337,6 +348,10 @@ class Strapi {
337
348
  this.app = await loaders.loadSrcIndex(this);
338
349
  }
339
350
 
351
+ async loadSanitizers() {
352
+ await loaders.loadSanitizers(this);
353
+ }
354
+
340
355
  registerInternalHooks() {
341
356
  this.container.get('hooks').set('strapi::content-types.beforeSync', createAsyncParallelHook());
342
357
  this.container.get('hooks').set('strapi::content-types.afterSync', createAsyncParallelHook());
@@ -348,6 +363,7 @@ class Strapi {
348
363
  async register() {
349
364
  await Promise.all([
350
365
  this.loadApp(),
366
+ this.loadSanitizers(),
351
367
  this.loadPlugins(),
352
368
  this.loadAdmin(),
353
369
  this.loadAPIs(),
@@ -3,6 +3,7 @@
3
3
  const { yup } = require('@strapi/utils');
4
4
  const _ = require('lodash');
5
5
  const inquirer = require('inquirer');
6
+ const tsUtils = require('@strapi/typescript-utils');
6
7
  const strapi = require('../index');
7
8
 
8
9
  const emailValidator = yup
@@ -90,7 +91,20 @@ module.exports = async function(cmdOptions = {}) {
90
91
  };
91
92
 
92
93
  async function createAdmin({ email, password, firstname, lastname }) {
93
- const app = await strapi().load();
94
+ const appDir = process.cwd();
95
+
96
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
97
+ const outDir = await tsUtils.resolveOutDir(appDir);
98
+
99
+ if (isTSProject)
100
+ await tsUtils.compile(appDir, {
101
+ watch: false,
102
+ configOptions: { options: { incremental: true } },
103
+ });
104
+
105
+ const distDir = isTSProject ? outDir : appDir;
106
+
107
+ const app = await strapi({ appDir, distDir }).load();
94
108
 
95
109
  const user = await app.admin.services.user.exists({ email });
96
110
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const _ = require('lodash');
4
4
  const inquirer = require('inquirer');
5
+ const tsUtils = require('@strapi/typescript-utils');
5
6
  const strapi = require('../index');
6
7
 
7
8
  const promptQuestions = [
@@ -42,7 +43,20 @@ module.exports = async function(cmdOptions = {}) {
42
43
  };
43
44
 
44
45
  async function changePassword({ email, password }) {
45
- const app = await strapi().load();
46
+ const appDir = process.cwd();
47
+
48
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
49
+ const outDir = await tsUtils.resolveOutDir(appDir);
50
+
51
+ if (isTSProject)
52
+ await tsUtils.compile(appDir, {
53
+ watch: false,
54
+ configOptions: { options: { incremental: true } },
55
+ });
56
+
57
+ const distDir = isTSProject ? outDir : appDir;
58
+
59
+ const app = await strapi({ appDir, distDir }).load();
46
60
 
47
61
  await app.admin.services.user.resetPasswordByEmail(email, password);
48
62
 
@@ -1,5 +1,4 @@
1
1
  'use strict';
2
- const path = require('path');
3
2
 
4
3
  const tsUtils = require('@strapi/typescript-utils');
5
4
  const { buildAdmin, buildTypeScript } = require('./builders');
@@ -12,13 +11,14 @@ module.exports = async ({ optimization, forceBuild = true }) => {
12
11
  const srcDir = process.cwd();
13
12
 
14
13
  const useTypeScriptServer = await tsUtils.isUsingTypeScript(srcDir);
14
+ const outDir = await tsUtils.resolveOutDir(srcDir);
15
15
 
16
16
  // Typescript
17
17
  if (useTypeScriptServer) {
18
18
  await buildTypeScript({ srcDir, watch: false });
19
19
 
20
20
  // Update the dir path for the next steps
21
- buildDestDir = path.join(srcDir, 'dist');
21
+ buildDestDir = outDir;
22
22
  }
23
23
 
24
24
  await buildAdmin({
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const tsUtils = require('@strapi/typescript-utils');
4
5
  const strapi = require('../index');
5
6
 
6
7
  const CHUNK_SIZE = 100;
@@ -12,7 +13,19 @@ const CHUNK_SIZE = 100;
12
13
  module.exports = async function({ file: filePath, pretty }) {
13
14
  const output = filePath ? fs.createWriteStream(filePath) : process.stdout;
14
15
 
15
- const app = await strapi().load();
16
+ const appDir = process.cwd();
17
+
18
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
19
+ const outDir = await tsUtils.resolveOutDir(appDir);
20
+ if (isTSProject)
21
+ await tsUtils.compile(appDir, {
22
+ watch: false,
23
+ configOptions: { options: { incremental: true } },
24
+ });
25
+
26
+ const distDir = isTSProject ? outDir : appDir;
27
+
28
+ const app = await strapi({ appDir, distDir }).load();
16
29
 
17
30
  const count = await app.query('strapi::core-store').count();
18
31
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const _ = require('lodash');
5
+ const tsUtils = require('@strapi/typescript-utils');
6
+
5
7
  const strapi = require('../index');
6
8
 
7
9
  /**
@@ -12,7 +14,20 @@ const strapi = require('../index');
12
14
  module.exports = async function({ file: filePath, strategy = 'replace' }) {
13
15
  const input = filePath ? fs.readFileSync(filePath) : await readStdin(process.stdin);
14
16
 
15
- const app = await strapi().load();
17
+ const appDir = process.cwd();
18
+
19
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
20
+ const outDir = await tsUtils.resolveOutDir(appDir);
21
+
22
+ if (isTSProject)
23
+ await tsUtils.compile(appDir, {
24
+ watch: false,
25
+ configOptions: { options: { incremental: true } },
26
+ });
27
+
28
+ const distDir = isTSProject ? outDir : appDir;
29
+
30
+ const app = await strapi({ appDir, distDir }).load();
16
31
 
17
32
  let dataToImport;
18
33
  try {
@@ -1,14 +1,28 @@
1
1
  'use strict';
2
2
 
3
3
  const REPL = require('repl');
4
+ const tsUtils = require('@strapi/typescript-utils');
5
+
4
6
  const strapi = require('../index');
5
7
 
6
8
  /**
7
9
  * `$ strapi console`
8
10
  */
9
- module.exports = () => {
11
+ module.exports = async () => {
10
12
  // Now load up the Strapi framework for real.
11
- const app = strapi();
13
+ const appDir = process.cwd();
14
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
15
+ const outDir = await tsUtils.resolveOutDir(appDir);
16
+
17
+ if (isTSProject)
18
+ await tsUtils.compile(appDir, {
19
+ watch: false,
20
+ configOptions: { options: { incremental: true } },
21
+ });
22
+
23
+ const distDir = isTSProject ? outDir : appDir;
24
+
25
+ const app = await strapi({ appDir, distDir }).load();
12
26
 
13
27
  app.start().then(() => {
14
28
  const repl = REPL.start(app.config.info.name + ' > ' || 'strapi > '); // eslint-disable-line prefer-template
@@ -22,7 +22,8 @@ module.exports = async function({ build, watchAdmin, polling, browser }) {
22
22
  const appDir = process.cwd();
23
23
 
24
24
  const isTSProject = await tsUtils.isUsingTypeScript(appDir);
25
- const distDir = isTSProject ? path.join(appDir, 'dist') : appDir;
25
+ const outDir = await tsUtils.resolveOutDir(appDir);
26
+ const distDir = isTSProject ? outDir : appDir;
26
27
 
27
28
  try {
28
29
  if (cluster.isMaster || cluster.isPrimary) {
@@ -3,11 +3,25 @@
3
3
  const CLITable = require('cli-table3');
4
4
  const chalk = require('chalk');
5
5
  const { toUpper } = require('lodash/fp');
6
+ const tsUtils = require('@strapi/typescript-utils');
6
7
 
7
8
  const strapi = require('../../index');
8
9
 
9
10
  module.exports = async function() {
10
- const app = await strapi().load();
11
+ const appDir = process.cwd();
12
+
13
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
14
+ const outDir = await tsUtils.resolveOutDir(appDir);
15
+
16
+ if (isTSProject)
17
+ await tsUtils.compile(appDir, {
18
+ watch: false,
19
+ configOptions: { options: { incremental: true } },
20
+ });
21
+
22
+ const distDir = isTSProject ? outDir : appDir;
23
+
24
+ const app = await strapi({ appDir, distDir }).load();
11
25
 
12
26
  const list = app.server.listRoutes();
13
27
 
@@ -1,8 +1,18 @@
1
1
  'use strict';
2
-
2
+ const fs = require('fs');
3
+ const tsUtils = require('@strapi/typescript-utils');
3
4
  const strapi = require('../index');
4
5
 
5
6
  /**
6
7
  * `$ strapi start`
7
8
  */
8
- module.exports = distDir => strapi({ distDir }).start();
9
+ module.exports = async specifiedDir => {
10
+ const appDir = process.cwd();
11
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
12
+ const outDir = await tsUtils.resolveOutDir(appDir);
13
+ const buildDirExists = fs.existsSync(outDir);
14
+ if (isTSProject && !buildDirExists) throw new Error(`${outDir} directory not found. Please run the build command before starting your application`);
15
+ const distDir = isTSProject && !specifiedDir ? outDir : specifiedDir;
16
+
17
+ strapi({ distDir }).start();
18
+ };
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const path = require('path');
4
3
  const strapiAdmin = require('@strapi/admin');
5
4
  const tsUtils = require('@strapi/typescript-utils');
6
5
  const { getConfigUrls, getAbsoluteServerUrl } = require('@strapi/utils');
@@ -13,7 +12,8 @@ module.exports = async function({ browser }) {
13
12
  const currentDirectory = process.cwd();
14
13
 
15
14
  const isTSProject = await tsUtils.isUsingTypeScript(currentDirectory);
16
- const buildDestDir = isTSProject ? path.join(currentDirectory, 'dist') : currentDirectory;
15
+ const outDir = await tsUtils.resolveOutDir(currentDirectory);
16
+ const buildDestDir = isTSProject ? outDir : currentDirectory;
17
17
 
18
18
  const strapiInstance = strapi({
19
19
  distDir: buildDestDir,
@@ -7,31 +7,35 @@ const fse = require('fs-extra');
7
7
  const { isKebabCase } = require('@strapi/utils');
8
8
  const { importDefault } = require('../../utils');
9
9
 
10
- // to handle names with numbers in it we first check if it is already in kebabCase
11
- const normalizeName = name => (isKebabCase(name) ? name : _.kebabCase(name));
12
-
13
10
  const DEFAULT_CONTENT_TYPE = {
14
11
  schema: {},
15
12
  actions: {},
16
13
  lifecycles: {},
17
14
  };
18
15
 
16
+ // to handle names with numbers in it we first check if it is already in kebabCase
17
+ const normalizeName = name => (isKebabCase(name) ? name : _.kebabCase(name));
18
+
19
+ const isDirectory = fd => fd.isDirectory();
20
+ const isDotFile = fd => fd.name.startsWith('.');
21
+
19
22
  module.exports = async strapi => {
20
23
  if (!existsSync(strapi.dirs.dist.api)) {
21
24
  return;
22
25
  }
23
26
 
24
- const apisFDs = await fse.readdir(strapi.dirs.dist.api, { withFileTypes: true });
27
+ const apisFDs = await (await fse.readdir(strapi.dirs.dist.api, { withFileTypes: true }))
28
+ .filter(isDirectory)
29
+ .filter(_.negate(isDotFile));
30
+
25
31
  const apis = {};
26
32
 
27
33
  // only load folders
28
34
  for (const apiFD of apisFDs) {
29
- if (apiFD.isDirectory()) {
30
- const apiName = normalizeName(apiFD.name);
31
- const api = await loadAPI(join(strapi.dirs.dist.api, apiFD.name));
35
+ const apiName = normalizeName(apiFD.name);
36
+ const api = await loadAPI(join(strapi.dirs.dist.api, apiFD.name));
32
37
 
33
- apis[apiName] = api;
34
- }
38
+ apis[apiName] = api;
35
39
  }
36
40
 
37
41
  validateContentTypesUnicity(apis);
@@ -8,4 +8,5 @@ module.exports = {
8
8
  loadPolicies: require('./policies'),
9
9
  loadPlugins: require('./plugins'),
10
10
  loadAdmin: require('./admin'),
11
+ loadSanitizers: require('./sanitizers'),
11
12
  };
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ module.exports = strapi => {
4
+ strapi.container.get('sanitizers').set('content-api', { input: [], output: [] });
5
+ };
@@ -6,4 +6,4 @@ interface PolicyContext extends BaseContext {
6
6
  is(name): boolean;
7
7
  }
8
8
 
9
- export type Policy = (ctx: PolicyContext, { strapi: Strapi }) => boolean | undefined;
9
+ export type Policy<T=unknown> = (ctx: PolicyContext,cfg:T, { strapi: Strapi }) => boolean | undefined;
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+
5
+ const sanitizersRegistry = () => {
6
+ const sanitizers = {};
7
+
8
+ return {
9
+ get(path) {
10
+ return _.get(sanitizers, path, []);
11
+ },
12
+ add(path, sanitizer) {
13
+ this.get(path).push(sanitizer);
14
+ return this;
15
+ },
16
+ set(path, value = []) {
17
+ _.set(sanitizers, path, value);
18
+ return this;
19
+ },
20
+ has(path) {
21
+ return _.has(sanitizers, path);
22
+ },
23
+ };
24
+ };
25
+
26
+ module.exports = sanitizersRegistry;
@@ -1,6 +1,6 @@
1
1
  import { Context } from 'koa';
2
2
 
3
- type Response = object;
3
+ type ControllerResponse <T=unknown> = T | Promise<T>;
4
4
 
5
5
  interface BaseController {
6
6
  transformResponse(data: object, meta: object): object;
@@ -9,17 +9,22 @@ interface BaseController {
9
9
  }
10
10
 
11
11
  export interface SingleTypeController extends BaseController {
12
- find(ctx: Context): Promise<Response>;
13
- update(ctx: Context): Promise<Response>;
14
- delete(ctx: Context): Promise<Response>;
12
+ find(ctx: Context): ControllerResponse;
13
+ update(ctx: Context): ControllerResponse;
14
+ delete(ctx: Context): ControllerResponse;
15
15
  }
16
16
 
17
17
  export interface CollectionTypeController extends BaseController {
18
- find(ctx: Context): Promise<Response>;
19
- findOne(ctx: Context): Promise<Response>;
20
- create(ctx: Context): Promise<Response>;
21
- update(ctx: Context): Promise<Response>;
22
- delete(ctx: Context): Promise<Response>;
18
+ find(ctx: Context): ControllerResponse;
19
+ findOne(ctx: Context): ControllerResponse
20
+ create(ctx: Context): ControllerResponse;
21
+ update(ctx: Context): ControllerResponse;
22
+ delete(ctx: Context): ControllerResponse;
23
23
  }
24
24
 
25
25
  export type Controller = SingleTypeController | CollectionTypeController;
26
+
27
+ export type GenericController = Partial<Controller> & {
28
+ [method: string | number | symbol]: (ctx: Context) => unknown
29
+ }
30
+
@@ -1,21 +1,22 @@
1
1
  type Entity = object;
2
2
 
3
3
  interface BaseService {
4
- getFetchParams(params: object): object;
4
+ getFetchParams?(params: object): object;
5
5
  }
6
6
 
7
7
  export interface SingleTypeService extends BaseService {
8
- find(params: object): Promise<Entity>;
9
- createOrUpdate(params: object): Promise<Entity>;
10
- delete(params: object): Promise<Entity>;
8
+ find?(params: object): Promise<Entity> | Entity;
9
+ createOrUpdate?(params: object): Promise<Entity> | Entity;
10
+ delete?(params: object): Promise<Entity> | Entity;
11
11
  }
12
12
 
13
13
  export interface CollectionTypeService extends BaseService {
14
- find(params: object): Promise<Entity[]>;
15
- findOne(params: object): Promise<Entity>;
16
- create(params: object): Promise<Entity>;
17
- update(params: object): Promise<Entity>;
18
- delete(params: object): Promise<Entity>;
14
+ find?(params: object): Promise<Entity[]> | Entity;
15
+ findOne?(entityId: string,params: object): Promise<Entity> | Entity;
16
+ create?(params: object): Promise<Entity> | Entity;
17
+ update?(entityId: string,params: object): Promise<Entity> | Entity;
18
+ delete?(entityId: string,params: object): Promise<Entity> | Entity;
19
19
  }
20
20
 
21
21
  export type Service = SingleTypeService | CollectionTypeService;
22
+
@@ -1,36 +1,37 @@
1
1
  import { Service } from './core-api/service';
2
- import { Controller } from './core-api/controller';
2
+ import { Controller, GenericController } from './core-api/controller';
3
3
  import { Middleware } from './middlewares';
4
4
  import { Policy } from './core/registries/policies';
5
+ import { Strapi } from '@strapi/strapi'
5
6
 
6
- type ControllerConfig = Controller;
7
+ type ControllerConfig<T extends Controller = Controller> = T;
7
8
 
8
9
  type ServiceConfig = Service;
9
10
 
10
11
  type HandlerConfig = {
11
- auth: false | { scope: string[] };
12
- policies: Array<string | Policy>;
13
- middlewares: Array<string | Middleware>;
12
+ auth?: false | { scope: string[] };
13
+ policies?: Array<string | Policy>;
14
+ middlewares?: Array<string | Middleware>;
14
15
  };
15
16
 
16
17
  type SingleTypeRouterConfig = {
17
- find: HandlerConfig;
18
- update: HandlerConfig;
19
- delete: HandlerConfig;
18
+ find?: HandlerConfig;
19
+ update?: HandlerConfig;
20
+ delete?: HandlerConfig;
20
21
  };
21
22
 
22
23
  type CollectionTypeRouterConfig = {
23
- find: HandlerConfig;
24
- findOne: HandlerConfig;
25
- create: HandlerConfig;
26
- update: HandlerConfig;
27
- delete: HandlerConfig;
24
+ find?: HandlerConfig;
25
+ findOne?: HandlerConfig;
26
+ create?: HandlerConfig;
27
+ update?: HandlerConfig;
28
+ delete?: HandlerConfig;
28
29
  };
29
30
 
30
31
  type RouterConfig = {
31
- prefix: string;
32
+ prefix?: string;
32
33
  only: string[];
33
- except: string[];
34
+ except?: string[];
34
35
  config: SingleTypeRouterConfig | CollectionTypeRouterConfig;
35
36
  };
36
37
 
@@ -43,6 +44,9 @@ interface Router {
43
44
  routes: Route[];
44
45
  }
45
46
 
47
+ type ControllerCallback <T extends GenericController = GenericController> = (params:{strapi:Strapi}) => T;
48
+ type ServiceCallback <T extends Service = Sevice> = (params:{strapi:Strapi}) => T
49
+
46
50
  export function createCoreRouter(uid: string, cfg?: RouterConfig = {}): () => Router;
47
- export function createCoreController(uid: string, cfg?: ControllerConfig = {}): () => Controller;
48
- export function createCoreService(uid: string, cfg?: ServiceConfig = {}): () => Service;
51
+ export function createCoreController<T extends GenericController = GenericController>(uid: string, cfg?: ControllerCallback<T> | T = {}): () => T & Controller;
52
+ export function createCoreService<T extends Service = Service>(uid: string, cfg?: ServiceCallback<T> | T = {}): () => T ;
package/lib/index.d.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  import { Database } from '@strapi/database';
2
2
  import { EntityService } from './services/entity-service';
3
- import { Strapi as StrapiClass } from './Strapi';
4
3
 
4
+ import * as Core from './types/strapi';
5
5
  export * as factories from './factories';
6
- export interface StrapiInterface extends StrapiClass {
7
- query: Database['query'];
8
- entityService: EntityService;
9
- }
10
6
 
11
- export type Strapi = StrapiInterface;
7
+ export type { Core };
8
+
9
+ // Alias to resolve the Strapi global type easily
10
+ export type Strapi = Core.Strapi;
11
+
12
+ export interface StrapiInterface extends Core.Strapi {};
12
13
 
13
14
  declare global {
14
15
  interface AllTypes {}
@@ -3,12 +3,23 @@
3
3
  const fse = require('fs-extra');
4
4
  const { defaultsDeep, get } = require('lodash/fp');
5
5
  const body = require('koa-body');
6
+ const mime = require('mime-types');
6
7
 
7
8
  const defaults = {
8
9
  multipart: true,
9
10
  patchKoa: true,
10
11
  };
11
12
 
13
+ function ensureFileMimeType(file) {
14
+ if (!file.type) {
15
+ file.type = mime.lookup(file.name) || 'application/octet-stream';
16
+ }
17
+ }
18
+
19
+ function getFiles(ctx) {
20
+ return get('request.files.files', ctx);
21
+ }
22
+
12
23
  /**
13
24
  * @type {import('./').MiddlewareFactory}
14
25
  */
@@ -18,21 +29,38 @@ module.exports = config => {
18
29
  return async (ctx, next) => {
19
30
  // TODO: find a better way later
20
31
  if (ctx.url === '/graphql') {
21
- return next();
22
- }
32
+ await next();
33
+ } else {
34
+ try {
35
+ await body({ patchKoa: true, ...bodyConfig })(ctx, () => {});
23
36
 
24
- try {
25
- await body({ patchKoa: true, ...bodyConfig })(ctx, next);
26
- } catch (e) {
27
- if ((e || {}).message && e.message.includes('maxFileSize exceeded')) {
28
- return ctx.payloadTooLarge('FileTooBig');
29
- }
37
+ const files = getFiles(ctx);
30
38
 
31
- throw e;
39
+ /**
40
+ * in case the mime-type wasn't sent, Strapi tries to guess it
41
+ * from the file extension, to avoid a corrupt database state
42
+ */
43
+ if (files) {
44
+ if (Array.isArray(files)) {
45
+ files.forEach(ensureFileMimeType);
46
+ } else {
47
+ ensureFileMimeType(files);
48
+ }
49
+ }
50
+
51
+ await next();
52
+ } catch (e) {
53
+ if ((e || {}).message && e.message.includes('maxFileSize exceeded')) {
54
+ return ctx.payloadTooLarge('FileTooBig');
55
+ }
56
+
57
+ throw e;
58
+ }
32
59
  }
33
60
 
61
+ const files = getFiles(ctx);
62
+
34
63
  // clean any file that was uploaded
35
- const files = get('request.files.files', ctx);
36
64
  if (files) {
37
65
  if (Array.isArray(files)) {
38
66
  // not awaiting to not slow the request
@@ -166,7 +166,9 @@ const stringValidator = composeValidators(
166
166
  addUniqueValidator
167
167
  );
168
168
 
169
- const emailValidator = composeValidators(stringValidator, validator => validator.email());
169
+ const emailValidator = composeValidators(stringValidator, validator =>
170
+ validator.email().min(1, '${path} cannot be empty')
171
+ );
170
172
 
171
173
  const uidValidator = composeValidators(stringValidator, validator =>
172
174
  validator.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
@@ -0,0 +1,291 @@
1
+ import type Koa from 'koa';
2
+
3
+ import type { StringMap } from './utils';
4
+
5
+ type Controller = {
6
+ [methodName: string | number | symbol]: (context: Koa.Context) => unknown;
7
+ }
8
+
9
+ /**
10
+ * The Strapi interface implemented by the main Strapi class.
11
+ */
12
+ export interface Strapi {
13
+ /**
14
+ * Getter for the Strapi enterprise edition configuration
15
+ */
16
+ readonly EE: any;
17
+
18
+ /**
19
+ * Getter for the Strapi configuration container
20
+ */
21
+ readonly config: any;
22
+
23
+ /**
24
+ * Getter for the Strapi auth container
25
+ */
26
+ readonly auth: any;
27
+
28
+ /**
29
+ * Getter for the Strapi sanitizers container
30
+ */
31
+ readonly sanitizers: any;
32
+
33
+ /**
34
+ * Getter for the Strapi services container
35
+ *
36
+ * It returns all the registered services
37
+ */
38
+ readonly services: StringMap<Service>;
39
+
40
+ /**
41
+ * Find a service using its unique identifier
42
+ */
43
+ service<T extends Service = unknown>(uid: string): T | undefined;
44
+
45
+ /**
46
+ * Getter for the Strapi controllers container
47
+ *
48
+ * It returns all the registered controllers
49
+ */
50
+ readonly controllers: StringMap<Controller>;
51
+
52
+ /**
53
+ * Find a controller using its unique identifier
54
+ */
55
+ controller(uid: string): Controller | undefined;
56
+
57
+ /**
58
+ * Getter for the Strapi content types container
59
+ *
60
+ * It returns all the registered content types
61
+ */
62
+ readonly contentTypes: any;
63
+
64
+ /**
65
+ * Find a content type using its unique identifier
66
+ */
67
+ contentType(uid: string): any;
68
+
69
+ /**
70
+ * Getter for the Strapi policies container
71
+ *
72
+ * It returns all the registered policies
73
+ */
74
+ readonly policies: any;
75
+
76
+ /**
77
+ * Find a policy using its name
78
+ */
79
+ policy(name: string): any;
80
+
81
+ /**
82
+ * Getter for the Strapi middlewares container
83
+ *
84
+ * It returns all the registered middlewares
85
+ */
86
+ readonly middlewares: any;
87
+
88
+ /**
89
+ * Find a middleware using its name
90
+ */
91
+ middleware(): any;
92
+
93
+ /**
94
+ * Getter for the Strapi plugins container
95
+ *
96
+ * It returns all the registered plugins
97
+ */
98
+ readonly plugins: any;
99
+
100
+ /**
101
+ * Find a plugin using its name
102
+ */
103
+ plugin(name: string): any;
104
+
105
+ /**
106
+ * Getter for the Strapi hooks container
107
+ *
108
+ * It returns all the registered hooks
109
+ */
110
+ readonly hooks: any;
111
+
112
+ /**
113
+ * Find a hook using its name
114
+ */
115
+ hook(): any;
116
+
117
+ /**
118
+ * Getter for the Strapi APIs container
119
+ *
120
+ * It returns all the registered APIs
121
+ */
122
+ readonly api: any;
123
+
124
+ /**
125
+ * Strapi Register Lifecycle.
126
+ *
127
+ * - Load
128
+ * - The user application
129
+ * - The plugins
130
+ * - The admin
131
+ * - The APIs
132
+ * - The components
133
+ * - The middlewares
134
+ * - The policies
135
+ * - Trigger Strapi internal bootstrap
136
+ * - Create the webhooks runner
137
+ * - Create the internal hooks registry.
138
+ * - Init the telemetry cron job and middleware
139
+ * - Run all the `register` lifecycle methods loaded by the user application or the enabled plugins
140
+ */
141
+ register(): Promise<Strapi>;
142
+
143
+ /**
144
+ * Bootstraping phase.
145
+ *
146
+ * - Load all the content types
147
+ * - Initialize the database layer
148
+ * - Initialize the entity service
149
+ * - Run the schemas/database synchronization
150
+ * - Start the webhooks and initializing middlewares and routes
151
+ * - Run all the `bootstrap` lifecycle methods loaded by the
152
+ * user application or the enabled plugins
153
+ */
154
+ bootstrap(): Promise<Strapi>;
155
+
156
+ /**
157
+ * Destroy phase
158
+ *
159
+ * - Destroy Strapi server
160
+ * - Run all the `destroy` lifecycle methods loaded by the
161
+ * user application or the enabled plugins
162
+ * - Cleanup the event hub
163
+ * - Gracefully stop the database
164
+ * - Stop the telemetry and cron instance
165
+ * - Cleanup the global scope by removing global.strapi
166
+ */
167
+ destroy(): Promise<void>;
168
+
169
+ /**
170
+ * Run all functions registered for a given lifecycle. (Strapi core, user app, plugins)
171
+ */
172
+ runLifecyclesFunctions<T extends Lifecycles[keyof Lifecycles]>(lifecycleName: T): Promise<void>;
173
+
174
+ /**
175
+ * Load the application if needed and start the server
176
+ */
177
+ start(): Promise<void>;
178
+
179
+ /**
180
+ * Stop the server and provide a custom error and message
181
+ */
182
+ stopWithError<TError = unknown>(error: TError, customMessage?: string): void;
183
+
184
+ /**
185
+ * Gracefully stop the server
186
+ * Call the destroy method.
187
+ */
188
+ stop(code?: number): void;
189
+
190
+ /**
191
+ * Load the server and the user application.
192
+ * It basically triggers the register and bootstrap phases
193
+ */
194
+ load(): Promise<Strapi>;
195
+
196
+ /**
197
+ * Restart the server and reload all the configuration.
198
+ * It re-runs all the lifecycles phases.
199
+ *
200
+ * @example
201
+ * ``` ts
202
+ * setImmediate(() => strapi.reload());
203
+ * ```
204
+ */
205
+ reload(): () => void;
206
+
207
+ /**
208
+ * Initialize and start all the webhooks registered in the webhook store
209
+ */
210
+ startWebhooks(): Promise<void>;
211
+
212
+ /**
213
+ * Method called when the server is fully initialized and listen to incomming requests.
214
+ * It handles tasks such as logging the startup message
215
+ * or automatically opening the administration panel.
216
+ */
217
+ postListen(): Promise<void>;
218
+
219
+ /**
220
+ * Start listening for incomming requests
221
+ */
222
+ listen(): Promise<void | Error>;
223
+
224
+ /**
225
+ * Opent he administration panel in a browser if the option is enabled.
226
+ * You can disable it using the admin.autoOpen configuration variable.
227
+ *
228
+ * Note: It only works in development envs.
229
+ */
230
+ openAdmin(options: { isInitialized: boolean }): Promise<void>;
231
+
232
+ /**
233
+ * Load the admin panel server logic into the server code and initialize its configuration.
234
+ */
235
+ loadAdmin(): Promise<void>;
236
+
237
+ /**
238
+ * Resolve every enabled plugin and load them into the application.
239
+ */
240
+ loadPlugins(): Promise<void>;
241
+
242
+ /**
243
+ * Load every global policies in the policies container by
244
+ * reading from the `strapi.dirs.dist.policies` directory.
245
+ */
246
+ loadPolicies(): Promise<void>;
247
+
248
+ /**
249
+ * Load every APIs and their components (config, routes, controllers, services,
250
+ * policies, middlewares, content-types) in the API container.
251
+ */
252
+ loadAPIs(): Promise<void>;
253
+
254
+ /**
255
+ * Resolve every components in the user application and store them in `strapi.components`
256
+ */
257
+ loadComponents(): Promise<void>;
258
+
259
+ /**
260
+ * Load every global and core middlewares in the middlewares container by
261
+ * reading from the `strapi.dirs.dist.middlewares` and internal middlewares directory.
262
+ */
263
+ loadMiddlewares(): Promise<void>;
264
+
265
+ /**
266
+ * Load the user application in the server by reading the `src/index.js` file.
267
+ */
268
+ loadApp(): Promise<void>;
269
+
270
+ /**
271
+ * Add internal hooks to the hooks container.
272
+ * Those hooks are meant for internal usage and might break in future releases.
273
+ */
274
+ registerInternalHooks(): void;
275
+
276
+ /**
277
+ * Find a model (content-type, component) based on its unique identifier.
278
+ */
279
+ getModel(uid: string): any;
280
+
281
+ /**
282
+ * Binds database queries for a specific model based on its unique identifier.
283
+ */
284
+ query(uid: string): any;
285
+ }
286
+
287
+ export interface Lifecycles {
288
+ REGISTER: 'register';
289
+ BOOTSTRAP: 'bootstrap';
290
+ DESTROY: 'destroy';
291
+ }
@@ -0,0 +1 @@
1
+ export type StringMap<T> = { [key: string]: T };
@@ -20,7 +20,7 @@ const boxenOptions = {
20
20
  borderStyle: 'round',
21
21
  };
22
22
 
23
- const geUpdatetMessage = (newVersion, currentVersion) => {
23
+ const getUpdateMessage = (newVersion, currentVersion) => {
24
24
  const currentVersionLog = chalk.dim(currentVersion);
25
25
  const newVersionLog = chalk.green(newVersion);
26
26
  const releaseLink = chalk.bold('https://github.com/strapi/strapi/releases');
@@ -78,7 +78,7 @@ const createUpdateNotifier = strapi => {
78
78
  return;
79
79
  }
80
80
 
81
- const message = boxen(geUpdatetMessage(latestVersion, pkg.version), boxenOptions);
81
+ const message = boxen(getUpdateMessage(latestVersion, pkg.version), boxenOptions);
82
82
  config.set('lastNotification', now);
83
83
  console.log(message);
84
84
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/strapi",
3
- "version": "4.2.0-beta.1",
3
+ "version": "4.2.0-beta.4",
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",
@@ -80,17 +80,17 @@
80
80
  "dependencies": {
81
81
  "@koa/cors": "3.1.0",
82
82
  "@koa/router": "10.1.1",
83
- "@strapi/admin": "4.2.0-beta.1",
84
- "@strapi/database": "4.2.0-beta.1",
85
- "@strapi/generate-new": "4.2.0-beta.1",
86
- "@strapi/generators": "4.2.0-beta.1",
87
- "@strapi/logger": "4.2.0-beta.1",
88
- "@strapi/plugin-content-manager": "4.2.0-beta.1",
89
- "@strapi/plugin-content-type-builder": "4.2.0-beta.1",
90
- "@strapi/plugin-email": "4.2.0-beta.1",
91
- "@strapi/plugin-upload": "4.2.0-beta.1",
92
- "@strapi/typescript-utils": "4.2.0-beta.1",
93
- "@strapi/utils": "4.2.0-beta.1",
83
+ "@strapi/admin": "4.2.0-beta.4",
84
+ "@strapi/database": "4.2.0-beta.4",
85
+ "@strapi/generate-new": "4.2.0-beta.4",
86
+ "@strapi/generators": "4.2.0-beta.4",
87
+ "@strapi/logger": "4.2.0-beta.4",
88
+ "@strapi/plugin-content-manager": "4.2.0-beta.4",
89
+ "@strapi/plugin-content-type-builder": "4.2.0-beta.4",
90
+ "@strapi/plugin-email": "4.2.0-beta.4",
91
+ "@strapi/plugin-upload": "4.2.0-beta.4",
92
+ "@strapi/typescript-utils": "4.2.0-beta.4",
93
+ "@strapi/utils": "4.2.0-beta.4",
94
94
  "bcryptjs": "2.4.3",
95
95
  "boxen": "5.1.2",
96
96
  "chalk": "4.1.2",
@@ -118,6 +118,7 @@
118
118
  "koa-session": "6.2.0",
119
119
  "koa-static": "5.0.0",
120
120
  "lodash": "4.17.21",
121
+ "mime-types": "2.1.35",
121
122
  "node-fetch": "2.6.7",
122
123
  "node-machine-id": "1.1.12",
123
124
  "node-schedule": "2.0.0",
@@ -135,8 +136,8 @@
135
136
  "typescript": "4.6.2"
136
137
  },
137
138
  "engines": {
138
- "node": ">=12.22.0 <=16.x.x",
139
+ "node": ">=14.19.1 <=16.x.x",
139
140
  "npm": ">=6.0.0"
140
141
  },
141
- "gitHead": "4fa2804f35e15ed72dfd309fb5bb1c4dba18932f"
142
+ "gitHead": "5caff35a30e33b7a660eb946299f9e3d2d35b2b8"
142
143
  }