@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.
@@ -0,0 +1,199 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const chalk = require('chalk');
5
+ const ora = require('ora');
6
+ const ts = require('typescript');
7
+
8
+ /**
9
+ * @description Load a tsconfig.json file and return the parsed config
10
+ *
11
+ * @internal
12
+ *
13
+ * @type {(args: { cwd: string; path: string }) => Promise<ts.ParsedCommandLine>)}
14
+ */
15
+ const loadTsConfig = async ({ cwd, path }) => {
16
+ const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, path);
17
+
18
+ if (!configPath) {
19
+ throw new TSConfigNotFoundError(`could not find a valid '${path}'`);
20
+ }
21
+
22
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
23
+
24
+ return ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd);
25
+ };
26
+
27
+ class TSConfigNotFoundError extends Error {
28
+ // eslint-disable-next-line no-useless-constructor
29
+ constructor(message, options) {
30
+ super(message, options);
31
+ }
32
+
33
+ get code() {
34
+ return 'TS_CONFIG_NOT_FOUND';
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @description
40
+ *
41
+ * @internal
42
+ *
43
+ * @type {(args: { cwd: string; logger: import('../../utils/logger').Logger; outDir: string; tsconfig: ts.ParsedCommandLine }) => Promise<void>}
44
+ */
45
+ const buildTypes = ({ cwd, logger, outDir, tsconfig }) => {
46
+ const compilerOptions = {
47
+ ...tsconfig.options,
48
+ declaration: true,
49
+ declarationDir: outDir,
50
+ emitDeclarationOnly: true,
51
+ noEmit: false,
52
+ outDir,
53
+ };
54
+
55
+ const program = ts.createProgram(tsconfig.fileNames, compilerOptions);
56
+
57
+ const emitResult = program.emit();
58
+
59
+ const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
60
+
61
+ for (const diagnostic of allDiagnostics) {
62
+ if (diagnostic.file && diagnostic.start) {
63
+ const { line, character } = ts.getLineAndCharacterOfPosition(
64
+ diagnostic.file,
65
+ diagnostic.start
66
+ );
67
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
68
+
69
+ const file = path.relative(cwd, diagnostic.file.fileName);
70
+
71
+ const output = [
72
+ `${chalk.cyan(file)}:${chalk.cyan(line + 1)}:${chalk.cyan(character + 1)} - `,
73
+ `${chalk.gray(`TS${diagnostic.code}:`)} ${message}`,
74
+ ].join('');
75
+
76
+ if (diagnostic.category === ts.DiagnosticCategory.Error) {
77
+ logger.error(output);
78
+ }
79
+
80
+ if (diagnostic.category === ts.DiagnosticCategory.Warning) {
81
+ logger.warn(output);
82
+ }
83
+
84
+ if (diagnostic.category === ts.DiagnosticCategory.Message) {
85
+ logger.info(output);
86
+ }
87
+
88
+ if (diagnostic.category === ts.DiagnosticCategory.Suggestion) {
89
+ logger.info(output);
90
+ }
91
+ } else {
92
+ logger.info(ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'));
93
+ }
94
+ }
95
+
96
+ if (emitResult.emitSkipped) {
97
+ const errors = allDiagnostics.filter((diag) => diag.category === ts.DiagnosticCategory.Error);
98
+
99
+ if (errors.length) {
100
+ throw new Error('Failed to compile TypeScript definitions');
101
+ }
102
+ }
103
+ };
104
+
105
+ /**
106
+ * @typedef {Object} DtsTaskEntry
107
+ * @property {string} exportPath
108
+ * @property {string} sourcePath
109
+ * @property {string} targetPath
110
+ */
111
+
112
+ /**
113
+ * @typedef {Object} DtsTask
114
+ * @property {"build:dts"} type
115
+ * @property {DtsTaskEntry[]} entries
116
+ */
117
+
118
+ /**
119
+ * @type {import('./index').TaskHandler<DtsTask>}
120
+ */
121
+ const dtsTask = {
122
+ _spinner: null,
123
+ print(ctx, task) {
124
+ const entries = [
125
+ ' entries:',
126
+ ...task.entries.map((entry) =>
127
+ [
128
+ ` – `,
129
+ chalk.green(`${entry.importId} `),
130
+ `${chalk.cyan(entry.sourcePath)} ${chalk.gray('→')} ${chalk.cyan(entry.targetPath)}`,
131
+ ].join('')
132
+ ),
133
+ '',
134
+ ];
135
+
136
+ this._spinner = ora(`Building type files:\n`).start();
137
+
138
+ ctx.logger.log([...entries].join('\n'));
139
+ },
140
+ async run(ctx, task) {
141
+ try {
142
+ await Promise.all(
143
+ task.entries.map(async (entry) => {
144
+ const config = await loadTsConfig({
145
+ /**
146
+ * TODO: this will not scale and assumes all project sourcePaths are `src/index.ts`
147
+ * so we can go back to the "root" of the project...
148
+ */
149
+ cwd: path.join(ctx.cwd, entry.sourcePath, '..', '..'),
150
+ path: 'tsconfig.build.json',
151
+ }).catch((err) => {
152
+ if (err instanceof TSConfigNotFoundError) {
153
+ return undefined;
154
+ }
155
+
156
+ throw err;
157
+ });
158
+
159
+ if (config) {
160
+ ctx.logger.debug(`TS config for '${entry.sourcePath}': \n`, config);
161
+ } else {
162
+ ctx.logger.warn(
163
+ `You've added a types entry but no tsconfig.json was found for ${entry.targetPath}. Skipping...`
164
+ );
165
+
166
+ return;
167
+ }
168
+
169
+ const { outDir } = config.raw.compilerOptions;
170
+
171
+ if (!outDir) {
172
+ throw new Error("tsconfig.json is missing 'compilerOptions.outDir'");
173
+ }
174
+
175
+ await buildTypes({
176
+ cwd: ctx.cwd,
177
+ logger: ctx.logger,
178
+ outDir: path.relative(ctx.cwd, outDir),
179
+ tsconfig: config,
180
+ });
181
+ })
182
+ );
183
+
184
+ await this.success(ctx, task);
185
+ } catch (err) {
186
+ this.fail(ctx, task, err);
187
+ }
188
+ },
189
+ async success() {
190
+ this._spinner.succeed('Built type files');
191
+ },
192
+ async fail(ctx, task, err) {
193
+ this._spinner.fail('Failed to build type files');
194
+
195
+ throw err;
196
+ },
197
+ };
198
+
199
+ module.exports = { dtsTask };
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const { dtsTask } = require('./dts');
4
+ const { viteTask } = require('./vite');
5
+
6
+ /**
7
+ * @template Task
8
+ * @param {Task}
9
+ * @returns {Task}
10
+ *
11
+ * @typedef {Object} TaskHandler
12
+ * @property {(ctx: import("../packages").BuildContext, task: Task) => import('ora').Ora} print
13
+ * @property {(ctx: import("../packages").BuildContext, task: Task) => Promise<void>} run
14
+ * @property {(ctx: import("../packages").BuildContext, task: Task) => Promise<void>} success
15
+ * @property {(ctx: import("../packages").BuildContext, task: Task, err: unknown) => Promise<void>} fail
16
+ * @property {import('ora').Ora | null} _spinner
17
+ */
18
+
19
+ /**
20
+ * @type {{ "build:js": TaskHandler<import("./vite").ViteTask>; "build:dts": TaskHandler<import("./dts").DtsTask>; }}}
21
+ */
22
+ const buildTaskHandlers = {
23
+ 'build:js': viteTask,
24
+ 'build:dts': dtsTask,
25
+ };
26
+
27
+ module.exports = {
28
+ buildTaskHandlers,
29
+ };
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { build, createLogger } = require('vite');
5
+ const react = require('@vitejs/plugin-react');
6
+ const ora = require('ora');
7
+ const chalk = require('chalk');
8
+
9
+ /**
10
+ * @internal
11
+ *
12
+ * @type {(ctx: import('../packages').BuildContext, task: ViteTask) => import('vite').UserConfig}
13
+ */
14
+ const resolveViteConfig = (ctx, task) => {
15
+ const { cwd, distPath, targets, external, extMap, pkg } = ctx;
16
+ const { entries, format, output, runtime } = task;
17
+ const outputExt = extMap[pkg.type || 'commonjs'][format];
18
+ const outDir = path.relative(cwd, distPath);
19
+
20
+ const customLogger = createLogger();
21
+ customLogger.warn = (msg) => ctx.logger.warn(msg);
22
+ customLogger.warnOnce = (msg) => ctx.logger.warn(msg);
23
+ customLogger.error = (msg) => ctx.logger.error(msg);
24
+
25
+ /**
26
+ * @type {import('vite').InlineConfig}
27
+ */
28
+ const config = {
29
+ configFile: false,
30
+ root: cwd,
31
+ mode: 'production',
32
+ logLevel: 'warn',
33
+ clearScreen: false,
34
+ customLogger,
35
+ build: {
36
+ sourcemap: true,
37
+ /**
38
+ * The task runner will clear this for us
39
+ */
40
+ emptyOutDir: false,
41
+ target: targets[runtime],
42
+ outDir,
43
+ lib: {
44
+ entry: entries.map((e) => e.entry),
45
+ formats: [format],
46
+ /**
47
+ * this enforces the file name to match what the output we've
48
+ * determined from the package.json exports.
49
+ */
50
+ fileName() {
51
+ return `${path.relative(outDir, output).replace(/\.[^/.]+$/, '')}${outputExt}`;
52
+ },
53
+ },
54
+ rollupOptions: {
55
+ external,
56
+ output: {
57
+ chunkFileNames() {
58
+ const parts = outputExt.split('.');
59
+
60
+ if (parts.length === 3) {
61
+ return `_chunks/[name]-[hash].${parts[2]}`;
62
+ }
63
+
64
+ return `_chunks/[name]-[hash]${outputExt}`;
65
+ },
66
+ },
67
+ },
68
+ },
69
+ /**
70
+ * We _could_ omit this, but we'd need to introduce the
71
+ * concept of a custom config for the scripts straight away
72
+ *
73
+ * and since this is isolated to the Strapi CLI, we can make
74
+ * some assumptions and add some weight until we move it outside.
75
+ */
76
+ plugins: runtime === 'node' ? [] : [react()],
77
+ };
78
+
79
+ return config;
80
+ };
81
+
82
+ /**
83
+ * @typedef {Object} ViteTaskEntry
84
+ * @property {string} path
85
+ * @property {string} entry
86
+ */
87
+
88
+ /**
89
+ * @typedef {Object} ViteTask
90
+ * @property {"build:js"} type
91
+ * @property {ViteTaskEntry[]} entries
92
+ * @property {string} format
93
+ * @property {string} output
94
+ * @property {keyof import('../packages').Targets} runtime
95
+ */
96
+
97
+ /**
98
+ * @type {import('./index').TaskHandler<ViteTask>}
99
+ */
100
+ const viteTask = {
101
+ _spinner: null,
102
+ print(ctx, task) {
103
+ const targetLines = [
104
+ ' target:',
105
+ ...ctx.targets[task.runtime].map((t) => chalk.cyan(` - ${t}`)),
106
+ ];
107
+ const entries = [
108
+ ' entries:',
109
+ ...task.entries.map((entry) =>
110
+ [
111
+ ` – `,
112
+ chalk.green(`${path.join(ctx.pkg.name, entry.path)}: `),
113
+ `${chalk.cyan(entry.entry)} ${chalk.gray('→')} ${chalk.cyan(task.output)}`,
114
+ ].join('')
115
+ ),
116
+ ];
117
+
118
+ this._spinner = ora(`Building javascript files:\n`).start();
119
+
120
+ ctx.logger.log([` format: ${task.format}`, ...targetLines, ...entries].join('\n'));
121
+ },
122
+ async run(ctx, task) {
123
+ try {
124
+ const config = resolveViteConfig(ctx, task);
125
+ ctx.logger.debug('Vite config: \n', config);
126
+ await build(config);
127
+ await this.success(ctx, task);
128
+ } catch (err) {
129
+ this.fail(ctx, task, err);
130
+ }
131
+ },
132
+ async success() {
133
+ this._spinner.succeed('Built javascript files');
134
+ },
135
+ async fail(ctx, task, err) {
136
+ this._spinner.fail('Failed to build javascript files');
137
+
138
+ throw err;
139
+ },
140
+ };
141
+
142
+ module.exports = {
143
+ viteTask,
144
+ };
@@ -19,7 +19,7 @@ const cleanupDistDirectory = async (distDir) => {
19
19
  }
20
20
  };
21
21
 
22
- module.exports = async ({ srcDir, distDir, watch = false }) => {
22
+ module.exports = async ({ srcDir, distDir, ignoreDiagnostics = false }) => {
23
23
  const isTSProject = await tsUtils.isUsingTypeScript(srcDir);
24
24
 
25
25
  if (!isTSProject) {
@@ -28,5 +28,5 @@ module.exports = async ({ srcDir, distDir, watch = false }) => {
28
28
 
29
29
  await cleanupDistDirectory(distDir);
30
30
 
31
- return tsUtils.compile(srcDir, { watch });
31
+ return tsUtils.compile(srcDir, { configOptions: { ignoreDiagnostics } });
32
32
  };
@@ -20,6 +20,7 @@ const strapiCommands = {
20
20
  install: require('./actions/install/command'),
21
21
  'middlewares/list': require('./actions/middlewares/list/command'),
22
22
  new: require('./actions/new/command'),
23
+ 'plugin/build': require('./actions/plugin/build-command/command'),
23
24
  'policies/list': require('./actions/policies/list/command'),
24
25
  report: require('./actions/report/command'),
25
26
  'routes/list': require('./actions/routes/list/command'),
@@ -8,6 +8,9 @@ const { yellow, red, green } = require('chalk');
8
8
  const { isString, isArray } = require('lodash/fp');
9
9
  const resolveCwd = require('resolve-cwd');
10
10
  const { has } = require('lodash/fp');
11
+ const { prompt } = require('inquirer');
12
+ const boxen = require('boxen');
13
+ const chalk = require('chalk');
11
14
 
12
15
  const bytesPerKb = 1024;
13
16
  const sizes = ['B ', 'KB', 'MB', 'GB', 'TB', 'PB'];
@@ -121,7 +124,10 @@ const assertCwdContainsStrapiProject = (name) => {
121
124
 
122
125
  try {
123
126
  const pkgJSON = require(`${process.cwd()}/package.json`);
124
- if (!has('dependencies.@strapi/strapi', pkgJSON)) {
127
+ if (
128
+ !has('dependencies.@strapi/strapi', pkgJSON) &&
129
+ !has('devDependencies.@strapi/strapi', pkgJSON)
130
+ ) {
125
131
  logErrorAndExit(name);
126
132
  }
127
133
  } catch (err) {
@@ -156,6 +162,51 @@ const getLocalScript =
156
162
  });
157
163
  };
158
164
 
165
+ /**
166
+ * @description Notify users this is an experimental command and get them to approve first
167
+ * this can be opted out by passing `yes` as a property of the args object.
168
+ *
169
+ * @type {(args?: { force?: boolean }) => Promise<void>}
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * const { notifyExperimentalCommand } = require('../utils/helpers');
174
+ *
175
+ * const myCommand = async ({ force }) => {
176
+ * await notifyExperimentalCommand({ force });
177
+ * }
178
+ * ```
179
+ */
180
+ const notifyExperimentalCommand = async ({ force } = {}) => {
181
+ console.log(
182
+ boxen(
183
+ `The ${chalk.bold(
184
+ chalk.underline('plugin:build')
185
+ )} command is considered experimental, use at your own risk.`,
186
+ {
187
+ title: 'Warning',
188
+ padding: 1,
189
+ margin: 1,
190
+ align: 'center',
191
+ borderColor: 'yellow',
192
+ borderStyle: 'bold',
193
+ }
194
+ )
195
+ );
196
+
197
+ if (!force) {
198
+ const { confirmed } = await prompt({
199
+ type: 'confirm',
200
+ name: 'confirmed',
201
+ message: 'Do you want to continue?',
202
+ });
203
+
204
+ if (!confirmed) {
205
+ process.exit(0);
206
+ }
207
+ }
208
+ };
209
+
159
210
  module.exports = {
160
211
  exitWith,
161
212
  assertUrlHasProtocol,
@@ -163,4 +214,5 @@ module.exports = {
163
214
  readableBytes,
164
215
  getLocalScript,
165
216
  assertCwdContainsStrapiProject,
217
+ notifyExperimentalCommand,
166
218
  };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * @typedef {{ silent?: boolean; debug?: boolean; timestamp?: boolean; }} LoggerOptions
7
+ */
8
+
9
+ /**
10
+ * @typedef {object} Logger
11
+ * @property {number} warnings
12
+ * @property {number} errors
13
+ * @property {(...args: any[]) => void} debug
14
+ * @property {(...args: any[]) => void} info
15
+ * @property {(...args: any[]) => void} warn
16
+ * @property {(...args: any[]) => void} error
17
+ * @property {(...args: any[]) => void} log
18
+ */
19
+
20
+ /**
21
+ * @type {(options: LoggerOptions) => Logger}
22
+ */
23
+ const createLogger = (options = {}) => {
24
+ const { silent = false, debug = false, timestamp = true } = options;
25
+
26
+ const state = { errors: 0, warning: 0 };
27
+
28
+ return {
29
+ get warnings() {
30
+ return state.warning;
31
+ },
32
+
33
+ get errors() {
34
+ return state.errors;
35
+ },
36
+
37
+ debug(...args) {
38
+ if (silent || !debug) {
39
+ return;
40
+ }
41
+
42
+ console.log(
43
+ chalk.cyan(`[DEBUG]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`),
44
+ ...args
45
+ );
46
+ },
47
+
48
+ info(...args) {
49
+ if (silent) {
50
+ return;
51
+ }
52
+
53
+ console.info(
54
+ chalk.blue(`[INFO]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`),
55
+ ...args
56
+ );
57
+ },
58
+
59
+ log(...args) {
60
+ if (silent) {
61
+ return;
62
+ }
63
+
64
+ console.info(chalk.blue(`${timestamp ? `\t[${new Date().toISOString()}]` : ''}`), ...args);
65
+ },
66
+
67
+ warn(...args) {
68
+ state.warning += 1;
69
+
70
+ if (silent) {
71
+ return;
72
+ }
73
+
74
+ console.warn(
75
+ chalk.yellow(`[WARN]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`),
76
+ ...args
77
+ );
78
+ },
79
+
80
+ error(...args) {
81
+ state.errors += 1;
82
+
83
+ if (silent) {
84
+ return;
85
+ }
86
+
87
+ console.error(
88
+ chalk.red(`[ERROR]${timestamp ? `\t[${new Date().toISOString()}]` : ''}`),
89
+ ...args
90
+ );
91
+ },
92
+ };
93
+ };
94
+
95
+ module.exports = {
96
+ createLogger,
97
+ };