create-strapi-app 5.9.0 → 5.10.1

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/dist/index.mjs CHANGED
@@ -1,452 +1,482 @@
1
- import path, { join as join$1, resolve, basename } from "node:path";
2
- import os$1 from "node:os";
3
- import chalk from "chalk";
4
- import commander from "commander";
5
- import crypto, { randomUUID } from "crypto";
6
- import fse from "fs-extra";
7
- import inquirer from "inquirer";
8
- import { services, cli } from "@strapi/cloud-cli";
9
- import execa from "execa";
10
- import url from "node:url";
11
- import { Readable } from "node:stream";
12
- import { pipeline } from "node:stream/promises";
13
- import * as tar from "tar";
14
- import retry from "async-retry";
15
- import os from "os";
16
- import _, { kebabCase, merge } from "lodash";
17
- import { join } from "path";
18
- import semver from "semver";
19
- import { machineIdSync } from "node-machine-id";
1
+ import path, { join as join$1, resolve, basename } from 'node:path';
2
+ import os$1 from 'node:os';
3
+ import chalk from 'chalk';
4
+ import commander from 'commander';
5
+ import crypto, { randomUUID } from 'crypto';
6
+ import fse from 'fs-extra';
7
+ import inquirer from 'inquirer';
8
+ import { services, cli } from '@strapi/cloud-cli';
9
+ import execa from 'execa';
10
+ import url from 'node:url';
11
+ import { Readable } from 'node:stream';
12
+ import { pipeline } from 'node:stream/promises';
13
+ import * as tar from 'tar';
14
+ import retry from 'async-retry';
15
+ import os from 'os';
16
+ import _, { kebabCase, merge } from 'lodash';
17
+ import { join } from 'path';
18
+ import semver from 'semver';
19
+ import { machineIdSync } from 'node-machine-id';
20
+
20
21
  async function directory() {
21
- const { directory: directory2 } = await inquirer.prompt([
22
- {
23
- type: "input",
24
- default: "my-strapi-project",
25
- name: "directory",
26
- message: "What is the name of your project?"
27
- }
28
- ]);
29
- return directory2;
22
+ const { directory } = await inquirer.prompt([
23
+ {
24
+ type: 'input',
25
+ default: 'my-strapi-project',
26
+ name: 'directory',
27
+ message: 'What is the name of your project?'
28
+ }
29
+ ]);
30
+ return directory;
30
31
  }
31
32
  async function typescript() {
32
- const { useTypescript } = await inquirer.prompt([
33
- {
34
- type: "confirm",
35
- name: "useTypescript",
36
- message: "Start with Typescript?",
37
- default: true
38
- }
39
- ]);
40
- return useTypescript;
33
+ const { useTypescript } = await inquirer.prompt([
34
+ {
35
+ type: 'confirm',
36
+ name: 'useTypescript',
37
+ message: 'Start with Typescript?',
38
+ default: true
39
+ }
40
+ ]);
41
+ return useTypescript;
41
42
  }
42
43
  async function example() {
43
- const { useExample } = await inquirer.prompt([
44
- {
45
- type: "confirm",
46
- name: "useExample",
47
- message: "Start with an example structure & data?",
48
- default: false
49
- }
50
- ]);
51
- return useExample;
44
+ const { useExample } = await inquirer.prompt([
45
+ {
46
+ type: 'confirm',
47
+ name: 'useExample',
48
+ message: 'Start with an example structure & data?',
49
+ default: false
50
+ }
51
+ ]);
52
+ return useExample;
52
53
  }
53
54
  async function gitInit() {
54
- const { gitInit: gitInit2 } = await inquirer.prompt([
55
- {
56
- type: "confirm",
57
- name: "gitInit",
58
- message: "Initialize a git repository?",
59
- default: true
60
- }
61
- ]);
62
- return gitInit2;
55
+ const { gitInit } = await inquirer.prompt([
56
+ {
57
+ type: 'confirm',
58
+ name: 'gitInit',
59
+ message: 'Initialize a git repository?',
60
+ default: true
61
+ }
62
+ ]);
63
+ return gitInit;
63
64
  }
64
65
  async function installDependencies(packageManager) {
65
- const { installDependencies: installDependencies2 } = await inquirer.prompt([
66
- {
67
- type: "confirm",
68
- name: "installDependencies",
69
- message: `Install dependencies with ${packageManager}?`,
70
- default: true
71
- }
72
- ]);
73
- return installDependencies2;
66
+ const { installDependencies } = await inquirer.prompt([
67
+ {
68
+ type: 'confirm',
69
+ name: 'installDependencies',
70
+ message: `Install dependencies with ${packageManager}?`,
71
+ default: true
72
+ }
73
+ ]);
74
+ return installDependencies;
74
75
  }
76
+
77
+ // TODO: move styles to API
75
78
  const supportedStyles = {
76
- magentaBright: chalk.magentaBright,
77
- blueBright: chalk.blueBright,
78
- yellowBright: chalk.yellowBright,
79
- green: chalk.green,
80
- red: chalk.red,
81
- bold: chalk.bold,
82
- italic: chalk.italic
79
+ magentaBright: chalk.magentaBright,
80
+ blueBright: chalk.blueBright,
81
+ yellowBright: chalk.yellowBright,
82
+ green: chalk.green,
83
+ red: chalk.red,
84
+ bold: chalk.bold,
85
+ italic: chalk.italic
83
86
  };
84
87
  function parseToChalk(template) {
85
- let result = template;
86
- for (const [color, chalkFunction] of Object.entries(supportedStyles)) {
87
- const regex = new RegExp(`{${color}}(.*?){/${color}}`, "g");
88
- result = result.replace(regex, (_2, p1) => chalkFunction(p1.trim()));
89
- }
90
- return result;
88
+ let result = template;
89
+ for (const [color, chalkFunction] of Object.entries(supportedStyles)){
90
+ const regex = new RegExp(`{${color}}(.*?){/${color}}`, 'g');
91
+ result = result.replace(regex, (_, p1)=>chalkFunction(p1.trim()));
92
+ }
93
+ return result;
91
94
  }
95
+
92
96
  function assertCloudError(e) {
93
- if (e.response === void 0) {
94
- throw Error("Expected CloudError");
95
- }
97
+ if (e.response === undefined) {
98
+ throw Error('Expected CloudError');
99
+ }
96
100
  }
97
101
  async function handleCloudLogin() {
98
- const logger2 = services.createLogger({
99
- silent: false,
100
- debug: process.argv.includes("--debug"),
101
- timestamp: false
102
- });
103
- const cloudApiService = await services.cloudApiFactory({ logger: logger2 });
104
- const defaultErrorMessage = "An error occurred while trying to interact with Strapi Cloud. Use strapi deploy command once the project is generated.";
105
- try {
106
- const { data: config } = await cloudApiService.config();
107
- logger2.log(parseToChalk(config.projectCreation.introText));
108
- } catch (e) {
109
- logger2.debug(e);
110
- logger2.error(defaultErrorMessage);
111
- return;
112
- }
113
- const { userChoice } = await inquirer.prompt([
114
- {
115
- type: "list",
116
- name: "userChoice",
117
- message: `Please log in or sign up.`,
118
- choices: ["Login/Sign up", "Skip"]
119
- }
120
- ]);
121
- if (userChoice !== "Skip") {
122
- const cliContext = {
123
- logger: logger2,
124
- cwd: process.cwd()
125
- };
102
+ const logger = services.createLogger({
103
+ silent: false,
104
+ debug: process.argv.includes('--debug'),
105
+ timestamp: false
106
+ });
107
+ const cloudApiService = await services.cloudApiFactory({
108
+ logger
109
+ });
110
+ const defaultErrorMessage = 'An error occurred while trying to interact with Strapi Cloud. Use strapi deploy command once the project is generated.';
126
111
  try {
127
- await cli.login.action(cliContext);
112
+ const { data: config } = await cloudApiService.config();
113
+ logger.log(parseToChalk(config.projectCreation.introText));
128
114
  } catch (e) {
129
- logger2.debug(e);
130
- try {
131
- assertCloudError(e);
132
- if (e.response.status === 403) {
133
- const message = typeof e.response.data === "string" ? e.response.data : "We are sorry, but we are not able to log you into Strapi Cloud at the moment.";
134
- logger2.warn(message);
135
- return;
115
+ logger.debug(e);
116
+ logger.error(defaultErrorMessage);
117
+ return;
118
+ }
119
+ const { userChoice } = await inquirer.prompt([
120
+ {
121
+ type: 'list',
122
+ name: 'userChoice',
123
+ message: `Please log in or sign up.`,
124
+ choices: [
125
+ 'Login/Sign up',
126
+ 'Skip'
127
+ ]
128
+ }
129
+ ]);
130
+ if (userChoice !== 'Skip') {
131
+ const cliContext = {
132
+ logger,
133
+ cwd: process.cwd()
134
+ };
135
+ try {
136
+ await cli.login.action(cliContext);
137
+ } catch (e) {
138
+ logger.debug(e);
139
+ try {
140
+ assertCloudError(e);
141
+ if (e.response.status === 403) {
142
+ const message = typeof e.response.data === 'string' ? e.response.data : 'We are sorry, but we are not able to log you into Strapi Cloud at the moment.';
143
+ logger.warn(message);
144
+ return;
145
+ }
146
+ } catch (e) {
147
+ /* empty */ }
148
+ logger.error(defaultErrorMessage);
136
149
  }
137
- } catch (e2) {
138
- }
139
- logger2.error(defaultErrorMessage);
140
150
  }
141
- }
142
151
  }
143
- const stripTrailingSlash = (str) => {
144
- return str.endsWith("/") ? str.slice(0, -1) : str;
152
+
153
+ const stripTrailingSlash = (str)=>{
154
+ return str.endsWith('/') ? str.slice(0, -1) : str;
145
155
  };
156
+ // Merge template with new project being created
146
157
  async function copyTemplate(scope, rootPath) {
147
- const { template } = scope;
148
- if (!template) {
149
- throw new Error("Missing template or example app option");
150
- }
151
- if (await isOfficialTemplate(template, scope.templateBranch)) {
152
- await retry(
153
- () => downloadGithubRepo(rootPath, {
154
- owner: "strapi",
155
- repo: "strapi",
156
- branch: scope.templateBranch,
157
- subPath: `templates/${template}`
158
- }),
159
- {
160
- retries: 3,
161
- onRetry(err, attempt) {
162
- console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
163
- }
164
- }
165
- );
166
- return;
167
- }
168
- if (isLocalTemplate(template)) {
169
- const filePath = template.startsWith("file://") ? url.fileURLToPath(template) : template;
170
- await fse.copy(filePath, rootPath);
171
- }
172
- if (isGithubShorthand(template)) {
173
- const [owner, repo, ...pathSegments] = template.split("/");
174
- const subPath = pathSegments.length ? pathSegments.join("/") : scope.templatePath;
175
- await retry(
176
- () => downloadGithubRepo(rootPath, { owner, repo, branch: scope.templateBranch, subPath }),
177
- {
178
- retries: 3,
179
- onRetry(err, attempt) {
180
- console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
181
- }
182
- }
183
- );
184
- return;
185
- }
186
- if (isGithubRepo(template)) {
187
- const url2 = new URL(template);
188
- const [owner, repo, t, branch, ...pathSegments] = stripTrailingSlash(
189
- url2.pathname.slice(1)
190
- ).split("/");
191
- if (t !== void 0 && t !== "tree") {
192
- throw new Error(`Invalid GitHub template URL: ${template}`);
193
- }
194
- if (scope.templateBranch) {
195
- await retry(
196
- () => downloadGithubRepo(rootPath, {
197
- owner,
198
- repo,
199
- branch: scope.templateBranch,
200
- subPath: scope.templatePath
201
- }),
202
- {
203
- retries: 3,
204
- onRetry(err, attempt) {
205
- console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
206
- }
158
+ const { template } = scope;
159
+ if (!template) {
160
+ throw new Error('Missing template or example app option');
161
+ }
162
+ if (await isOfficialTemplate(template, scope.templateBranch)) {
163
+ await retry(()=>downloadGithubRepo(rootPath, {
164
+ owner: 'strapi',
165
+ repo: 'strapi',
166
+ branch: scope.templateBranch,
167
+ subPath: `templates/${template}`
168
+ }), {
169
+ retries: 3,
170
+ onRetry (err, attempt) {
171
+ console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
172
+ }
173
+ });
174
+ return;
175
+ }
176
+ if (isLocalTemplate(template)) {
177
+ const filePath = template.startsWith('file://') ? url.fileURLToPath(template) : template;
178
+ await fse.copy(filePath, rootPath);
179
+ }
180
+ if (isGithubShorthand(template)) {
181
+ const [owner, repo, ...pathSegments] = template.split('/');
182
+ const subPath = pathSegments.length ? pathSegments.join('/') : scope.templatePath;
183
+ await retry(()=>downloadGithubRepo(rootPath, {
184
+ owner,
185
+ repo,
186
+ branch: scope.templateBranch,
187
+ subPath
188
+ }), {
189
+ retries: 3,
190
+ onRetry (err, attempt) {
191
+ console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
192
+ }
193
+ });
194
+ return;
195
+ }
196
+ if (isGithubRepo(template)) {
197
+ const url = new URL(template);
198
+ const [owner, repo, t, branch, ...pathSegments] = stripTrailingSlash(url.pathname.slice(1)).split('/');
199
+ if (t !== undefined && t !== 'tree') {
200
+ throw new Error(`Invalid GitHub template URL: ${template}`);
207
201
  }
208
- );
209
- return;
210
- }
211
- await retry(
212
- () => downloadGithubRepo(rootPath, {
213
- owner,
214
- repo,
215
- branch: decodeURIComponent(branch) ?? scope.templateBranch,
216
- subPath: pathSegments.length ? decodeURIComponent(pathSegments.join("/")) : scope.templatePath
217
- }),
218
- {
219
- retries: 3,
220
- onRetry(err, attempt) {
221
- console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
202
+ if (scope.templateBranch) {
203
+ await retry(()=>downloadGithubRepo(rootPath, {
204
+ owner,
205
+ repo,
206
+ branch: scope.templateBranch,
207
+ subPath: scope.templatePath
208
+ }), {
209
+ retries: 3,
210
+ onRetry (err, attempt) {
211
+ console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
212
+ }
213
+ });
214
+ return;
222
215
  }
223
- }
224
- );
225
- throw new Error(`Invalid GitHub template URL: ${template}`);
226
- }
216
+ await retry(()=>downloadGithubRepo(rootPath, {
217
+ owner,
218
+ repo,
219
+ branch: decodeURIComponent(branch) ?? scope.templateBranch,
220
+ subPath: pathSegments.length ? decodeURIComponent(pathSegments.join('/')) : scope.templatePath
221
+ }), {
222
+ retries: 3,
223
+ onRetry (err, attempt) {
224
+ console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
225
+ }
226
+ });
227
+ throw new Error(`Invalid GitHub template URL: ${template}`);
228
+ }
227
229
  }
228
230
  async function downloadGithubRepo(rootPath, { owner, repo, branch, subPath }) {
229
- const filePath = subPath ? subPath.split("/").join(path.posix.sep) : null;
230
- let checkContentUrl = `https://api.github.com/repos/${owner}/${repo}/contents`;
231
- if (filePath) {
232
- checkContentUrl = `${checkContentUrl}/${filePath}`;
233
- }
234
- if (branch) {
235
- checkContentUrl = `${checkContentUrl}?ref=${branch}`;
236
- }
237
- const checkRes = await fetch(checkContentUrl, {
238
- method: "HEAD"
239
- });
240
- if (checkRes.status !== 200) {
241
- throw new Error(
242
- `Could not find a template at https://github.com/${owner}/${repo}${branch ? ` on branch ${branch}` : ""}${filePath ? ` at path ${filePath}` : ""}`
243
- );
244
- }
245
- let url2 = `https://api.github.com/repos/${owner}/${repo}/tarball`;
246
- if (branch) {
247
- url2 = `${url2}/${branch}`;
248
- }
249
- const res = await fetch(url2);
250
- if (!res.body) {
251
- throw new Error(`Failed to download ${url2}`);
252
- }
253
- await pipeline(
254
- // @ts-expect-error - Readable is not a valid source
255
- Readable.fromWeb(res.body),
256
- tar.x({
257
- cwd: rootPath,
258
- strip: filePath ? filePath.split("/").length + 1 : 1,
259
- filter(path2) {
260
- if (filePath) {
261
- return path2.split("/").slice(1).join("/").startsWith(filePath);
231
+ const filePath = subPath ? subPath.split('/').join(path.posix.sep) : null;
232
+ let checkContentUrl = `https://api.github.com/repos/${owner}/${repo}/contents`;
233
+ if (filePath) {
234
+ checkContentUrl = `${checkContentUrl}/${filePath}`;
235
+ }
236
+ if (branch) {
237
+ checkContentUrl = `${checkContentUrl}?ref=${branch}`;
238
+ }
239
+ const checkRes = await fetch(checkContentUrl, {
240
+ method: 'HEAD'
241
+ });
242
+ if (checkRes.status !== 200) {
243
+ throw new Error(`Could not find a template at https://github.com/${owner}/${repo}${branch ? ` on branch ${branch}` : ''}${filePath ? ` at path ${filePath}` : ''}`);
244
+ }
245
+ let url = `https://api.github.com/repos/${owner}/${repo}/tarball`;
246
+ if (branch) {
247
+ url = `${url}/${branch}`;
248
+ }
249
+ const res = await fetch(url);
250
+ if (!res.body) {
251
+ throw new Error(`Failed to download ${url}`);
252
+ }
253
+ await pipeline(// @ts-expect-error - Readable is not a valid source
254
+ Readable.fromWeb(res.body), tar.x({
255
+ cwd: rootPath,
256
+ strip: filePath ? filePath.split('/').length + 1 : 1,
257
+ filter (path) {
258
+ if (filePath) {
259
+ return path.split('/').slice(1).join('/').startsWith(filePath);
260
+ }
261
+ return true;
262
262
  }
263
- return true;
264
- }
265
- })
266
- );
263
+ }));
267
264
  }
268
265
  function isLocalTemplate(template) {
269
- return template.startsWith("file://") || fse.existsSync(path.isAbsolute(template) ? template : path.resolve(process.cwd(), template));
266
+ return template.startsWith('file://') || fse.existsSync(path.isAbsolute(template) ? template : path.resolve(process.cwd(), template));
270
267
  }
271
268
  function isGithubShorthand(value) {
272
- if (isValidUrl(value)) {
273
- return false;
274
- }
275
- return /^[\w-]+\/[\w-.]+(\/[\w-.]+)*$/.test(value);
269
+ if (isValidUrl(value)) {
270
+ return false;
271
+ }
272
+ return /^[\w-]+\/[\w-.]+(\/[\w-.]+)*$/.test(value);
276
273
  }
277
274
  function isGithubRepo(value) {
278
- try {
279
- const url2 = new URL(value);
280
- return url2.origin === "https://github.com";
281
- } catch {
282
- return false;
283
- }
275
+ try {
276
+ const url = new URL(value);
277
+ return url.origin === 'https://github.com';
278
+ } catch {
279
+ return false;
280
+ }
284
281
  }
285
282
  function isValidUrl(value) {
286
- try {
287
- new URL(value);
288
- return true;
289
- } catch {
290
- return false;
291
- }
283
+ try {
284
+ // eslint-disable-next-line no-new
285
+ new URL(value);
286
+ return true;
287
+ } catch {
288
+ return false;
289
+ }
292
290
  }
291
+ const OFFICIAL_NAME_REGEX = /^[a-zA-Z]*$/;
293
292
  async function isOfficialTemplate(template, branch) {
294
- if (isValidUrl(template)) {
295
- return false;
296
- }
297
- const res = await fetch(
298
- `https://api.github.com/repos/strapi/strapi/contents/templates/${template}?${branch ? `ref=${branch}` : ""}`,
299
- { method: "HEAD" }
300
- );
301
- return res.status === 200;
293
+ if (isValidUrl(template) || !OFFICIAL_NAME_REGEX.test(template)) {
294
+ return false;
295
+ }
296
+ const res = await fetch(`https://api.github.com/repos/strapi/strapi/contents/templates/${template}?${branch ? `ref=${branch}` : ''}`, {
297
+ method: 'HEAD'
298
+ });
299
+ return res.status === 200;
302
300
  }
301
+
303
302
  async function isInGitRepository(rootDir) {
304
- try {
305
- await execa("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "ignore", cwd: rootDir });
306
- return true;
307
- } catch (_2) {
308
- return false;
309
- }
303
+ try {
304
+ await execa('git', [
305
+ 'rev-parse',
306
+ '--is-inside-work-tree'
307
+ ], {
308
+ stdio: 'ignore',
309
+ cwd: rootDir
310
+ });
311
+ return true;
312
+ } catch (_) {
313
+ return false;
314
+ }
310
315
  }
311
316
  async function isInMercurialRepository(rootDir) {
312
- try {
313
- await execa("hg", ["-cwd", ".", "root"], { stdio: "ignore", cwd: rootDir });
314
- return true;
315
- } catch (_2) {
316
- return false;
317
- }
317
+ try {
318
+ await execa('hg', [
319
+ '-cwd',
320
+ '.',
321
+ 'root'
322
+ ], {
323
+ stdio: 'ignore',
324
+ cwd: rootDir
325
+ });
326
+ return true;
327
+ } catch (_) {
328
+ return false;
329
+ }
318
330
  }
319
331
  async function tryGitInit(rootDir) {
320
- try {
321
- await execa("git", ["--version"], { stdio: "ignore" });
322
- if (await isInGitRepository(rootDir) || await isInMercurialRepository(rootDir)) {
323
- return false;
324
- }
325
- await execa("git", ["init"], { stdio: "ignore", cwd: rootDir });
326
- await execa("git", ["add", "."], { stdio: "ignore", cwd: rootDir });
327
- await execa("git", ["commit", "-m", "Initial commit from Strapi"], {
328
- stdio: "ignore",
329
- cwd: rootDir
330
- });
331
- return true;
332
- } catch (e) {
333
- console.error("Error while trying to initialize git:", e);
334
- return false;
335
- }
332
+ try {
333
+ await execa('git', [
334
+ '--version'
335
+ ], {
336
+ stdio: 'ignore'
337
+ });
338
+ if (await isInGitRepository(rootDir) || await isInMercurialRepository(rootDir)) {
339
+ return false;
340
+ }
341
+ await execa('git', [
342
+ 'init'
343
+ ], {
344
+ stdio: 'ignore',
345
+ cwd: rootDir
346
+ });
347
+ await execa('git', [
348
+ 'add',
349
+ '.'
350
+ ], {
351
+ stdio: 'ignore',
352
+ cwd: rootDir
353
+ });
354
+ await execa('git', [
355
+ 'commit',
356
+ '-m',
357
+ 'Initial commit from Strapi'
358
+ ], {
359
+ stdio: 'ignore',
360
+ cwd: rootDir
361
+ });
362
+ return true;
363
+ } catch (e) {
364
+ console.error('Error while trying to initialize git:', e);
365
+ return false;
366
+ }
336
367
  }
368
+
369
+ // Add properties from the package.json strapi key in the metadata
337
370
  function addPackageJsonStrapiMetadata(metadata, scope) {
338
- const { packageJsonStrapi = {} } = scope;
339
- return _.defaults(metadata, packageJsonStrapi);
371
+ const { packageJsonStrapi = {} } = scope;
372
+ return _.defaults(metadata, packageJsonStrapi);
340
373
  }
341
- const boolToString = (value) => (value === true).toString();
342
- const getProperties = (scope, error) => {
343
- const eventProperties = {
344
- error: typeof error === "string" ? error : error && error.message
345
- };
346
- const userProperties = {
347
- os: os.type(),
348
- osPlatform: os.platform(),
349
- osArch: os.arch(),
350
- osRelease: os.release(),
351
- nodeVersion: process.versions.node
352
- };
353
- const groupProperties = {
354
- version: scope.strapiVersion,
355
- docker: scope.docker,
356
- useYarn: scope.packageManager === "yarn",
357
- packageManager: scope.packageManager,
358
- /** @deprecated */
359
- useTypescriptOnServer: boolToString(scope.useTypescript),
360
- /** @deprecated */
361
- useTypescriptOnAdmin: boolToString(scope.useTypescript),
362
- useTypescript: boolToString(scope.useTypescript),
363
- isHostedOnStrapiCloud: process.env.STRAPI_HOSTING === "strapi.cloud",
364
- noRun: boolToString(scope.runApp),
365
- projectId: scope.uuid,
366
- useExample: boolToString(scope.useExample),
367
- gitInit: boolToString(scope.gitInit),
368
- installDependencies: boolToString(scope.installDependencies)
369
- };
370
- return {
371
- eventProperties,
372
- userProperties,
373
- groupProperties: addPackageJsonStrapiMetadata(groupProperties, scope)
374
- };
374
+ const boolToString = (value)=>(value === true).toString();
375
+ const getProperties = (scope, error)=>{
376
+ const eventProperties = {
377
+ error: typeof error === 'string' ? error : error && error.message
378
+ };
379
+ const userProperties = {
380
+ os: os.type(),
381
+ osPlatform: os.platform(),
382
+ osArch: os.arch(),
383
+ osRelease: os.release(),
384
+ nodeVersion: process.versions.node
385
+ };
386
+ const groupProperties = {
387
+ version: scope.strapiVersion,
388
+ docker: scope.docker,
389
+ useYarn: scope.packageManager === 'yarn',
390
+ packageManager: scope.packageManager,
391
+ /** @deprecated */ useTypescriptOnServer: boolToString(scope.useTypescript),
392
+ /** @deprecated */ useTypescriptOnAdmin: boolToString(scope.useTypescript),
393
+ useTypescript: boolToString(scope.useTypescript),
394
+ isHostedOnStrapiCloud: process.env.STRAPI_HOSTING === 'strapi.cloud',
395
+ noRun: boolToString(scope.runApp),
396
+ projectId: scope.uuid,
397
+ useExample: boolToString(scope.useExample),
398
+ gitInit: boolToString(scope.gitInit),
399
+ installDependencies: boolToString(scope.installDependencies)
400
+ };
401
+ return {
402
+ eventProperties,
403
+ userProperties,
404
+ groupProperties: addPackageJsonStrapiMetadata(groupProperties, scope)
405
+ };
375
406
  };
376
407
  function trackEvent(event, payload) {
377
- if (process.env.NODE_ENV === "test") {
378
- return;
379
- }
380
- try {
381
- return fetch("https://analytics.strapi.io/api/v2/track", {
382
- method: "POST",
383
- body: JSON.stringify({
384
- event,
385
- ...payload
386
- }),
387
- signal: AbortSignal.timeout(1e3),
388
- headers: {
389
- "Content-Type": "application/json",
390
- "X-Strapi-Event": event
391
- }
392
- }).catch(() => {
393
- });
394
- } catch (err) {
395
- return Promise.resolve();
396
- }
408
+ if (process.env.NODE_ENV === 'test') {
409
+ return;
410
+ }
411
+ try {
412
+ return fetch('https://analytics.strapi.io/api/v2/track', {
413
+ method: 'POST',
414
+ body: JSON.stringify({
415
+ event,
416
+ ...payload
417
+ }),
418
+ signal: AbortSignal.timeout(1000),
419
+ headers: {
420
+ 'Content-Type': 'application/json',
421
+ 'X-Strapi-Event': event
422
+ }
423
+ }).catch(()=>{});
424
+ } catch (err) {
425
+ /** ignore errors */ return Promise.resolve();
426
+ }
397
427
  }
398
428
  async function trackError({ scope, error }) {
399
- const properties = getProperties(scope, error);
400
- try {
401
- return await trackEvent("didNotCreateProject", {
402
- deviceId: scope.deviceId,
403
- ...properties
404
- });
405
- } catch (err) {
406
- return Promise.resolve();
407
- }
429
+ const properties = getProperties(scope, error);
430
+ try {
431
+ return await trackEvent('didNotCreateProject', {
432
+ deviceId: scope.deviceId,
433
+ ...properties
434
+ });
435
+ } catch (err) {
436
+ /** ignore errors */ return Promise.resolve();
437
+ }
408
438
  }
409
- async function trackUsage({
410
- event,
411
- scope,
412
- error
413
- }) {
414
- const properties = getProperties(scope, error);
415
- try {
416
- return await trackEvent(event, {
417
- deviceId: scope.deviceId,
418
- ...properties
419
- });
420
- } catch (err) {
421
- return Promise.resolve();
422
- }
439
+ async function trackUsage({ event, scope, error }) {
440
+ const properties = getProperties(scope, error);
441
+ try {
442
+ return await trackEvent(event, {
443
+ deviceId: scope.deviceId,
444
+ ...properties
445
+ });
446
+ } catch (err) {
447
+ /** ignore errors */ return Promise.resolve();
448
+ }
423
449
  }
450
+
424
451
  const engines = {
425
- node: ">=18.0.0 <=22.x.x",
426
- npm: ">=6.0.0"
452
+ node: '>=18.0.0 <=22.x.x',
453
+ npm: '>=6.0.0'
427
454
  };
455
+
428
456
  async function createPackageJSON(scope) {
429
- const { sortPackageJson } = await import("sort-package-json");
430
- const pkgJSONPath = join(scope.rootPath, "package.json");
431
- const existingPkg = await fse.readJSON(pkgJSONPath).catch(() => ({}));
432
- const pkg = {
433
- name: kebabCase(scope.name),
434
- private: true,
435
- version: "0.1.0",
436
- description: "A Strapi application",
437
- devDependencies: scope.devDependencies ?? {},
438
- dependencies: scope.dependencies ?? {},
439
- strapi: {
440
- ...scope.packageJsonStrapi ?? {},
441
- uuid: scope.uuid
442
- },
443
- engines
444
- };
445
- await fse.writeJSON(pkgJSONPath, sortPackageJson(merge(existingPkg, pkg)), {
446
- spaces: 2
447
- });
457
+ const { sortPackageJson } = await import('sort-package-json');
458
+ const pkgJSONPath = join(scope.rootPath, 'package.json');
459
+ const existingPkg = await fse.readJSON(pkgJSONPath).catch(()=>({}));
460
+ const pkg = {
461
+ name: kebabCase(scope.name),
462
+ private: true,
463
+ version: '0.1.0',
464
+ description: 'A Strapi application',
465
+ devDependencies: scope.devDependencies ?? {},
466
+ dependencies: scope.dependencies ?? {},
467
+ strapi: {
468
+ ...scope.packageJsonStrapi ?? {},
469
+ uuid: scope.uuid
470
+ },
471
+ engines
472
+ };
473
+ // copy templates
474
+ await fse.writeJSON(pkgJSONPath, sortPackageJson(merge(existingPkg, pkg)), {
475
+ spaces: 2
476
+ });
448
477
  }
449
- const generateASecret = () => crypto.randomBytes(16).toString("base64");
478
+
479
+ const generateASecret = ()=>crypto.randomBytes(16).toString('base64');
450
480
  const envTmpl = `
451
481
  # Server
452
482
  HOST=0.0.0.0
@@ -469,77 +499,74 @@ DATABASE_SSL=<%= database.connection.ssl %>
469
499
  DATABASE_FILENAME=<%= database.connection.filename %>
470
500
  `;
471
501
  function generateDotEnv(scope) {
472
- const compile = _.template(envTmpl);
473
- return compile({
474
- appKeys: new Array(4).fill(null).map(generateASecret).join(","),
475
- apiTokenSalt: generateASecret(),
476
- transferTokenSalt: generateASecret(),
477
- adminJwtToken: generateASecret(),
478
- database: {
479
- client: scope.database.client,
480
- connection: {
481
- ...scope.database.connection,
482
- ssl: scope.database.connection?.ssl || false
483
- }
484
- }
485
- });
502
+ const compile = _.template(envTmpl);
503
+ return compile({
504
+ appKeys: new Array(4).fill(null).map(generateASecret).join(','),
505
+ apiTokenSalt: generateASecret(),
506
+ transferTokenSalt: generateASecret(),
507
+ adminJwtToken: generateASecret(),
508
+ database: {
509
+ client: scope.database.client,
510
+ connection: {
511
+ ...scope.database.connection,
512
+ ssl: scope.database.connection?.ssl || false
513
+ }
514
+ }
515
+ });
486
516
  }
517
+
487
518
  function isStderrError(error) {
488
- return typeof error === "object" && error !== null && "stderr" in error && typeof error.stderr === "string";
519
+ return typeof error === 'object' && error !== null && 'stderr' in error && typeof error.stderr === 'string';
489
520
  }
521
+
490
522
  const MAX_PREFIX_LENGTH = 8;
491
- const badge = (text, bgColor, textColor = chalk.black) => {
492
- const wrappedText = ` ${text} `;
493
- const repeat = Math.max(0, MAX_PREFIX_LENGTH - wrappedText.length);
494
- return " ".repeat(repeat) + bgColor(textColor(wrappedText));
523
+ const badge = (text, bgColor, textColor = chalk.black)=>{
524
+ const wrappedText = ` ${text} `;
525
+ const repeat = Math.max(0, MAX_PREFIX_LENGTH - wrappedText.length);
526
+ return ' '.repeat(repeat) + bgColor(textColor(wrappedText));
495
527
  };
496
- const textIndent = (text, indentFirst = true, indent = MAX_PREFIX_LENGTH + 2) => {
497
- const parts = Array.isArray(text) ? text : [text];
498
- return parts.map((part, i) => {
499
- if (i === 0 && !indentFirst) {
500
- return part;
501
- }
502
- return " ".repeat(indent) + part;
503
- }).join("\n");
528
+ const textIndent = (text, indentFirst = true, indent = MAX_PREFIX_LENGTH + 2)=>{
529
+ const parts = Array.isArray(text) ? text : [
530
+ text
531
+ ];
532
+ return parts.map((part, i)=>{
533
+ if (i === 0 && !indentFirst) {
534
+ return part;
535
+ }
536
+ return ' '.repeat(indent) + part;
537
+ }).join('\n');
504
538
  };
505
539
  const logger = {
506
- log(message) {
507
- console.log(textIndent(message));
508
- },
509
- title(title, message) {
510
- const prefix = badge(title, chalk.bgBlueBright);
511
- console.log(`
512
- ${prefix} ${message}`);
513
- },
514
- info(message) {
515
- console.log(`${" ".repeat(7)}${chalk.cyan("●")} ${message}`);
516
- },
517
- success(message) {
518
- console.log(`
519
- ${" ".repeat(7)}${chalk.green("✓")} ${chalk.green(message)}`);
520
- },
521
- fatal(message) {
522
- const prefix = badge("Error", chalk.bgRed);
523
- if (message) {
524
- console.error(`
525
- ${prefix} ${textIndent(message, false)}
526
- `);
527
- }
528
- process.exit(1);
529
- },
530
- error(message) {
531
- const prefix = badge("Error", chalk.bgRed);
532
- console.error(`
533
- ${prefix} ${textIndent(message, false)}
534
- `);
535
- },
536
- warn(message) {
537
- const prefix = badge("Warn", chalk.bgYellow);
538
- console.warn(`
539
- ${prefix} ${textIndent(message, false)}
540
- `);
541
- }
540
+ log (message) {
541
+ console.log(textIndent(message));
542
+ },
543
+ title (title, message) {
544
+ const prefix = badge(title, chalk.bgBlueBright);
545
+ console.log(`\n${prefix} ${message}`);
546
+ },
547
+ info (message) {
548
+ console.log(`${' '.repeat(7)}${chalk.cyan('●')} ${message}`);
549
+ },
550
+ success (message) {
551
+ console.log(`\n${' '.repeat(7)}${chalk.green('✓')} ${chalk.green(message)}`);
552
+ },
553
+ fatal (message) {
554
+ const prefix = badge('Error', chalk.bgRed);
555
+ if (message) {
556
+ console.error(`\n${prefix} ${textIndent(message, false)}\n`);
557
+ }
558
+ process.exit(1);
559
+ },
560
+ error (message) {
561
+ const prefix = badge('Error', chalk.bgRed);
562
+ console.error(`\n${prefix} ${textIndent(message, false)}\n`);
563
+ },
564
+ warn (message) {
565
+ const prefix = badge('Warn', chalk.bgYellow);
566
+ console.warn(`\n${prefix} ${textIndent(message, false)}\n`);
567
+ }
542
568
  };
569
+
543
570
  const baseGitIgnore = `
544
571
  ############################
545
572
  # OS X
@@ -674,593 +701,742 @@ build
674
701
  .strapi-cloud.json
675
702
  `;
676
703
  const gitIgnore = baseGitIgnore.trim();
677
- const installArguments = ["install"];
704
+
705
+ const installArguments = [
706
+ 'install'
707
+ ];
708
+ // Set command line options for specific package managers, with full semver ranges
678
709
  const installArgumentsMap = {
679
- npm: {
680
- "*": ["--legacy-peer-deps"]
681
- },
682
- yarn: {
683
- "<4": ["--network-timeout", "1000000"],
684
- "*": []
685
- },
686
- pnpm: {
687
- "*": []
688
- }
710
+ npm: {
711
+ '*': [
712
+ '--legacy-peer-deps'
713
+ ]
714
+ },
715
+ yarn: {
716
+ '<4': [
717
+ '--network-timeout',
718
+ '1000000'
719
+ ],
720
+ '*': []
721
+ },
722
+ pnpm: {
723
+ '*': []
724
+ }
689
725
  };
726
+ // Set environment variables for specific package managers, with full semver ranges
690
727
  const installEnvMap = {
691
- yarn: {
692
- ">=4": { YARN_HTTP_TIMEOUT: "1000000" },
693
- "*": {}
694
- },
695
- npm: {
696
- "*": {}
697
- },
698
- pnpm: {
699
- "*": {}
700
- }
701
- };
702
- const getPackageManagerVersion = async (packageManager, options) => {
703
- try {
704
- const { stdout } = await execa(packageManager, ["--version"], options);
705
- return stdout.trim();
706
- } catch (err) {
707
- throw new Error(`Error detecting ${packageManager} version: ${err}`);
708
- }
728
+ yarn: {
729
+ '>=4': {
730
+ YARN_HTTP_TIMEOUT: '1000000'
731
+ },
732
+ '*': {}
733
+ },
734
+ npm: {
735
+ '*': {}
736
+ },
737
+ pnpm: {
738
+ '*': {}
739
+ }
709
740
  };
710
- function mergeMatchingVersionRanges(version2, versionMap, mergeFn) {
711
- return Object.keys(versionMap).reduce((acc, range) => {
712
- if (semver.satisfies(version2, range) || range === "*") {
713
- return mergeFn(acc, versionMap[range]);
741
+ /**
742
+ * Retrieves the version of the specified package manager.
743
+ *
744
+ * Executes the package manager's `--version` command to determine its version.
745
+ *
746
+ * @param packageManager - The name of the package manager (e.g., 'npm', 'yarn', 'pnpm').
747
+ * @param options - Optional execution options to pass to `execa`.
748
+ * @returns A promise that resolves to the trimmed version string of the package manager.
749
+ *
750
+ * @throws Will throw an error if the package manager's version cannot be determined.
751
+ */ const getPackageManagerVersion = async (packageManager, options)=>{
752
+ try {
753
+ const { stdout } = await execa(packageManager, [
754
+ '--version'
755
+ ], options);
756
+ return stdout.trim();
757
+ } catch (err) {
758
+ throw new Error(`Error detecting ${packageManager} version: ${err}`);
714
759
  }
715
- return acc;
716
- }, versionMap["*"]);
760
+ };
761
+ /**
762
+ * Merges all matching semver ranges using a custom merge function.
763
+ *
764
+ * Iterates over the `versionMap`, checking if the provided `version` satisfies each semver range.
765
+ * If it does, the corresponding value is merged using the provided `mergeFn`.
766
+ * The merging starts with the value associated with the wildcard '*' key.
767
+ *
768
+ * @param version - The package manager version to check against the ranges.
769
+ * @param versionMap - A map of semver ranges to corresponding values (arguments or environment variables).
770
+ * @param mergeFn - A function that defines how to merge two values (accumulated and current).
771
+ * @returns The merged result of all matching ranges.
772
+ */ function mergeMatchingVersionRanges(version, versionMap, mergeFn) {
773
+ return Object.keys(versionMap).reduce((acc, range)=>{
774
+ if (semver.satisfies(version, range) || range === '*') {
775
+ return mergeFn(acc, versionMap[range]);
776
+ }
777
+ return acc;
778
+ }, versionMap['*']); // Start with the wildcard entry
717
779
  }
718
780
  function mergeArguments(acc, curr) {
719
- return [...acc, ...curr];
781
+ return [
782
+ ...acc,
783
+ ...curr
784
+ ];
720
785
  }
721
786
  function mergeEnvVars(acc, curr) {
722
- return { ...acc, ...curr };
787
+ return {
788
+ ...acc,
789
+ ...curr
790
+ };
723
791
  }
724
- const getInstallArgs = async (packageManager, options) => {
725
- const packageManagerVersion = await getPackageManagerVersion(packageManager, options);
726
- const envMap = installEnvMap[packageManager];
727
- const envArgs = packageManagerVersion ? mergeMatchingVersionRanges(packageManagerVersion, envMap, mergeEnvVars) : envMap["*"];
728
- const argsMap = installArgumentsMap[packageManager];
729
- const cmdArgs = packageManagerVersion ? mergeMatchingVersionRanges(packageManagerVersion, argsMap, mergeArguments) : argsMap["*"];
730
- return { envArgs, cmdArgs: [...installArguments, ...cmdArgs], version: packageManagerVersion };
792
+ /**
793
+ * Retrieves the install arguments and environment variables for a given package manager.
794
+ *
795
+ * This function determines the correct command line arguments and environment variables
796
+ * based on the package manager's version. It uses predefined semver ranges to match
797
+ * the package manager's version and merges all applicable settings.
798
+ *
799
+ * The arguments and environment variables are sourced from:
800
+ * - `installArgumentsMap` for command line arguments.
801
+ * - `installEnvMap` for environment variables.
802
+ *
803
+ * The function ensures that all matching semver ranges are considered and merged appropriately.
804
+ * It always includes the base `installArguments` (e.g., `['install']`) and applies any additional
805
+ * arguments or environment variables as defined by the matched version ranges.
806
+ *
807
+ * @param packageManager - The name of the package manager (e.g., 'npm', 'yarn', 'pnpm').
808
+ * @param options - Optional execution options to pass to `execa`.
809
+ * @returns An object containing:
810
+ * - `cmdArgs`: The full array of install arguments for the given package manager and version.
811
+ * - `envArgs`: The merged environment variables applicable to the package manager and version.
812
+ *
813
+ * @throws Will throw an error if the package manager version cannot be determined.
814
+ */ const getInstallArgs = async (packageManager, options)=>{
815
+ const packageManagerVersion = await getPackageManagerVersion(packageManager, options);
816
+ // Get environment variables
817
+ const envMap = installEnvMap[packageManager];
818
+ const envArgs = packageManagerVersion ? mergeMatchingVersionRanges(packageManagerVersion, envMap, mergeEnvVars) : envMap['*'];
819
+ // Get install arguments
820
+ const argsMap = installArgumentsMap[packageManager];
821
+ const cmdArgs = packageManagerVersion ? mergeMatchingVersionRanges(packageManagerVersion, argsMap, mergeArguments) : argsMap['*'];
822
+ return {
823
+ envArgs,
824
+ cmdArgs: [
825
+ ...installArguments,
826
+ ...cmdArgs
827
+ ],
828
+ version: packageManagerVersion
829
+ };
731
830
  };
831
+
732
832
  async function createStrapi(scope) {
733
- const { rootPath } = scope;
734
- try {
735
- await fse.ensureDir(rootPath);
736
- await createApp(scope);
737
- } catch (error) {
738
- await fse.remove(rootPath);
739
- throw error;
740
- }
741
- }
742
- async function createApp(scope) {
743
- const {
744
- rootPath,
745
- useTypescript,
746
- useExample,
747
- installDependencies: installDependencies2,
748
- isQuickstart,
749
- template,
750
- packageManager,
751
- gitInit: gitInit2,
752
- runApp
753
- } = scope;
754
- const shouldRunSeed = useExample && installDependencies2;
755
- await trackUsage({ event: "willCreateProject", scope });
756
- logger.title("Strapi", `Creating a new application at ${chalk.green(rootPath)}`);
757
- if (!isQuickstart) {
758
- await trackUsage({ event: "didChooseCustomDatabase", scope });
759
- } else {
760
- await trackUsage({ event: "didChooseQuickstart", scope });
761
- }
762
- if (!template) {
763
- let templateName = useExample ? "example" : "vanilla";
764
- if (!useTypescript) {
765
- templateName = `${templateName}-js`;
766
- }
767
- const internalTemplatePath = join$1(__dirname, "../templates", templateName);
768
- if (await fse.exists(internalTemplatePath)) {
769
- await fse.copy(internalTemplatePath, rootPath);
770
- }
771
- } else {
833
+ const { rootPath } = scope;
772
834
  try {
773
- logger.info(`${chalk.cyan("Installing template")} ${template}`);
774
- await copyTemplate(scope, rootPath);
775
- logger.success("Template copied successfully.");
835
+ await fse.ensureDir(rootPath);
836
+ await createApp(scope);
776
837
  } catch (error) {
777
- if (error instanceof Error) {
778
- logger.fatal(`Template installation failed: ${error.message}`);
779
- }
780
- throw error;
781
- }
782
- if (!fse.existsSync(join$1(rootPath, "package.json"))) {
783
- logger.fatal(`Missing ${chalk.bold("package.json")} in template`);
784
- }
785
- }
786
- await trackUsage({ event: "didCopyProjectFiles", scope });
787
- try {
788
- await createPackageJSON(scope);
789
- await trackUsage({ event: "didWritePackageJSON", scope });
790
- await fse.ensureDir(join$1(rootPath, "node_modules"));
791
- await fse.writeFile(join$1(rootPath, ".env"), generateDotEnv(scope));
792
- await trackUsage({ event: "didCopyConfigurationFiles", scope });
793
- } catch (err) {
794
- await fse.remove(rootPath);
795
- throw err;
796
- }
797
- if (installDependencies2) {
838
+ await fse.remove(rootPath);
839
+ throw error;
840
+ }
841
+ }
842
+ async function createApp(scope) {
843
+ const { rootPath, useTypescript, useExample, installDependencies, isQuickstart, template, packageManager, gitInit, runApp } = scope;
844
+ const shouldRunSeed = useExample && installDependencies;
845
+ await trackUsage({
846
+ event: 'willCreateProject',
847
+ scope
848
+ });
849
+ logger.title('Strapi', `Creating a new application at ${chalk.green(rootPath)}`);
850
+ if (!isQuickstart) {
851
+ await trackUsage({
852
+ event: 'didChooseCustomDatabase',
853
+ scope
854
+ });
855
+ } else {
856
+ await trackUsage({
857
+ event: 'didChooseQuickstart',
858
+ scope
859
+ });
860
+ }
861
+ if (!template) {
862
+ let templateName = useExample ? 'example' : 'vanilla';
863
+ if (!useTypescript) {
864
+ templateName = `${templateName}-js`;
865
+ }
866
+ const internalTemplatePath = join$1(__dirname, '../templates', templateName);
867
+ if (await fse.exists(internalTemplatePath)) {
868
+ await fse.copy(internalTemplatePath, rootPath);
869
+ }
870
+ } else {
871
+ try {
872
+ logger.info(`${chalk.cyan('Installing template')} ${template}`);
873
+ await copyTemplate(scope, rootPath);
874
+ logger.success('Template copied successfully.');
875
+ } catch (error) {
876
+ if (error instanceof Error) {
877
+ logger.fatal(`Template installation failed: ${error.message}`);
878
+ }
879
+ throw error;
880
+ }
881
+ if (!fse.existsSync(join$1(rootPath, 'package.json'))) {
882
+ logger.fatal(`Missing ${chalk.bold('package.json')} in template`);
883
+ }
884
+ }
885
+ await trackUsage({
886
+ event: 'didCopyProjectFiles',
887
+ scope
888
+ });
798
889
  try {
799
- logger.title("deps", `Installing dependencies with ${chalk.cyan(packageManager)}`);
800
- await trackUsage({ event: "willInstallProjectDependencies", scope });
801
- await runInstall(scope);
802
- await trackUsage({ event: "didInstallProjectDependencies", scope });
803
- logger.success(`Dependencies installed`);
804
- } catch (error) {
805
- const stderr = isStderrError(error) ? error.stderr : "";
806
- await trackUsage({
807
- event: "didNotInstallProjectDependencies",
808
- scope,
809
- error: stderr.slice(-1024)
810
- });
811
- logger.fatal([
812
- chalk.bold(
813
- "Oh, it seems that you encountered an error while installing dependencies in your project"
814
- ),
815
- "",
816
- `Don't give up, your project was created correctly`,
817
- "",
818
- `Fix the issues mentioned in the installation errors and try to run the following command:`,
819
- "",
820
- `cd ${chalk.green(rootPath)} && ${chalk.cyan(packageManager)} install`
821
- ]);
822
- }
823
- }
824
- await trackUsage({ event: "didCreateProject", scope });
825
- if (!await fse.exists(join$1(rootPath, ".gitignore"))) {
826
- await fse.writeFile(join$1(rootPath, ".gitignore"), gitIgnore);
827
- }
828
- if (gitInit2) {
829
- logger.title("git", "Initializing git repository.");
830
- await tryGitInit(rootPath);
831
- logger.success("Initialized a git repository.");
832
- }
833
- if (shouldRunSeed) {
834
- if (await fse.exists(join$1(rootPath, "scripts/seed.js"))) {
835
- logger.title("Seed", "Seeding your database with sample data");
836
- try {
837
- await execa(packageManager, ["run", "seed:example"], {
838
- stdio: "inherit",
839
- cwd: rootPath
890
+ await createPackageJSON(scope);
891
+ await trackUsage({
892
+ event: 'didWritePackageJSON',
893
+ scope
840
894
  });
841
- logger.success("Sample data added to your database");
842
- } catch (error) {
843
- logger.error("Failed to seed your database. Skipping");
844
- }
845
- }
846
- }
847
- const cmd = chalk.cyan(`${packageManager} run`);
848
- logger.title("Strapi", `Your application was created!`);
849
- logger.log([
850
- "Available commands in your project:",
851
- "",
852
- "Start Strapi in watch mode. (Changes in Strapi project files will trigger a server restart)",
853
- `${cmd} develop`,
854
- "",
855
- "Start Strapi without watch mode.",
856
- `${cmd} start`,
857
- "",
858
- "Build Strapi admin panel.",
859
- `${cmd} build`,
860
- "",
861
- "Deploy Strapi project.",
862
- `${cmd} deploy`,
863
- ""
864
- ]);
865
- if (useExample) {
866
- logger.log(["Seed your database with sample data.", `${cmd} seed:example`, ""]);
867
- }
868
- logger.log(["Display all available commands.", `${cmd} strapi
869
- `]);
870
- if (installDependencies2) {
895
+ // ensure node_modules is created
896
+ await fse.ensureDir(join$1(rootPath, 'node_modules'));
897
+ // create config/database
898
+ await fse.writeFile(join$1(rootPath, '.env'), generateDotEnv(scope));
899
+ await trackUsage({
900
+ event: 'didCopyConfigurationFiles',
901
+ scope
902
+ });
903
+ } catch (err) {
904
+ await fse.remove(rootPath);
905
+ throw err;
906
+ }
907
+ if (installDependencies) {
908
+ try {
909
+ logger.title('deps', `Installing dependencies with ${chalk.cyan(packageManager)}`);
910
+ await trackUsage({
911
+ event: 'willInstallProjectDependencies',
912
+ scope
913
+ });
914
+ await runInstall(scope);
915
+ await trackUsage({
916
+ event: 'didInstallProjectDependencies',
917
+ scope
918
+ });
919
+ logger.success(`Dependencies installed`);
920
+ } catch (error) {
921
+ const stderr = isStderrError(error) ? error.stderr : '';
922
+ await trackUsage({
923
+ event: 'didNotInstallProjectDependencies',
924
+ scope,
925
+ error: stderr.slice(-1024)
926
+ });
927
+ logger.fatal([
928
+ chalk.bold('Oh, it seems that you encountered an error while installing dependencies in your project'),
929
+ '',
930
+ `Don't give up, your project was created correctly`,
931
+ '',
932
+ `Fix the issues mentioned in the installation errors and try to run the following command:`,
933
+ '',
934
+ `cd ${chalk.green(rootPath)} && ${chalk.cyan(packageManager)} install`
935
+ ]);
936
+ }
937
+ }
938
+ await trackUsage({
939
+ event: 'didCreateProject',
940
+ scope
941
+ });
942
+ // make sure a gitignore file is created regardless of the user using git or not
943
+ if (!await fse.exists(join$1(rootPath, '.gitignore'))) {
944
+ await fse.writeFile(join$1(rootPath, '.gitignore'), gitIgnore);
945
+ }
946
+ // Init git
947
+ if (gitInit) {
948
+ logger.title('git', 'Initializing git repository.');
949
+ await tryGitInit(rootPath);
950
+ logger.success('Initialized a git repository.');
951
+ }
952
+ if (shouldRunSeed) {
953
+ if (await fse.exists(join$1(rootPath, 'scripts/seed.js'))) {
954
+ logger.title('Seed', 'Seeding your database with sample data');
955
+ try {
956
+ await execa(packageManager, [
957
+ 'run',
958
+ 'seed:example'
959
+ ], {
960
+ stdio: 'inherit',
961
+ cwd: rootPath
962
+ });
963
+ logger.success('Sample data added to your database');
964
+ } catch (error) {
965
+ logger.error('Failed to seed your database. Skipping');
966
+ }
967
+ }
968
+ }
969
+ const cmd = chalk.cyan(`${packageManager} run`);
970
+ logger.title('Strapi', `Your application was created!`);
871
971
  logger.log([
872
- "To get started run",
873
- "",
874
- `${chalk.cyan("cd")} ${rootPath}`,
875
- !shouldRunSeed && useExample ? `${cmd} seed:example && ${cmd} develop` : `${cmd} develop`
972
+ 'Available commands in your project:',
973
+ '',
974
+ 'Start Strapi in watch mode. (Changes in Strapi project files will trigger a server restart)',
975
+ `${cmd} develop`,
976
+ '',
977
+ 'Start Strapi without watch mode.',
978
+ `${cmd} start`,
979
+ '',
980
+ 'Build Strapi admin panel.',
981
+ `${cmd} build`,
982
+ '',
983
+ 'Deploy Strapi project.',
984
+ `${cmd} deploy`,
985
+ ''
876
986
  ]);
877
- } else {
987
+ if (useExample) {
988
+ logger.log([
989
+ 'Seed your database with sample data.',
990
+ `${cmd} seed:example`,
991
+ ''
992
+ ]);
993
+ }
878
994
  logger.log([
879
- "To get started run",
880
- "",
881
- `${chalk.cyan("cd")} ${rootPath}`,
882
- `${chalk.cyan(packageManager)} install`,
883
- !shouldRunSeed && useExample ? `${cmd} seed:example && ${cmd} develop` : `${cmd} develop`
995
+ 'Display all available commands.',
996
+ `${cmd} strapi\n`
884
997
  ]);
885
- }
886
- if (runApp && installDependencies2) {
887
- logger.title("Run", "Running your Strapi application");
888
- try {
889
- await trackUsage({ event: "willStartServer", scope });
890
- await execa(packageManager, ["run", "develop"], {
891
- stdio: "inherit",
892
- cwd: rootPath,
893
- env: {
894
- FORCE_COLOR: "1"
998
+ if (installDependencies) {
999
+ logger.log([
1000
+ 'To get started run',
1001
+ '',
1002
+ `${chalk.cyan('cd')} ${rootPath}`,
1003
+ !shouldRunSeed && useExample ? `${cmd} seed:example && ${cmd} develop` : `${cmd} develop`
1004
+ ]);
1005
+ } else {
1006
+ logger.log([
1007
+ 'To get started run',
1008
+ '',
1009
+ `${chalk.cyan('cd')} ${rootPath}`,
1010
+ `${chalk.cyan(packageManager)} install`,
1011
+ !shouldRunSeed && useExample ? `${cmd} seed:example && ${cmd} develop` : `${cmd} develop`
1012
+ ]);
1013
+ }
1014
+ if (runApp && installDependencies) {
1015
+ logger.title('Run', 'Running your Strapi application');
1016
+ try {
1017
+ await trackUsage({
1018
+ event: 'willStartServer',
1019
+ scope
1020
+ });
1021
+ await execa(packageManager, [
1022
+ 'run',
1023
+ 'develop'
1024
+ ], {
1025
+ stdio: 'inherit',
1026
+ cwd: rootPath,
1027
+ env: {
1028
+ FORCE_COLOR: '1'
1029
+ }
1030
+ });
1031
+ } catch (error) {
1032
+ if (typeof error === 'string' || error instanceof Error) {
1033
+ await trackUsage({
1034
+ event: 'didNotStartServer',
1035
+ scope,
1036
+ error
1037
+ });
1038
+ }
1039
+ logger.fatal('Failed to start your Strapi application');
895
1040
  }
896
- });
897
- } catch (error) {
898
- if (typeof error === "string" || error instanceof Error) {
899
- await trackUsage({
900
- event: "didNotStartServer",
901
- scope,
902
- error
903
- });
904
- }
905
- logger.fatal("Failed to start your Strapi application");
906
1041
  }
907
- }
908
1042
  }
909
1043
  async function runInstall({ rootPath, packageManager }) {
910
- const { envArgs, cmdArgs } = await getInstallArgs(packageManager, {
911
- cwd: rootPath,
912
- env: {
913
- ...process.env,
914
- NODE_ENV: "development"
915
- }
916
- });
917
- const options = {
918
- cwd: rootPath,
919
- stdio: "inherit",
920
- env: {
921
- ...process.env,
922
- ...envArgs,
923
- NODE_ENV: "development"
924
- }
925
- };
926
- const proc = execa(packageManager, cmdArgs, options);
927
- return proc;
1044
+ // include same cwd and env to ensure version check returns same version we use below
1045
+ const { envArgs, cmdArgs } = await getInstallArgs(packageManager, {
1046
+ cwd: rootPath,
1047
+ env: {
1048
+ ...process.env,
1049
+ NODE_ENV: 'development'
1050
+ }
1051
+ });
1052
+ const options = {
1053
+ cwd: rootPath,
1054
+ stdio: 'inherit',
1055
+ env: {
1056
+ ...process.env,
1057
+ ...envArgs,
1058
+ NODE_ENV: 'development'
1059
+ }
1060
+ };
1061
+ const proc = execa(packageManager, cmdArgs, options);
1062
+ return proc;
928
1063
  }
1064
+
929
1065
  function checkNodeRequirements() {
930
- const currentNodeVersion = process.versions.node;
931
- if (!semver.satisfies(currentNodeVersion, engines.node)) {
932
- logger.fatal([
933
- chalk.red(`You are running ${chalk.bold(`Node.js ${currentNodeVersion}`)}`),
934
- `Strapi requires ${chalk.bold(chalk.green(`Node.js ${engines.node}`))}`,
935
- "Please make sure to use the right version of Node."
936
- ]);
937
- } else if (semver.major(currentNodeVersion) % 2 !== 0) {
938
- logger.warn([
939
- chalk.yellow(`You are running ${chalk.bold(`Node.js ${currentNodeVersion}`)}`),
940
- `Strapi only supports ${chalk.bold(chalk.green("LTS versions of Node.js"))}, other versions may not be compatible.`
941
- ]);
942
- }
1066
+ const currentNodeVersion = process.versions.node;
1067
+ // error if the node version isn't supported
1068
+ if (!semver.satisfies(currentNodeVersion, engines.node)) {
1069
+ logger.fatal([
1070
+ chalk.red(`You are running ${chalk.bold(`Node.js ${currentNodeVersion}`)}`),
1071
+ `Strapi requires ${chalk.bold(chalk.green(`Node.js ${engines.node}`))}`,
1072
+ 'Please make sure to use the right version of Node.'
1073
+ ]);
1074
+ } else if (semver.major(currentNodeVersion) % 2 !== 0) {
1075
+ logger.warn([
1076
+ chalk.yellow(`You are running ${chalk.bold(`Node.js ${currentNodeVersion}`)}`),
1077
+ `Strapi only supports ${chalk.bold(chalk.green('LTS versions of Node.js'))}, other versions may not be compatible.`
1078
+ ]);
1079
+ }
943
1080
  }
944
- async function checkInstallPath(directory2) {
945
- const rootPath = resolve(directory2);
946
- if (await fse.pathExists(rootPath)) {
947
- const stat = await fse.stat(rootPath);
948
- if (!stat.isDirectory()) {
949
- logger.fatal(
950
- `${chalk.green(
951
- rootPath
952
- )} is not a directory. Make sure to create a Strapi application in an empty directory.`
953
- );
954
- }
955
- const files = await fse.readdir(rootPath);
956
- if (files.length > 1) {
957
- logger.fatal([
958
- "You can only create a Strapi app in an empty directory",
959
- `Make sure ${chalk.green(rootPath)} is empty.`
960
- ]);
961
- }
962
- }
963
- return rootPath;
1081
+
1082
+ // Checks if the an empty directory exists at rootPath
1083
+ async function checkInstallPath(directory) {
1084
+ const rootPath = resolve(directory);
1085
+ if (await fse.pathExists(rootPath)) {
1086
+ const stat = await fse.stat(rootPath);
1087
+ if (!stat.isDirectory()) {
1088
+ logger.fatal(`${chalk.green(rootPath)} is not a directory. Make sure to create a Strapi application in an empty directory.`);
1089
+ }
1090
+ const files = await fse.readdir(rootPath);
1091
+ if (files.length > 1) {
1092
+ logger.fatal([
1093
+ 'You can only create a Strapi app in an empty directory',
1094
+ `Make sure ${chalk.green(rootPath)} is empty.`
1095
+ ]);
1096
+ }
1097
+ }
1098
+ return rootPath;
964
1099
  }
1100
+
965
1101
  function machineID() {
966
- try {
967
- const deviceId = machineIdSync();
968
- return deviceId;
969
- } catch (error) {
970
- const deviceId = randomUUID();
971
- return deviceId;
972
- }
1102
+ try {
1103
+ const deviceId = machineIdSync();
1104
+ return deviceId;
1105
+ } catch (error) {
1106
+ const deviceId = randomUUID();
1107
+ return deviceId;
1108
+ }
973
1109
  }
974
- const DBOptions = ["dbclient", "dbhost", "dbport", "dbname", "dbusername", "dbpassword"];
975
- const VALID_CLIENTS = ["sqlite", "mysql", "postgres"];
1110
+
1111
+ const DBOptions = [
1112
+ 'dbclient',
1113
+ 'dbhost',
1114
+ 'dbport',
1115
+ 'dbname',
1116
+ 'dbusername',
1117
+ 'dbpassword'
1118
+ ];
1119
+ const VALID_CLIENTS = [
1120
+ 'sqlite',
1121
+ 'mysql',
1122
+ 'postgres'
1123
+ ];
976
1124
  const DEFAULT_CONFIG = {
977
- client: "sqlite",
978
- connection: {
979
- filename: ".tmp/data.db"
980
- }
1125
+ client: 'sqlite',
1126
+ connection: {
1127
+ filename: '.tmp/data.db'
1128
+ }
981
1129
  };
982
1130
  async function dbPrompt() {
983
- const { useDefault } = await inquirer.prompt([
984
- {
985
- type: "confirm",
986
- name: "useDefault",
987
- message: "Do you want to use the default database (sqlite) ?",
988
- default: true
989
- }
990
- ]);
991
- if (useDefault) {
992
- return DEFAULT_CONFIG;
993
- }
994
- const { client } = await inquirer.prompt([
995
- {
996
- type: "list",
997
- name: "client",
998
- message: "Choose your default database client",
999
- choices: ["sqlite", "postgres", "mysql"],
1000
- default: "sqlite"
1001
- }
1002
- ]);
1003
- const questions = dbQuestions[client].map((q) => q({ client }));
1004
- const responses = await inquirer.prompt(questions);
1005
- return {
1006
- client,
1007
- connection: responses
1008
- };
1131
+ const { useDefault } = await inquirer.prompt([
1132
+ {
1133
+ type: 'confirm',
1134
+ name: 'useDefault',
1135
+ message: 'Do you want to use the default database (sqlite) ?',
1136
+ default: true
1137
+ }
1138
+ ]);
1139
+ if (useDefault) {
1140
+ return DEFAULT_CONFIG;
1141
+ }
1142
+ const { client } = await inquirer.prompt([
1143
+ {
1144
+ type: 'list',
1145
+ name: 'client',
1146
+ message: 'Choose your default database client',
1147
+ choices: [
1148
+ 'sqlite',
1149
+ 'postgres',
1150
+ 'mysql'
1151
+ ],
1152
+ default: 'sqlite'
1153
+ }
1154
+ ]);
1155
+ const questions = dbQuestions[client].map((q)=>q({
1156
+ client
1157
+ }));
1158
+ const responses = await inquirer.prompt(questions);
1159
+ return {
1160
+ client,
1161
+ connection: responses
1162
+ };
1009
1163
  }
1010
1164
  async function getDatabaseInfos(options) {
1011
- if (options.skipDb) {
1012
- return DEFAULT_CONFIG;
1013
- }
1014
- if (options.dbclient && !VALID_CLIENTS.includes(options.dbclient)) {
1015
- logger.fatal(
1016
- `Invalid --dbclient: ${options.dbclient}, expected one of ${VALID_CLIENTS.join(", ")}`
1017
- );
1018
- }
1019
- const matchingArgs = DBOptions.filter((key) => key in options);
1020
- const missingArgs = DBOptions.filter((key) => !(key in options));
1021
- if (matchingArgs.length > 0 && matchingArgs.length !== DBOptions.length && options.dbclient !== "sqlite") {
1022
- logger.fatal(`Required database arguments are missing: ${missingArgs.join(", ")}.`);
1023
- }
1024
- const hasDBOptions = DBOptions.some((key) => key in options);
1025
- if (!hasDBOptions) {
1026
- if (options.quickstart) {
1027
- return DEFAULT_CONFIG;
1028
- }
1029
- return dbPrompt();
1030
- }
1031
- if (!options.dbclient) {
1032
- return logger.fatal("Please specify the database client");
1033
- }
1034
- const database2 = {
1035
- client: options.dbclient,
1036
- connection: {
1037
- host: options.dbhost,
1038
- port: options.dbport,
1039
- database: options.dbname,
1040
- username: options.dbusername,
1041
- password: options.dbpassword,
1042
- filename: options.dbfile
1043
- }
1044
- };
1045
- if (options.dbssl !== void 0) {
1046
- database2.connection.ssl = options.dbssl === "true";
1047
- }
1048
- return database2;
1165
+ if (options.skipDb) {
1166
+ return DEFAULT_CONFIG;
1167
+ }
1168
+ if (options.dbclient && !VALID_CLIENTS.includes(options.dbclient)) {
1169
+ logger.fatal(`Invalid --dbclient: ${options.dbclient}, expected one of ${VALID_CLIENTS.join(', ')}`);
1170
+ }
1171
+ const matchingArgs = DBOptions.filter((key)=>key in options);
1172
+ const missingArgs = DBOptions.filter((key)=>!(key in options));
1173
+ if (matchingArgs.length > 0 && matchingArgs.length !== DBOptions.length && options.dbclient !== 'sqlite') {
1174
+ logger.fatal(`Required database arguments are missing: ${missingArgs.join(', ')}.`);
1175
+ }
1176
+ const hasDBOptions = DBOptions.some((key)=>key in options);
1177
+ if (!hasDBOptions) {
1178
+ if (options.quickstart) {
1179
+ return DEFAULT_CONFIG;
1180
+ }
1181
+ return dbPrompt();
1182
+ }
1183
+ if (!options.dbclient) {
1184
+ return logger.fatal('Please specify the database client');
1185
+ }
1186
+ const database = {
1187
+ client: options.dbclient,
1188
+ connection: {
1189
+ host: options.dbhost,
1190
+ port: options.dbport,
1191
+ database: options.dbname,
1192
+ username: options.dbusername,
1193
+ password: options.dbpassword,
1194
+ filename: options.dbfile
1195
+ }
1196
+ };
1197
+ if (options.dbssl !== undefined) {
1198
+ database.connection.ssl = options.dbssl === 'true';
1199
+ }
1200
+ return database;
1049
1201
  }
1050
1202
  const sqlClientModule = {
1051
- mysql: { mysql2: "3.9.8" },
1052
- postgres: { pg: "8.8.0" },
1053
- sqlite: { "better-sqlite3": "11.3.0" }
1203
+ mysql: {
1204
+ mysql2: '3.9.8'
1205
+ },
1206
+ postgres: {
1207
+ pg: '8.8.0'
1208
+ },
1209
+ sqlite: {
1210
+ 'better-sqlite3': '11.3.0'
1211
+ }
1054
1212
  };
1055
1213
  function addDatabaseDependencies(scope) {
1056
- scope.dependencies = {
1057
- ...scope.dependencies,
1058
- ...sqlClientModule[scope.database.client]
1059
- };
1214
+ scope.dependencies = {
1215
+ ...scope.dependencies,
1216
+ ...sqlClientModule[scope.database.client]
1217
+ };
1060
1218
  }
1061
1219
  const DEFAULT_PORTS = {
1062
- postgres: 5432,
1063
- mysql: 3306,
1064
- sqlite: void 0
1220
+ postgres: 5432,
1221
+ mysql: 3306,
1222
+ sqlite: undefined
1065
1223
  };
1066
- const database = () => ({
1067
- type: "input",
1068
- name: "database",
1069
- message: "Database name:",
1070
- default: "strapi",
1071
- validate(value) {
1072
- if (value.includes(".")) {
1073
- return `The database name can't contain a "."`;
1074
- }
1075
- return true;
1076
- }
1077
- });
1078
- const host = () => ({
1079
- type: "input",
1080
- name: "host",
1081
- message: "Host:",
1082
- default: "127.0.0.1"
1083
- });
1084
- const port = ({ client }) => ({
1085
- type: "input",
1086
- name: "port",
1087
- message: "Port:",
1088
- default: DEFAULT_PORTS[client]
1089
- });
1090
- const username = () => ({
1091
- type: "input",
1092
- name: "username",
1093
- message: "Username:"
1094
- });
1095
- const password = () => ({
1096
- type: "password",
1097
- name: "password",
1098
- message: "Password:",
1099
- mask: "*"
1100
- });
1101
- const ssl = () => ({
1102
- type: "confirm",
1103
- name: "ssl",
1104
- message: "Enable SSL connection:",
1105
- default: false
1106
- });
1107
- const filename = () => ({
1108
- type: "input",
1109
- name: "filename",
1110
- message: "Filename:",
1111
- default: ".tmp/data.db"
1112
- });
1224
+ const database = ()=>({
1225
+ type: 'input',
1226
+ name: 'database',
1227
+ message: 'Database name:',
1228
+ default: 'strapi',
1229
+ validate (value) {
1230
+ if (value.includes('.')) {
1231
+ return `The database name can't contain a "."`;
1232
+ }
1233
+ return true;
1234
+ }
1235
+ });
1236
+ const host = ()=>({
1237
+ type: 'input',
1238
+ name: 'host',
1239
+ message: 'Host:',
1240
+ default: '127.0.0.1'
1241
+ });
1242
+ const port = ({ client })=>({
1243
+ type: 'input',
1244
+ name: 'port',
1245
+ message: 'Port:',
1246
+ default: DEFAULT_PORTS[client]
1247
+ });
1248
+ const username = ()=>({
1249
+ type: 'input',
1250
+ name: 'username',
1251
+ message: 'Username:'
1252
+ });
1253
+ const password = ()=>({
1254
+ type: 'password',
1255
+ name: 'password',
1256
+ message: 'Password:',
1257
+ mask: '*'
1258
+ });
1259
+ const ssl = ()=>({
1260
+ type: 'confirm',
1261
+ name: 'ssl',
1262
+ message: 'Enable SSL connection:',
1263
+ default: false
1264
+ });
1265
+ const filename = ()=>({
1266
+ type: 'input',
1267
+ name: 'filename',
1268
+ message: 'Filename:',
1269
+ default: '.tmp/data.db'
1270
+ });
1113
1271
  const dbQuestions = {
1114
- sqlite: [filename],
1115
- postgres: [database, host, port, username, password, ssl],
1116
- mysql: [database, host, port, username, password, ssl]
1272
+ sqlite: [
1273
+ filename
1274
+ ],
1275
+ postgres: [
1276
+ database,
1277
+ host,
1278
+ port,
1279
+ username,
1280
+ password,
1281
+ ssl
1282
+ ],
1283
+ mysql: [
1284
+ database,
1285
+ host,
1286
+ port,
1287
+ username,
1288
+ password,
1289
+ ssl
1290
+ ]
1117
1291
  };
1118
- const { version } = fse.readJSONSync(join$1(__dirname, "..", "package.json"));
1119
- const command = new commander.Command("create-strapi-app").version(version).arguments("[directory]").usage("[directory] [options]").option("--quickstart", "Quickstart app creation (deprecated)").option("--no-run", "Do not start the application after it is created.").option("--ts, --typescript", "Initialize the project with TypeScript (default)").option("--js, --javascript", "Initialize the project with Javascript").option("--use-npm", "Use npm as the project package manager").option("--use-yarn", "Use yarn as the project package manager").option("--use-pnpm", "Use pnpm as the project package manager").option("--install", "Install dependencies").option("--no-install", "Do not install dependencies").option("--skip-cloud", "Skip cloud login and project creation").option("--example", "Use an example app").option("--no-example", "Do not use an example app").option("--git-init", "Initialize a git repository").option("--no-git-init", "Do no initialize a git repository").option("--dbclient <dbclient>", "Database client").option("--dbhost <dbhost>", "Database host").option("--dbport <dbport>", "Database port").option("--dbname <dbname>", "Database name").option("--dbusername <dbusername>", "Database username").option("--dbpassword <dbpassword>", "Database password").option("--dbssl <dbssl>", "Database SSL").option("--dbfile <dbfile>", "Database file path for sqlite").option("--skip-db", "Skip database configuration").option("--template <template>", "Specify a Strapi template").option("--template-branch <templateBranch>", "Specify a branch for the template").option("--template-path <templatePath>", "Specify a path to the template inside the repository").description("create a new application");
1292
+
1293
+ const { version } = fse.readJSONSync(join$1(__dirname, '..', 'package.json'));
1294
+ const command = new commander.Command('create-strapi-app').version(version).arguments('[directory]').usage('[directory] [options]').option('--quickstart', 'Quickstart app creation (deprecated)').option('--no-run', 'Do not start the application after it is created.')// setup options
1295
+ .option('--ts, --typescript', 'Initialize the project with TypeScript (default)').option('--js, --javascript', 'Initialize the project with Javascript')// Package manager options
1296
+ .option('--use-npm', 'Use npm as the project package manager').option('--use-yarn', 'Use yarn as the project package manager').option('--use-pnpm', 'Use pnpm as the project package manager')// dependencies options
1297
+ .option('--install', 'Install dependencies').option('--no-install', 'Do not install dependencies')// Cloud options
1298
+ .option('--skip-cloud', 'Skip cloud login and project creation')// Example app
1299
+ .option('--example', 'Use an example app').option('--no-example', 'Do not use an example app')// git options
1300
+ .option('--git-init', 'Initialize a git repository').option('--no-git-init', 'Do no initialize a git repository')// Database options
1301
+ .option('--dbclient <dbclient>', 'Database client').option('--dbhost <dbhost>', 'Database host').option('--dbport <dbport>', 'Database port').option('--dbname <dbname>', 'Database name').option('--dbusername <dbusername>', 'Database username').option('--dbpassword <dbpassword>', 'Database password').option('--dbssl <dbssl>', 'Database SSL').option('--dbfile <dbfile>', 'Database file path for sqlite').option('--skip-db', 'Skip database configuration').option('--template <template>', 'Specify a Strapi template').option('--template-branch <templateBranch>', 'Specify a branch for the template').option('--template-path <templatePath>', 'Specify a path to the template inside the repository').description('create a new application');
1120
1302
  async function run(args) {
1121
- const options = command.parse(args).opts();
1122
- const directory$1 = command.args[0];
1123
- logger.title(
1124
- "Strapi",
1125
- `${chalk.green(chalk.bold(`v${version}`))} ${chalk.bold("🚀 Let's create your new project")}
1126
- `
1127
- );
1128
- if ((options.javascript !== void 0 || options.typescript !== void 0) && options.template !== void 0) {
1129
- logger.fatal(
1130
- `You cannot use ${chalk.bold("--javascript")} or ${chalk.bold("--typescript")} with ${chalk.bold("--template")}`
1131
- );
1132
- }
1133
- if (options.javascript === true && options.typescript === true) {
1134
- logger.fatal(
1135
- `You cannot use both ${chalk.bold("--typescript")} (--ts) and ${chalk.bold("--javascript")} (--js) flags together`
1136
- );
1137
- }
1138
- if (options.example === true && options.template !== void 0) {
1139
- logger.fatal(`You cannot use ${chalk.bold("--example")} with ${chalk.bold("--template")}`);
1140
- }
1141
- if (options.template !== void 0 && options.template.startsWith("-")) {
1142
- logger.fatal(`Template name ${chalk.bold(`"${options.template}"`)} is invalid`);
1143
- }
1144
- if ([options.useNpm, options.usePnpm, options.useYarn].filter(Boolean).length > 1) {
1145
- logger.fatal(
1146
- `You cannot specify multiple package managers at the same time ${chalk.bold("(--use-npm, --use-pnpm, --use-yarn)")}`
1147
- );
1148
- }
1149
- if (options.quickstart && !directory$1) {
1150
- logger.fatal(
1151
- `Please specify the ${chalk.bold("<directory>")} of your project when using ${chalk.bold("--quickstart")}`
1152
- );
1153
- }
1154
- checkNodeRequirements();
1155
- const appDirectory = directory$1 || await directory();
1156
- const rootPath = await checkInstallPath(appDirectory);
1157
- if (!options.skipCloud) {
1158
- await handleCloudLogin();
1159
- }
1160
- const tmpPath = join$1(os$1.tmpdir(), `strapi${crypto.randomBytes(6).toString("hex")}`);
1161
- const scope = {
1162
- rootPath,
1163
- name: basename(rootPath),
1164
- packageManager: getPkgManager(options),
1165
- database: await getDatabaseInfos(options),
1166
- template: options.template,
1167
- templateBranch: options.templateBranch,
1168
- templatePath: options.templatePath,
1169
- isQuickstart: options.quickstart,
1170
- useExample: false,
1171
- runApp: options.quickstart === true && options.run !== false,
1172
- strapiVersion: version,
1173
- packageJsonStrapi: {
1174
- template: options.template
1175
- },
1176
- uuid: (process.env.STRAPI_UUID_PREFIX || "") + crypto.randomUUID(),
1177
- docker: process.env.DOCKER === "true",
1178
- deviceId: machineID(),
1179
- tmpPath,
1180
- gitInit: true,
1181
- devDependencies: {},
1182
- dependencies: {
1183
- "@strapi/strapi": version,
1184
- "@strapi/plugin-users-permissions": version,
1185
- "@strapi/plugin-cloud": version,
1186
- // third party
1187
- react: "^18.0.0",
1188
- "react-dom": "^18.0.0",
1189
- "react-router-dom": "^6.0.0",
1190
- "styled-components": "^6.0.0"
1191
- }
1192
- };
1193
- if (options.template !== void 0) {
1194
- scope.useExample = false;
1195
- } else if (options.example === true) {
1196
- scope.useExample = true;
1197
- } else if (options.example === false || options.quickstart === true) {
1198
- scope.useExample = false;
1199
- } else {
1200
- scope.useExample = await example();
1201
- }
1202
- if (options.javascript === true) {
1203
- scope.useTypescript = false;
1204
- } else if (options.typescript === true || options.quickstart) {
1205
- scope.useTypescript = true;
1206
- } else if (!options.template) {
1207
- scope.useTypescript = await typescript();
1208
- }
1209
- if (options.install === true || options.quickstart) {
1210
- scope.installDependencies = true;
1211
- } else if (options.install === false) {
1212
- scope.installDependencies = false;
1213
- } else {
1214
- scope.installDependencies = await installDependencies(scope.packageManager);
1215
- }
1216
- if (scope.useTypescript) {
1217
- scope.devDependencies = {
1218
- ...scope.devDependencies,
1219
- typescript: "^5",
1220
- "@types/node": "^20",
1221
- "@types/react": "^18",
1222
- "@types/react-dom": "^18"
1303
+ const options = command.parse(args).opts();
1304
+ const directory$1 = command.args[0];
1305
+ logger.title('Strapi', `${chalk.green(chalk.bold(`v${version}`))} ${chalk.bold("🚀 Let's create your new project")}\n`);
1306
+ if ((options.javascript !== undefined || options.typescript !== undefined) && options.template !== undefined) {
1307
+ logger.fatal(`You cannot use ${chalk.bold('--javascript')} or ${chalk.bold('--typescript')} with ${chalk.bold('--template')}`);
1308
+ }
1309
+ if (options.javascript === true && options.typescript === true) {
1310
+ logger.fatal(`You cannot use both ${chalk.bold('--typescript')} (--ts) and ${chalk.bold('--javascript')} (--js) flags together`);
1311
+ }
1312
+ // Only prompt the example app option if there is no template option
1313
+ if (options.example === true && options.template !== undefined) {
1314
+ logger.fatal(`You cannot use ${chalk.bold('--example')} with ${chalk.bold('--template')}`);
1315
+ }
1316
+ if (options.template !== undefined && options.template.startsWith('-')) {
1317
+ logger.fatal(`Template name ${chalk.bold(`"${options.template}"`)} is invalid`);
1318
+ }
1319
+ if ([
1320
+ options.useNpm,
1321
+ options.usePnpm,
1322
+ options.useYarn
1323
+ ].filter(Boolean).length > 1) {
1324
+ logger.fatal(`You cannot specify multiple package managers at the same time ${chalk.bold('(--use-npm, --use-pnpm, --use-yarn)')}`);
1325
+ }
1326
+ if (options.quickstart && !directory$1) {
1327
+ logger.fatal(`Please specify the ${chalk.bold('<directory>')} of your project when using ${chalk.bold('--quickstart')}`);
1328
+ }
1329
+ checkNodeRequirements();
1330
+ const appDirectory = directory$1 || await directory();
1331
+ const rootPath = await checkInstallPath(appDirectory);
1332
+ if (!options.skipCloud) {
1333
+ await handleCloudLogin();
1334
+ }
1335
+ const tmpPath = join$1(os$1.tmpdir(), `strapi${crypto.randomBytes(6).toString('hex')}`);
1336
+ const scope = {
1337
+ rootPath,
1338
+ name: basename(rootPath),
1339
+ packageManager: getPkgManager(options),
1340
+ database: await getDatabaseInfos(options),
1341
+ template: options.template,
1342
+ templateBranch: options.templateBranch,
1343
+ templatePath: options.templatePath,
1344
+ isQuickstart: options.quickstart,
1345
+ useExample: false,
1346
+ runApp: options.quickstart === true && options.run !== false,
1347
+ strapiVersion: version,
1348
+ packageJsonStrapi: {
1349
+ template: options.template
1350
+ },
1351
+ uuid: (process.env.STRAPI_UUID_PREFIX || '') + crypto.randomUUID(),
1352
+ docker: process.env.DOCKER === 'true',
1353
+ deviceId: machineID(),
1354
+ tmpPath,
1355
+ gitInit: true,
1356
+ devDependencies: {},
1357
+ dependencies: {
1358
+ '@strapi/strapi': version,
1359
+ '@strapi/plugin-users-permissions': version,
1360
+ '@strapi/plugin-cloud': version,
1361
+ // third party
1362
+ react: '^18.0.0',
1363
+ 'react-dom': '^18.0.0',
1364
+ 'react-router-dom': '^6.0.0',
1365
+ 'styled-components': '^6.0.0'
1366
+ }
1223
1367
  };
1224
- }
1225
- if (options.gitInit === true || options.quickstart) {
1226
- scope.gitInit = true;
1227
- } else if (options.gitInit === false) {
1228
- scope.gitInit = false;
1229
- } else {
1230
- scope.gitInit = await gitInit();
1231
- }
1232
- addDatabaseDependencies(scope);
1233
- try {
1234
- await createStrapi(scope);
1235
- } catch (error) {
1236
- if (!(error instanceof Error)) {
1237
- throw error;
1238
- }
1239
- await trackError({ scope, error });
1240
- logger.fatal(error.message);
1241
- }
1368
+ if (options.template !== undefined) {
1369
+ scope.useExample = false;
1370
+ } else if (options.example === true) {
1371
+ scope.useExample = true;
1372
+ } else if (options.example === false || options.quickstart === true) {
1373
+ scope.useExample = false;
1374
+ } else {
1375
+ scope.useExample = await example();
1376
+ }
1377
+ if (options.javascript === true) {
1378
+ scope.useTypescript = false;
1379
+ } else if (options.typescript === true || options.quickstart) {
1380
+ scope.useTypescript = true;
1381
+ } else if (!options.template) {
1382
+ scope.useTypescript = await typescript();
1383
+ }
1384
+ if (options.install === true || options.quickstart) {
1385
+ scope.installDependencies = true;
1386
+ } else if (options.install === false) {
1387
+ scope.installDependencies = false;
1388
+ } else {
1389
+ scope.installDependencies = await installDependencies(scope.packageManager);
1390
+ }
1391
+ if (scope.useTypescript) {
1392
+ scope.devDependencies = {
1393
+ ...scope.devDependencies,
1394
+ typescript: '^5',
1395
+ '@types/node': '^20',
1396
+ '@types/react': '^18',
1397
+ '@types/react-dom': '^18'
1398
+ };
1399
+ }
1400
+ if (options.gitInit === true || options.quickstart) {
1401
+ scope.gitInit = true;
1402
+ } else if (options.gitInit === false) {
1403
+ scope.gitInit = false;
1404
+ } else {
1405
+ scope.gitInit = await gitInit();
1406
+ }
1407
+ addDatabaseDependencies(scope);
1408
+ try {
1409
+ await createStrapi(scope);
1410
+ } catch (error) {
1411
+ if (!(error instanceof Error)) {
1412
+ throw error;
1413
+ }
1414
+ await trackError({
1415
+ scope,
1416
+ error
1417
+ });
1418
+ logger.fatal(error.message);
1419
+ }
1242
1420
  }
1243
1421
  function getPkgManager(options) {
1244
- if (options.useNpm === true) {
1245
- return "npm";
1246
- }
1247
- if (options.usePnpm === true) {
1248
- return "pnpm";
1249
- }
1250
- if (options.useYarn === true) {
1251
- return "yarn";
1252
- }
1253
- const userAgent = process.env.npm_config_user_agent || "";
1254
- if (userAgent.startsWith("yarn")) {
1255
- return "yarn";
1256
- }
1257
- if (userAgent.startsWith("pnpm")) {
1258
- return "pnpm";
1259
- }
1260
- return "npm";
1422
+ if (options.useNpm === true) {
1423
+ return 'npm';
1424
+ }
1425
+ if (options.usePnpm === true) {
1426
+ return 'pnpm';
1427
+ }
1428
+ if (options.useYarn === true) {
1429
+ return 'yarn';
1430
+ }
1431
+ const userAgent = process.env.npm_config_user_agent || '';
1432
+ if (userAgent.startsWith('yarn')) {
1433
+ return 'yarn';
1434
+ }
1435
+ if (userAgent.startsWith('pnpm')) {
1436
+ return 'pnpm';
1437
+ }
1438
+ return 'npm';
1261
1439
  }
1262
- export {
1263
- createStrapi,
1264
- run
1265
- };
1440
+
1441
+ export { createStrapi, run };
1266
1442
  //# sourceMappingURL=index.mjs.map