@strapi/strapi 4.2.0-alpha.O → 4.2.0-beta.2

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.
Files changed (45) hide show
  1. package/README.md +2 -3
  2. package/bin/strapi.js +18 -1
  3. package/lib/Strapi.js +38 -4
  4. package/lib/commands/admin-create.js +122 -0
  5. package/lib/commands/admin-reset.js +8 -1
  6. package/lib/commands/build.js +18 -44
  7. package/lib/commands/builders/admin.js +59 -0
  8. package/lib/commands/builders/index.js +9 -0
  9. package/lib/commands/builders/typescript.js +32 -0
  10. package/lib/commands/configurationDump.js +8 -1
  11. package/lib/commands/configurationRestore.js +9 -1
  12. package/lib/commands/console.js +10 -2
  13. package/lib/commands/develop.js +113 -71
  14. package/lib/commands/opt-out-telemetry.js +83 -0
  15. package/lib/commands/routes/list.js +8 -1
  16. package/lib/commands/start.js +8 -2
  17. package/lib/commands/watchAdmin.js +10 -10
  18. package/lib/core/app-configuration/index.js +5 -3
  19. package/lib/core/app-configuration/load-config-file.js +3 -1
  20. package/lib/core/bootstrap.js +9 -1
  21. package/lib/core/loaders/apis.js +6 -5
  22. package/lib/core/loaders/components.js +5 -4
  23. package/lib/core/loaders/middlewares.js +5 -3
  24. package/lib/core/loaders/plugins/get-enabled-plugins.js +6 -2
  25. package/lib/core/loaders/plugins/get-user-plugins-config.js +2 -2
  26. package/lib/core/loaders/plugins/index.js +1 -1
  27. package/lib/core/loaders/policies.js +4 -2
  28. package/lib/core/loaders/src-index.js +6 -4
  29. package/lib/factories.d.ts +3 -3
  30. package/lib/index.d.ts +1 -1
  31. package/lib/load/load-files.js +3 -1
  32. package/lib/middlewares/favicon.js +1 -1
  33. package/lib/middlewares/public/index.js +2 -1
  34. package/lib/middlewares/security.js +1 -1
  35. package/lib/middlewares/session.js +3 -1
  36. package/lib/services/fs.js +1 -1
  37. package/lib/services/metrics/index.js +8 -2
  38. package/lib/services/metrics/sender.js +7 -0
  39. package/lib/services/server/index.js +1 -1
  40. package/lib/services/server/middleware.js +1 -1
  41. package/lib/utils/get-dirs.js +25 -11
  42. package/lib/utils/import-default.js +9 -0
  43. package/lib/utils/index.js +2 -0
  44. package/lib/utils/update-notifier/index.js +1 -1
  45. package/package.json +15 -13
package/README.md CHANGED
@@ -114,8 +114,7 @@ For general help using Strapi, please refer to [the official Strapi documentatio
114
114
  - [Discord](https://discord.strapi.io) (For live discussion with the Community and Strapi team)
115
115
  - [GitHub](https://github.com/strapi/strapi) (Bug reports, Contributions)
116
116
  - [Community Forum](https://forum.strapi.io) (Questions and Discussions)
117
- - [Academy](https://academy.strapi.io) (Learn the fundamentals of Strapi)
118
- - [ProductBoard](https://portal.productboard.com/strapi/tabs/2-under-consideration) (Roadmap, Feature requests)
117
+ - [Roadmap & Feature Requests](https://feedback.strapi.io/)
119
118
  - [Twitter](https://twitter.com/strapijs) (Get the news fast)
120
119
  - [Facebook](https://www.facebook.com/Strapi-616063331867161)
121
120
  - [YouTube Channel](https://www.youtube.com/strapi) (Learn from Video Tutorials)
@@ -126,7 +125,7 @@ Follow our [migration guides](https://docs.strapi.io/developer-docs/latest/updat
126
125
 
127
126
  ## Roadmap
128
127
 
129
- Check out our [roadmap](https://portal.productboard.com/strapi) to get informed of the latest features released and the upcoming ones. You may also give us insights and vote for a specific feature.
128
+ Check out our [roadmap](https://feedback.strapi.io/) to get informed of the latest features released and the upcoming ones. You may also give us insights and vote for a specific feature.
130
129
 
131
130
  ## Documentation
132
131
 
package/bin/strapi.js CHANGED
@@ -95,12 +95,13 @@ 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
 
101
102
  // `$ strapi start`
102
103
  program
103
- .command('start')
104
+ .command('start [dir]')
104
105
  .description('Start your Strapi application')
105
106
  .action(getLocalScript('start'));
106
107
 
@@ -174,6 +175,16 @@ program
174
175
  .action(getLocalScript('configurationRestore'));
175
176
 
176
177
  // Admin
178
+ program
179
+ .command('admin:create-user')
180
+ .alias('admin:create')
181
+ .description('Create a new admin')
182
+ .option('-e, --email <email>', 'Email of the new admin')
183
+ .option('-p, --password <password>', 'Password of the new admin')
184
+ .option('-f, --firstname <first name>', 'First name of the new admin')
185
+ .option('-l, --lastname <last name>', 'Last name of the new admin')
186
+ .action(getLocalScript('admin-create'));
187
+
177
188
  program
178
189
  .command('admin:reset-user-password')
179
190
  .alias('admin:reset-password')
@@ -217,4 +228,10 @@ program
217
228
  .description('List all the application controllers')
218
229
  .action(getLocalScript('controllers/list'));
219
230
 
231
+ // `$ strapi opt-out-telemetry`
232
+ program
233
+ .command('telemetry:disable')
234
+ .description('Stop Strapi from sending anonymous telemetry and metadata')
235
+ .action(getLocalScript('opt-out-telemetry'));
236
+
220
237
  program.parseAsync(process.argv);
package/lib/Strapi.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
3
4
  const _ = require('lodash');
4
5
  const { isFunction } = require('lodash/fp');
5
6
  const { createLogger } = require('@strapi/logger');
@@ -47,12 +48,39 @@ const LIFECYCLES = {
47
48
  DESTROY: 'destroy',
48
49
  };
49
50
 
51
+ /**
52
+ * Resolve the working directories based on the instance options.
53
+ *
54
+ * Behavior:
55
+ * - `appDir` is the directory where Strapi will write every file (schemas, generated APIs, controllers or services)
56
+ * - `distDir` is the directory where Strapi will read configurations, schemas and any compiled code
57
+ *
58
+ * Default values:
59
+ * - If `appDir` is `undefined`, it'll be set to `process.cwd()`
60
+ * - If `distDir` is `undefined`, it'll be set to `appDir`
61
+ */
62
+ const resolveWorkingDirectories = opts => {
63
+ const cwd = process.cwd();
64
+
65
+ const appDir = opts.appDir ? path.resolve(cwd, opts.appDir) : cwd;
66
+ const distDir = opts.distDir ? path.resolve(cwd, opts.distDir) : appDir;
67
+
68
+ return { app: appDir, dist: distDir };
69
+ };
70
+
50
71
  class Strapi {
51
72
  constructor(opts = {}) {
52
73
  destroyOnSignal(this);
53
- this.dirs = utils.getDirs(opts.dir || process.cwd());
54
- const appConfig = loadConfiguration(this.dirs.root, opts);
74
+
75
+ const rootDirs = resolveWorkingDirectories(opts);
76
+
77
+ // Load the app configuration from the dist directory
78
+ const appConfig = loadConfiguration({ appDir: rootDirs.app, distDir: rootDirs.dist }, opts);
79
+
80
+ // Instanciate the Strapi container
55
81
  this.container = createContainer(this);
82
+
83
+ // Register every Strapi registry in the container
56
84
  this.container.register('config', createConfigProvider(appConfig));
57
85
  this.container.register('content-types', contentTypesRegistry(this));
58
86
  this.container.register('services', servicesRegistry(this));
@@ -65,10 +93,17 @@ class Strapi {
65
93
  this.container.register('apis', apisRegistry(this));
66
94
  this.container.register('auth', createAuth(this));
67
95
 
96
+ // Create a mapping of every useful directory (for the app, dist and static directories)
97
+ this.dirs = utils.getDirs(rootDirs, { strapi: this });
98
+
99
+ // Strapi state management variables
68
100
  this.isLoaded = false;
69
101
  this.reload = this.reload();
102
+
103
+ // Instanciate the Koa app & the HTTP server
70
104
  this.server = createServer(this);
71
105
 
106
+ // Strapi utils instanciation
72
107
  this.fs = createStrapiFs(this);
73
108
  this.eventHub = createEventHub();
74
109
  this.startupLogger = createStartupLogger(this);
@@ -84,7 +119,7 @@ class Strapi {
84
119
  }
85
120
 
86
121
  get EE() {
87
- return ee({ dir: this.dirs.root, logger: this.log });
122
+ return ee({ dir: this.dirs.dist.root, logger: this.log });
88
123
  }
89
124
 
90
125
  get services() {
@@ -211,7 +246,6 @@ class Strapi {
211
246
  } catch (e) {
212
247
  this.telemetry.send('didNotOpenTab');
213
248
  }
214
- ;
215
249
  }
216
250
  }
217
251
 
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { yup } = require('@strapi/utils');
5
+ const _ = require('lodash');
6
+ const inquirer = require('inquirer');
7
+ const tsUtils = require('@strapi/typescript-utils');
8
+ const strapi = require('../index');
9
+
10
+ const emailValidator = yup
11
+ .string()
12
+ .email('Invalid email address')
13
+ .lowercase();
14
+
15
+ const passwordValidator = yup
16
+ .string()
17
+ .min(8, 'Password must be at least 8 characters long')
18
+ .matches(/[a-z]/, 'Password must contain at least one lowercase character')
19
+ .matches(/[A-Z]/, 'Password must contain at least one uppercase character')
20
+ .matches(/\d/, 'Password must contain at least one number');
21
+
22
+ const adminCreateSchema = yup.object().shape({
23
+ email: emailValidator,
24
+ password: passwordValidator,
25
+ firstname: yup.string().required('First name is required'),
26
+ lastname: yup.string(),
27
+ });
28
+
29
+ const promptQuestions = [
30
+ {
31
+ type: 'input',
32
+ name: 'email',
33
+ message: 'Admin email?',
34
+ async validate(value) {
35
+ const validEmail = await emailValidator.validate(value);
36
+ return validEmail === value || validEmail;
37
+ },
38
+ },
39
+ {
40
+ type: 'password',
41
+ name: 'password',
42
+ message: 'Admin password?',
43
+ async validate(value) {
44
+ const validPassword = await passwordValidator.validate(value);
45
+ return validPassword === value || validPassword;
46
+ },
47
+ },
48
+ { type: 'input', name: 'firstname', message: 'First name?' },
49
+ { type: 'input', name: 'lastname', message: 'Last name?' },
50
+ {
51
+ type: 'confirm',
52
+ name: 'confirm',
53
+ message: 'Do you really want to create a new admin?',
54
+ },
55
+ ];
56
+
57
+ /**
58
+ * Create new admin user
59
+ * @param {Object} cmdOptions - command options
60
+ * @param {string} cmdOptions.email - new admin's email
61
+ * @param {string} [cmdOptions.password] - new admin's password
62
+ * @param {string} cmdOptions.firstname - new admin's first name
63
+ * @param {string} [cmdOptions.lastname] - new admin's last name
64
+ */
65
+ module.exports = async function(cmdOptions = {}) {
66
+ let { email, password, firstname, lastname } = cmdOptions;
67
+
68
+ if (
69
+ _.isEmpty(email) &&
70
+ _.isEmpty(password) &&
71
+ _.isEmpty(firstname) &&
72
+ _.isEmpty(lastname) &&
73
+ process.stdin.isTTY
74
+ ) {
75
+ const inquiry = await inquirer.prompt(promptQuestions);
76
+
77
+ if (!inquiry.confirm) {
78
+ process.exit(0);
79
+ }
80
+
81
+ ({ email, password, firstname, lastname } = inquiry);
82
+ }
83
+
84
+ try {
85
+ await adminCreateSchema.validate({ email, password, firstname, lastname });
86
+ } catch (err) {
87
+ console.error(err.errors[0]);
88
+ process.exit(1);
89
+ }
90
+
91
+ return createAdmin({ email, password, firstname, lastname });
92
+ };
93
+
94
+ async function createAdmin({ email, password, firstname, lastname }) {
95
+ const appDir = process.cwd();
96
+
97
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
98
+ const distDir = isTSProject ? path.join(appDir, 'dist') : appDir;
99
+
100
+ const app = await strapi({ appDir, distDir }).load();
101
+
102
+ const user = await app.admin.services.user.exists({ email });
103
+
104
+ if (user) {
105
+ console.error(`User with email "${email}" already exists`);
106
+ process.exit(1);
107
+ }
108
+
109
+ const superAdminRole = await app.admin.services.role.getSuperAdmin();
110
+
111
+ await app.admin.services.user.create({
112
+ email,
113
+ firstname,
114
+ lastname,
115
+ isActive: true,
116
+ roles: [superAdminRole.id],
117
+ ...(password && { password, registrationToken: null }),
118
+ });
119
+
120
+ console.log(`Successfully created new admin`);
121
+ process.exit(0);
122
+ }
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
3
4
  const _ = require('lodash');
4
5
  const inquirer = require('inquirer');
6
+ const tsUtils = require('@strapi/typescript-utils');
5
7
  const strapi = require('../index');
6
8
 
7
9
  const promptQuestions = [
@@ -42,7 +44,12 @@ module.exports = async function(cmdOptions = {}) {
42
44
  };
43
45
 
44
46
  async function changePassword({ email, password }) {
45
- const app = await strapi().load();
47
+ const appDir = process.cwd();
48
+
49
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
50
+ const distDir = isTSProject ? path.join(appDir, 'dist') : appDir;
51
+
52
+ const app = await strapi({ appDir, distDir }).load();
46
53
 
47
54
  await app.admin.services.user.resetPasswordByEmail(email, password);
48
55
 
@@ -1,56 +1,30 @@
1
1
  'use strict';
2
- const { green } = require('chalk');
2
+ const path = require('path');
3
3
 
4
- const strapiAdmin = require('@strapi/admin');
5
- const { getConfigUrls } = require('@strapi/utils');
6
-
7
- const ee = require('../utils/ee');
8
- const addSlash = require('../utils/addSlash');
9
- const strapi = require('../index');
10
- const getEnabledPlugins = require('../core/loaders/plugins/get-enabled-plugins');
4
+ const tsUtils = require('@strapi/typescript-utils');
5
+ const { buildAdmin, buildTypeScript } = require('./builders');
11
6
 
12
7
  /**
13
8
  * `$ strapi build`
14
9
  */
15
10
  module.exports = async ({ optimization, forceBuild = true }) => {
16
- const dir = process.cwd();
17
-
18
- const strapiInstance = strapi({
19
- dir,
20
- autoReload: true,
21
- serveAdminPanel: false,
22
- });
11
+ let buildDestDir = process.cwd();
12
+ const srcDir = process.cwd();
23
13
 
24
- const plugins = await getEnabledPlugins(strapiInstance);
14
+ const useTypeScriptServer = await tsUtils.isUsingTypeScript(srcDir);
25
15
 
26
- const env = strapiInstance.config.get('environment');
27
- const { serverUrl, adminPath } = getConfigUrls(strapiInstance.config, true);
16
+ // Typescript
17
+ if (useTypeScriptServer) {
18
+ await buildTypeScript({ srcDir, watch: false });
28
19
 
29
- console.log(`Building your admin UI with ${green(env)} configuration ...`);
20
+ // Update the dir path for the next steps
21
+ buildDestDir = path.join(srcDir, 'dist');
22
+ }
30
23
 
31
- // Always remove the .cache and build folders
32
- await strapiAdmin.clean({ dir });
33
-
34
- ee({ dir });
35
-
36
- return strapiAdmin
37
- .build({
38
- forceBuild,
39
- dir,
40
- plugins,
41
- // front end build env is always production for now
42
- env: 'production',
43
- optimize: optimization,
44
- options: {
45
- backend: serverUrl,
46
- adminPath: addSlash(adminPath),
47
- },
48
- })
49
- .then(() => {
50
- console.log('Admin UI built successfully');
51
- })
52
- .catch(err => {
53
- console.error(err);
54
- process.exit(1);
55
- });
24
+ await buildAdmin({
25
+ buildDestDir,
26
+ forceBuild,
27
+ optimization,
28
+ srcDir,
29
+ });
56
30
  };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const { green } = require('chalk');
4
+
5
+ const strapiAdmin = require('@strapi/admin');
6
+ const { getConfigUrls } = require('@strapi/utils');
7
+
8
+ const ee = require('../../utils/ee');
9
+ const addSlash = require('../../utils/addSlash');
10
+ const strapi = require('../../index');
11
+ const getEnabledPlugins = require('../../core/loaders/plugins/get-enabled-plugins');
12
+
13
+ module.exports = async ({ buildDestDir, forceBuild = true, optimization, srcDir }) => {
14
+ const strapiInstance = strapi({
15
+ // Directories
16
+ appDir: srcDir,
17
+ distDir: buildDestDir,
18
+ // Options
19
+ autoReload: true,
20
+ serveAdminPanel: false,
21
+ });
22
+
23
+ const plugins = await getEnabledPlugins(strapiInstance);
24
+
25
+ const env = strapiInstance.config.get('environment');
26
+ const { serverUrl, adminPath } = getConfigUrls(strapiInstance.config, true);
27
+
28
+ console.log(`Building your admin UI with ${green(env)} configuration...`);
29
+
30
+ // Always remove the .cache and build folders
31
+ // FIXME the BE should remove the build dir and the admin should only
32
+ // be responsible of removing the .cache dir.
33
+ await strapiAdmin.clean({ appDir: srcDir, buildDestDir });
34
+
35
+ // @convly shouldn't we use the app dir here?
36
+ ee({ dir: buildDestDir });
37
+
38
+ return strapiAdmin
39
+ .build({
40
+ appDir: srcDir,
41
+ buildDestDir,
42
+ // front end build env is always production for now
43
+ env: 'production',
44
+ forceBuild,
45
+ plugins,
46
+ optimize: optimization,
47
+ options: {
48
+ backend: serverUrl,
49
+ adminPath: addSlash(adminPath),
50
+ },
51
+ })
52
+ .then(() => {
53
+ console.log('Admin UI built successfully');
54
+ })
55
+ .catch(err => {
56
+ console.error(err);
57
+ process.exit(1);
58
+ });
59
+ };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const buildAdmin = require('./admin');
4
+ const buildTypeScript = require('./typescript');
5
+
6
+ module.exports = {
7
+ buildAdmin,
8
+ buildTypeScript,
9
+ };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const tsUtils = require('@strapi/typescript-utils');
6
+
7
+ const cleanupDistDirectory = async distDir => {
8
+ if (!(await fs.pathExists(distDir))) {
9
+ return;
10
+ }
11
+
12
+ const dirContent = await fs.readdir(distDir);
13
+ const validFilenames = dirContent
14
+ // Ignore the admin build folder
15
+ .filter(filename => filename !== 'build');
16
+
17
+ for (const filename of validFilenames) {
18
+ await fs.remove(path.resolve(distDir, filename));
19
+ }
20
+ };
21
+
22
+ module.exports = async ({ srcDir, distDir, watch = false }) => {
23
+ const isTSProject = await tsUtils.isUsingTypeScript(srcDir);
24
+
25
+ if (!isTSProject) {
26
+ throw new Error(`tsconfig file not found in ${srcDir}`);
27
+ }
28
+
29
+ await cleanupDistDirectory(distDir);
30
+
31
+ return tsUtils.compile(srcDir, { watch });
32
+ };
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const path = require('path');
5
+ const tsUtils = require('@strapi/typescript-utils');
4
6
  const strapi = require('../index');
5
7
 
6
8
  const CHUNK_SIZE = 100;
@@ -12,7 +14,12 @@ const CHUNK_SIZE = 100;
12
14
  module.exports = async function({ file: filePath, pretty }) {
13
15
  const output = filePath ? fs.createWriteStream(filePath) : process.stdout;
14
16
 
15
- const app = await strapi().load();
17
+ const appDir = process.cwd();
18
+
19
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
20
+ const distDir = isTSProject ? path.join(appDir, 'dist') : appDir;
21
+
22
+ const app = await strapi({ appDir, distDir }).load();
16
23
 
17
24
  const count = await app.query('strapi::core-store').count();
18
25
 
@@ -1,7 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const path = require('path');
4
5
  const _ = require('lodash');
6
+ const tsUtils = require('@strapi/typescript-utils');
7
+
5
8
  const strapi = require('../index');
6
9
 
7
10
  /**
@@ -12,7 +15,12 @@ const strapi = require('../index');
12
15
  module.exports = async function({ file: filePath, strategy = 'replace' }) {
13
16
  const input = filePath ? fs.readFileSync(filePath) : await readStdin(process.stdin);
14
17
 
15
- const app = await strapi().load();
18
+ const appDir = process.cwd();
19
+
20
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
21
+ const distDir = isTSProject ? path.join(appDir, 'dist') : appDir;
22
+
23
+ const app = await strapi({ appDir, distDir }).load();
16
24
 
17
25
  let dataToImport;
18
26
  try {
@@ -1,14 +1,22 @@
1
1
  'use strict';
2
2
 
3
3
  const REPL = require('repl');
4
+ const path = require('path');
5
+ const tsUtils = require('@strapi/typescript-utils');
6
+
4
7
  const strapi = require('../index');
5
8
 
6
9
  /**
7
10
  * `$ strapi console`
8
11
  */
9
- module.exports = () => {
12
+ module.exports = async () => {
10
13
  // Now load up the Strapi framework for real.
11
- const app = strapi();
14
+ const appDir = process.cwd();
15
+
16
+ const isTSProject = await tsUtils.isUsingTypeScript(appDir);
17
+ const distDir = isTSProject ? path.join(appDir, 'dist') : appDir;
18
+
19
+ const app = await strapi({ appDir, distDir }).load();
12
20
 
13
21
  app.start().then(() => {
14
22
  const repl = REPL.start(app.config.info.name + ' > ' || 'strapi > '); // eslint-disable-line prefer-template