create-strapi-app 5.12.1 → 5.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/cloud.js +65 -0
  2. package/dist/cloud.js.map +1 -0
  3. package/dist/cloud.mjs +63 -0
  4. package/dist/cloud.mjs.map +1 -0
  5. package/dist/create-strapi.js +251 -0
  6. package/dist/create-strapi.js.map +1 -0
  7. package/dist/create-strapi.mjs +249 -0
  8. package/dist/create-strapi.mjs.map +1 -0
  9. package/dist/index.js +35 -1330
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +15 -1291
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/prompts.js +66 -0
  14. package/dist/prompts.js.map +1 -0
  15. package/dist/prompts.mjs +60 -0
  16. package/dist/prompts.mjs.map +1 -0
  17. package/dist/types.js +8 -0
  18. package/dist/types.js.map +1 -0
  19. package/dist/types.mjs +6 -0
  20. package/dist/types.mjs.map +1 -0
  21. package/dist/utils/check-install-path.js +28 -0
  22. package/dist/utils/check-install-path.js.map +1 -0
  23. package/dist/utils/check-install-path.mjs +26 -0
  24. package/dist/utils/check-install-path.mjs.map +1 -0
  25. package/dist/utils/check-requirements.js +26 -0
  26. package/dist/utils/check-requirements.js.map +1 -0
  27. package/dist/utils/check-requirements.mjs +24 -0
  28. package/dist/utils/check-requirements.mjs.map +1 -0
  29. package/dist/utils/database.js +190 -0
  30. package/dist/utils/database.js.map +1 -0
  31. package/dist/utils/database.mjs +187 -0
  32. package/dist/utils/database.mjs.map +1 -0
  33. package/dist/utils/dot-env.js +46 -0
  34. package/dist/utils/dot-env.js.map +1 -0
  35. package/dist/utils/dot-env.mjs +44 -0
  36. package/dist/utils/dot-env.mjs.map +1 -0
  37. package/dist/utils/engines.js +9 -0
  38. package/dist/utils/engines.js.map +1 -0
  39. package/dist/utils/engines.mjs +7 -0
  40. package/dist/utils/engines.mjs.map +1 -0
  41. package/dist/utils/get-package-manager-args.js +135 -0
  42. package/dist/utils/get-package-manager-args.js.map +1 -0
  43. package/dist/utils/get-package-manager-args.mjs +132 -0
  44. package/dist/utils/get-package-manager-args.mjs.map +1 -0
  45. package/dist/utils/git.js +73 -0
  46. package/dist/utils/git.js.map +1 -0
  47. package/dist/utils/git.mjs +71 -0
  48. package/dist/utils/git.mjs.map +1 -0
  49. package/dist/utils/gitignore.js +139 -0
  50. package/dist/utils/gitignore.js.map +1 -0
  51. package/dist/utils/gitignore.mjs +137 -0
  52. package/dist/utils/gitignore.mjs.map +1 -0
  53. package/dist/utils/logger.js +54 -0
  54. package/dist/utils/logger.js.map +1 -0
  55. package/dist/utils/logger.mjs +52 -0
  56. package/dist/utils/logger.mjs.map +1 -0
  57. package/dist/utils/machine-id.js +17 -0
  58. package/dist/utils/machine-id.js.map +1 -0
  59. package/dist/utils/machine-id.mjs +15 -0
  60. package/dist/utils/machine-id.mjs.map +1 -0
  61. package/dist/utils/package-json.js +32 -0
  62. package/dist/utils/package-json.js.map +1 -0
  63. package/dist/utils/package-json.mjs +30 -0
  64. package/dist/utils/package-json.mjs.map +1 -0
  65. package/dist/utils/parse-to-chalk.js +25 -0
  66. package/dist/utils/parse-to-chalk.js.map +1 -0
  67. package/dist/utils/parse-to-chalk.mjs +23 -0
  68. package/dist/utils/parse-to-chalk.mjs.map +1 -0
  69. package/dist/utils/template.js +180 -0
  70. package/dist/utils/template.js.map +1 -0
  71. package/dist/utils/template.mjs +159 -0
  72. package/dist/utils/template.mjs.map +1 -0
  73. package/dist/utils/usage.js +90 -0
  74. package/dist/utils/usage.js.map +1 -0
  75. package/dist/utils/usage.mjs +87 -0
  76. package/dist/utils/usage.mjs.map +1 -0
  77. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -1,1296 +1,20 @@
1
- import path, { join as join$1, resolve, basename } from 'node:path';
2
- import os$1 from 'node:os';
1
+ import { join, basename } from 'node:path';
2
+ import os from 'node:os';
3
3
  import chalk from 'chalk';
4
4
  import commander from 'commander';
5
- import crypto, { randomUUID } from 'crypto';
5
+ import crypto from 'crypto';
6
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
-
21
- async function directory() {
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;
31
- }
32
- async function typescript() {
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;
42
- }
43
- async function example() {
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;
53
- }
54
- async function gitInit() {
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;
64
- }
65
- async function installDependencies(packageManager) {
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;
75
- }
76
-
77
- // TODO: move styles to API
78
- const supportedStyles = {
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
86
- };
87
- function parseToChalk(template) {
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;
94
- }
95
-
96
- function assertCloudError(e) {
97
- if (e.response === undefined) {
98
- throw Error('Expected CloudError');
99
- }
100
- }
101
- async function handleCloudLogin() {
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.';
111
- try {
112
- const { data: config } = await cloudApiService.config();
113
- logger.log(parseToChalk(config.projectCreation.introText));
114
- } catch (e) {
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);
149
- }
150
- }
151
- }
152
-
153
- const stripTrailingSlash = (str)=>{
154
- return str.endsWith('/') ? str.slice(0, -1) : str;
155
- };
156
- // Merge template with new project being created
157
- async function copyTemplate(scope, rootPath) {
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}`);
201
- }
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;
215
- }
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
- }
229
- }
230
- async function downloadGithubRepo(rootPath, { owner, repo, branch, subPath }) {
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
- }
263
- }));
264
- }
265
- function isLocalTemplate(template) {
266
- return template.startsWith('file://') || fse.existsSync(path.isAbsolute(template) ? template : path.resolve(process.cwd(), template));
267
- }
268
- function isGithubShorthand(value) {
269
- if (isValidUrl(value)) {
270
- return false;
271
- }
272
- return /^[\w-]+\/[\w-.]+(\/[\w-.]+)*$/.test(value);
273
- }
274
- function isGithubRepo(value) {
275
- try {
276
- const url = new URL(value);
277
- return url.origin === 'https://github.com';
278
- } catch {
279
- return false;
280
- }
281
- }
282
- function isValidUrl(value) {
283
- try {
284
- // eslint-disable-next-line no-new
285
- new URL(value);
286
- return true;
287
- } catch {
288
- return false;
289
- }
290
- }
291
- const OFFICIAL_NAME_REGEX = /^[a-zA-Z]*$/;
292
- async function isOfficialTemplate(template, branch) {
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;
300
- }
301
-
302
- async function isInGitRepository(rootDir) {
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
- }
315
- }
316
- async function isInMercurialRepository(rootDir) {
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
- }
330
- }
331
- async function tryGitInit(rootDir) {
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
- }
367
- }
368
-
369
- // Add properties from the package.json strapi key in the metadata
370
- function addPackageJsonStrapiMetadata(metadata, scope) {
371
- const { packageJsonStrapi = {} } = scope;
372
- return _.defaults(metadata, packageJsonStrapi);
373
- }
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
- };
406
- };
407
- function trackEvent(event, payload) {
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
- }
427
- }
428
- async function trackError({ scope, error }) {
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
- }
438
- }
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
- }
449
- }
450
-
451
- const engines = {
452
- node: '>=18.0.0 <=22.x.x',
453
- npm: '>=6.0.0'
454
- };
455
-
456
- async function createPackageJSON(scope) {
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
- });
477
- }
478
-
479
- const generateASecret = ()=>crypto.randomBytes(16).toString('base64');
480
- const envTmpl = `
481
- # Server
482
- HOST=0.0.0.0
483
- PORT=1337
484
-
485
- # Secrets
486
- APP_KEYS=<%= appKeys %>
487
- API_TOKEN_SALT=<%= apiTokenSalt %>
488
- ADMIN_JWT_SECRET=<%= adminJwtToken %>
489
- TRANSFER_TOKEN_SALT=<%= transferTokenSalt %>
490
-
491
- # Database
492
- DATABASE_CLIENT=<%= database.client %>
493
- DATABASE_HOST=<%= database.connection.host %>
494
- DATABASE_PORT=<%= database.connection.port %>
495
- DATABASE_NAME=<%= database.connection.database %>
496
- DATABASE_USERNAME=<%= database.connection.username %>
497
- DATABASE_PASSWORD=<%= database.connection.password %>
498
- DATABASE_SSL=<%= database.connection.ssl %>
499
- DATABASE_FILENAME=<%= database.connection.filename %>
500
- `;
501
- function generateDotEnv(scope) {
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
- });
516
- }
517
-
518
- function isStderrError(error) {
519
- return typeof error === 'object' && error !== null && 'stderr' in error && typeof error.stderr === 'string';
520
- }
521
-
522
- const MAX_PREFIX_LENGTH = 8;
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));
527
- };
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');
538
- };
539
- const logger = {
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
- }
568
- };
569
-
570
- const baseGitIgnore = `
571
- ############################
572
- # OS X
573
- ############################
574
-
575
- .DS_Store
576
- .AppleDouble
577
- .LSOverride
578
- Icon
579
- .Spotlight-V100
580
- .Trashes
581
- ._*
582
-
583
-
584
- ############################
585
- # Linux
586
- ############################
587
-
588
- *~
589
-
590
-
591
- ############################
592
- # Windows
593
- ############################
594
-
595
- Thumbs.db
596
- ehthumbs.db
597
- Desktop.ini
598
- $RECYCLE.BIN/
599
- *.cab
600
- *.msi
601
- *.msm
602
- *.msp
603
-
604
-
605
- ############################
606
- # Packages
607
- ############################
608
-
609
- *.7z
610
- *.csv
611
- *.dat
612
- *.dmg
613
- *.gz
614
- *.iso
615
- *.jar
616
- *.rar
617
- *.tar
618
- *.zip
619
- *.com
620
- *.class
621
- *.dll
622
- *.exe
623
- *.o
624
- *.seed
625
- *.so
626
- *.swo
627
- *.swp
628
- *.swn
629
- *.swm
630
- *.out
631
- *.pid
632
-
633
-
634
- ############################
635
- # Logs and databases
636
- ############################
637
-
638
- .tmp
639
- *.log
640
- *.sql
641
- *.sqlite
642
- *.sqlite3
643
-
644
-
645
- ############################
646
- # Misc.
647
- ############################
648
-
649
- *#
650
- ssl
651
- .idea
652
- nbproject
653
- public/uploads/*
654
- !public/uploads/.gitkeep
655
- .tsbuildinfo
656
- .eslintcache
657
-
658
- ############################
659
- # Node.js
660
- ############################
661
-
662
- lib-cov
663
- lcov.info
664
- pids
665
- logs
666
- results
667
- node_modules
668
- .node_history
669
-
670
- ############################
671
- # Package managers
672
- ############################
673
-
674
- .yarn/*
675
- !.yarn/cache
676
- !.yarn/unplugged
677
- !.yarn/patches
678
- !.yarn/releases
679
- !.yarn/sdks
680
- !.yarn/versions
681
- .pnp.*
682
- yarn-error.log
683
-
684
- ############################
685
- # Tests
686
- ############################
687
-
688
- coverage
689
-
690
- ############################
691
- # Strapi
692
- ############################
693
-
694
- .env
695
- license.txt
696
- exports
697
- .strapi
698
- dist
699
- build
700
- .strapi-updater.json
701
- .strapi-cloud.json
702
- `;
703
- const gitIgnore = baseGitIgnore.trim();
704
-
705
- const installArguments = [
706
- 'install'
707
- ];
708
- // Set command line options for specific package managers, with full semver ranges
709
- const installArgumentsMap = {
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
- }
725
- };
726
- // Set environment variables for specific package managers, with full semver ranges
727
- const installEnvMap = {
728
- yarn: {
729
- '>=4': {
730
- YARN_HTTP_TIMEOUT: '1000000'
731
- },
732
- '*': {}
733
- },
734
- npm: {
735
- '*': {}
736
- },
737
- pnpm: {
738
- '*': {}
739
- }
740
- };
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}`);
759
- }
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
779
- }
780
- function mergeArguments(acc, curr) {
781
- return [
782
- ...acc,
783
- ...curr
784
- ];
785
- }
786
- function mergeEnvVars(acc, curr) {
787
- return {
788
- ...acc,
789
- ...curr
790
- };
791
- }
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
- };
830
- };
831
-
832
- async function createStrapi(scope) {
833
- const { rootPath } = scope;
834
- try {
835
- await fse.ensureDir(rootPath);
836
- await createApp(scope);
837
- } catch (error) {
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
- });
889
- try {
890
- await createPackageJSON(scope);
891
- await trackUsage({
892
- event: 'didWritePackageJSON',
893
- scope
894
- });
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!`);
971
- logger.log([
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
- ''
986
- ]);
987
- if (useExample) {
988
- logger.log([
989
- 'Seed your database with sample data.',
990
- `${cmd} seed:example`,
991
- ''
992
- ]);
993
- }
994
- logger.log([
995
- 'Display all available commands.',
996
- `${cmd} strapi\n`
997
- ]);
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');
1040
- }
1041
- }
1042
- }
1043
- async function runInstall({ rootPath, packageManager }) {
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;
1063
- }
1064
-
1065
- function checkNodeRequirements() {
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
- }
1080
- }
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;
1099
- }
1100
-
1101
- function machineID() {
1102
- try {
1103
- const deviceId = machineIdSync();
1104
- return deviceId;
1105
- } catch (error) {
1106
- const deviceId = randomUUID();
1107
- return deviceId;
1108
- }
1109
- }
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
- ];
1124
- const DEFAULT_CONFIG = {
1125
- client: 'sqlite',
1126
- connection: {
1127
- filename: '.tmp/data.db'
1128
- }
1129
- };
1130
- async function dbPrompt() {
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
- };
1163
- }
1164
- async function getDatabaseInfos(options) {
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;
1201
- }
1202
- const sqlClientModule = {
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
- }
1212
- };
1213
- function addDatabaseDependencies(scope) {
1214
- scope.dependencies = {
1215
- ...scope.dependencies,
1216
- ...sqlClientModule[scope.database.client]
1217
- };
1218
- }
1219
- const DEFAULT_PORTS = {
1220
- postgres: 5432,
1221
- mysql: 3306,
1222
- sqlite: undefined
1223
- };
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
- });
1271
- const dbQuestions = {
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
- ]
1291
- };
1292
-
1293
- const { version } = fse.readJSONSync(join$1(__dirname, '..', 'package.json'));
7
+ import { directory, example, typescript, installDependencies, gitInit } from './prompts.mjs';
8
+ import { handleCloudLogin } from './cloud.mjs';
9
+ import { createStrapi } from './create-strapi.mjs';
10
+ import { checkNodeRequirements } from './utils/check-requirements.mjs';
11
+ import { checkInstallPath } from './utils/check-install-path.mjs';
12
+ import { machineID } from './utils/machine-id.mjs';
13
+ import { trackError } from './utils/usage.mjs';
14
+ import { getDatabaseInfos, addDatabaseDependencies } from './utils/database.mjs';
15
+ import { logger } from './utils/logger.mjs';
16
+
17
+ const { version } = fse.readJSONSync(join(__dirname, '..', 'package.json'));
1294
18
  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
19
  .option('--ts, --typescript', 'Initialize the project with TypeScript (default)').option('--js, --javascript', 'Initialize the project with Javascript')// Package manager options
1296
20
  .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
@@ -1332,7 +56,7 @@ async function run(args) {
1332
56
  if (!options.skipCloud) {
1333
57
  await handleCloudLogin();
1334
58
  }
1335
- const tmpPath = join$1(os$1.tmpdir(), `strapi${crypto.randomBytes(6).toString('hex')}`);
59
+ const tmpPath = join(os.tmpdir(), `strapi${crypto.randomBytes(6).toString('hex')}`);
1336
60
  const scope = {
1337
61
  rootPath,
1338
62
  name: basename(rootPath),