@dotcms/create-app 1.2.5-next.7 → 1.2.6-next.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/index.js ADDED
@@ -0,0 +1,1522 @@
1
+ #!/usr/bin/env node
2
+
3
+ // libs/sdk/create-app/src/index.ts
4
+ import cfonts from "cfonts";
5
+ import chalk4 from "chalk";
6
+ import { Command } from "commander";
7
+ import { execa as execa3 } from "execa";
8
+ import fs4 from "fs-extra";
9
+ import ora from "ora";
10
+ import path5 from "path";
11
+
12
+ // libs/sdk/create-app/src/api/index.ts
13
+ import axios from "axios";
14
+ import chalk from "chalk";
15
+
16
+ // libs/sdk/create-app/src/constants/index.ts
17
+ var DOTCMS_HOST = "http://localhost:8082";
18
+ var DOTCMS_USER = {
19
+ username: "admin@dotcms.com",
20
+ password: "admin"
21
+ };
22
+ var DOTCMS_HEALTH_API = `${DOTCMS_HOST}/api/v1/appconfiguration`;
23
+ var DOTCMS_TOKEN_API = `${DOTCMS_HOST}/api/v1/authentication/api-token`;
24
+ var DOTCMS_EMA_CONFIG_API = `${DOTCMS_HOST}/api/v1/apps/dotema-config-v2/`;
25
+ var DOTCMS_SITE_API = `${DOTCMS_HOST}/api/v1/site/`;
26
+ var CLOUD_HEALTH_CHECK_RETRIES = 5;
27
+ var LOCAL_HEALTH_CHECK_RETRIES = 60;
28
+ var FRAMEWORKS = [
29
+ "nextjs",
30
+ "astro",
31
+ "angular",
32
+ "angular-ssr"
33
+ ];
34
+ var FRAMEWORKS_CHOICES = [
35
+ { name: "Next.js", value: "nextjs" },
36
+ { name: "Astro", value: "astro" },
37
+ { name: "Angular", value: "angular" },
38
+ { name: "Angular (SSR)", value: "angular-ssr" }
39
+ ];
40
+ var NEXTJS_DEPENDENCIES = [
41
+ "@dotcms/client",
42
+ "@dotcms/experiments",
43
+ "@dotcms/react",
44
+ "@dotcms/types",
45
+ "@dotcms/uve",
46
+ "@tinymce/tinymce-react",
47
+ "next",
48
+ "react",
49
+ "react-dom"
50
+ ];
51
+ var NEXTJS_DEPENDENCIES_DEV = [
52
+ "@tailwindcss/postcss",
53
+ "@tailwindcss/typography",
54
+ "eslint",
55
+ "eslint-config-next",
56
+ "postcss",
57
+ "prettier",
58
+ "tailwindcss"
59
+ ];
60
+ var ASTRO_DEPENDENCIES = [
61
+ "@astrojs/check",
62
+ "@astrojs/react",
63
+ "@astrojs/ts-plugin",
64
+ "@astrojs/vercel",
65
+ "@dotcms/client",
66
+ "@dotcms/react",
67
+ "@dotcms/types",
68
+ "@dotcms/uve",
69
+ "@tailwindcss/typography",
70
+ "@tailwindcss/vite",
71
+ "@tinymce/tinymce-react",
72
+ "@types/react",
73
+ "@types/react-dom",
74
+ "astro",
75
+ "dotenv",
76
+ "react",
77
+ "react-dom",
78
+ "tailwindcss",
79
+ "typescript"
80
+ ];
81
+ var ASTRO_DEPENDENCIES_DEV = [
82
+ "@types/node",
83
+ "prettier",
84
+ "prettier-plugin-astro"
85
+ ];
86
+ var ANGULAR_DEPENDENCIES = [
87
+ "@angular/animations",
88
+ "@angular/common",
89
+ "@angular/compiler",
90
+ "@angular/core",
91
+ "@angular/forms",
92
+ "@angular/platform-browser",
93
+ "@angular/platform-browser-dynamic",
94
+ "@angular/router",
95
+ "@dotcms/angular",
96
+ "@dotcms/client",
97
+ "@dotcms/types",
98
+ "@dotcms/uve",
99
+ "rxjs",
100
+ "tslib",
101
+ "zone.js"
102
+ ];
103
+ var ANGULAR_DEPENDENCIES_DEV = [
104
+ "@angular/build",
105
+ "@angular/cli",
106
+ "@angular/compiler-cli",
107
+ "@tailwindcss/typography",
108
+ "@types/jasmine",
109
+ "autoprefixer",
110
+ "jasmine-core",
111
+ "karma",
112
+ "karma-chrome-launcher",
113
+ "karma-coverage",
114
+ "karma-jasmine",
115
+ "karma-jasmine-html-reporter",
116
+ "postcss",
117
+ "tailwindcss",
118
+ "typescript"
119
+ ];
120
+ var ANGULAR_SSR_DEPENDENCIES = [
121
+ "@angular/common",
122
+ "@angular/compiler",
123
+ "@angular/core",
124
+ "@angular/forms",
125
+ "@angular/platform-browser",
126
+ "@angular/platform-server",
127
+ "@angular/router",
128
+ "@angular/ssr",
129
+ "@dotcms/angular",
130
+ "@dotcms/client",
131
+ "@dotcms/types",
132
+ "@dotcms/uve",
133
+ "@tailwindcss/postcss",
134
+ "@types/dotenv",
135
+ "dotenv",
136
+ "express",
137
+ "postcss",
138
+ "rxjs",
139
+ "tailwindcss",
140
+ "tslib",
141
+ "zone.js"
142
+ ];
143
+ var ANGULAR_SSR_DEPENDENCIES_DEV = [
144
+ "@angular/build",
145
+ "@angular/cli",
146
+ "@angular/compiler-cli",
147
+ "@tailwindcss/typography",
148
+ "@types/express",
149
+ "@types/jasmine",
150
+ "@types/node",
151
+ "jasmine-core",
152
+ "karma",
153
+ "karma-chrome-launcher",
154
+ "karma-coverage",
155
+ "karma-jasmine",
156
+ "karma-jasmine-html-reporter",
157
+ "typescript"
158
+ ];
159
+
160
+ // libs/sdk/create-app/src/errors/index.ts
161
+ var FailedToCreateFrontendProjectError = class extends Error {
162
+ constructor(framework) {
163
+ super(
164
+ `Failed to create frontend project for framework: ${framework}. Please check if you have git installed and an active internet connection.`
165
+ );
166
+ }
167
+ };
168
+ var FailedToDownloadDockerComposeError = class extends Error {
169
+ constructor() {
170
+ super(`Failed to download the docker compose file`);
171
+ }
172
+ };
173
+ var FailedToSetUpUVEConfig = class extends Error {
174
+ constructor() {
175
+ super(`Failed to set up UVE configuration in DotCMS.`);
176
+ }
177
+ };
178
+ var FailedToGetDefaultSiteError = class extends Error {
179
+ constructor() {
180
+ super(`Failed to get default site identifier from DotCMS.`);
181
+ }
182
+ };
183
+
184
+ // libs/sdk/create-app/src/result.ts
185
+ function Ok(val) {
186
+ return { ok: true, val };
187
+ }
188
+ function Err(val) {
189
+ return { ok: false, val };
190
+ }
191
+
192
+ // libs/sdk/create-app/src/api/index.ts
193
+ function getSafeErrorDetails(err) {
194
+ if (axios.isAxiosError(err)) {
195
+ const details = [
196
+ err.response?.status ? `status=${err.response.status}` : null,
197
+ err.response?.statusText ? `statusText=${err.response.statusText}` : null,
198
+ err.code ? `code=${err.code}` : null,
199
+ err.message ? `message=${err.message}` : null
200
+ ].filter(Boolean);
201
+ return details.length > 0 ? details.join(", ") : "Axios request failed";
202
+ }
203
+ if (err instanceof Error) {
204
+ return err.message;
205
+ }
206
+ return String(err);
207
+ }
208
+ var DotCMSApi = class {
209
+ static {
210
+ this.defaultTokenApi = DOTCMS_TOKEN_API;
211
+ }
212
+ static {
213
+ this.defaultSiteApi = DOTCMS_SITE_API;
214
+ }
215
+ static {
216
+ this.defaultUveConfigApi = DOTCMS_EMA_CONFIG_API;
217
+ }
218
+ /** Get authentication token */
219
+ static async getAuthToken({
220
+ payload,
221
+ url
222
+ }) {
223
+ const endpoint = url || this.defaultTokenApi;
224
+ try {
225
+ const res = await axios.post(endpoint, payload);
226
+ return Ok(res.data.entity.token);
227
+ } catch (err) {
228
+ if (axios.isAxiosError(err)) {
229
+ if (err.response?.status === 401) {
230
+ return Err(
231
+ chalk.red("\n\u274C Authentication failed\n\n") + chalk.white("Invalid username or password.\n\n") + chalk.yellow("Please check your credentials and try again:\n") + chalk.white(" \u2022 Verify your username is correct\n") + chalk.white(" \u2022 Ensure your password is correct\n") + chalk.white(" \u2022 Check if your account is active\n")
232
+ );
233
+ } else if (err.code === "ECONNREFUSED") {
234
+ return Err(
235
+ chalk.red("\n\u274C Connection refused\n\n") + chalk.white(`Could not connect to dotCMS at: ${endpoint}
236
+
237
+ `) + chalk.yellow("Please verify:\n") + chalk.white(" \u2022 The URL is correct\n") + chalk.white(" \u2022 The dotCMS instance is running\n") + chalk.white(" \u2022 There are no firewall issues\n")
238
+ );
239
+ } else if (err.response) {
240
+ return Err(
241
+ chalk.red(`
242
+ \u274C Server error (${err.response.status})
243
+
244
+ `) + chalk.white("The dotCMS server returned an error.\n") + chalk.gray(`Details: ${err.response.statusText || "Unknown error"}
245
+ `)
246
+ );
247
+ }
248
+ }
249
+ return Err(
250
+ chalk.red("\n\u274C Failed to get authentication token\n\n") + chalk.gray(err instanceof Error ? err.message : String(err))
251
+ );
252
+ }
253
+ }
254
+ /** Get default site */
255
+ static async getDefaultSite({
256
+ authenticationToken,
257
+ url
258
+ }) {
259
+ try {
260
+ const endpoint = (url || this.defaultSiteApi) + "defaultSite";
261
+ const res = await axios.get(endpoint, {
262
+ headers: { Authorization: `Bearer ${authenticationToken}` }
263
+ });
264
+ return Ok(res.data);
265
+ } catch (err) {
266
+ console.error(`failed to get default site identifier: ${getSafeErrorDetails(err)}`);
267
+ return Err(new FailedToGetDefaultSiteError());
268
+ }
269
+ }
270
+ /** Setup UVE Config */
271
+ static async setupUVEConfig({
272
+ payload,
273
+ siteId,
274
+ authenticationToken,
275
+ url
276
+ }) {
277
+ try {
278
+ const endpoint = (url || this.defaultUveConfigApi) + siteId;
279
+ const res = await axios.post(endpoint, payload, {
280
+ headers: { Authorization: `Bearer ${authenticationToken}` }
281
+ });
282
+ return Ok(res.data.entity);
283
+ } catch (err) {
284
+ console.error(`failed to setup UVE config: ${getSafeErrorDetails(err)}`);
285
+ return Err(new FailedToSetUpUVEConfig());
286
+ }
287
+ }
288
+ };
289
+
290
+ // libs/sdk/create-app/src/asks.ts
291
+ import fs from "fs-extra";
292
+ import inquirer from "inquirer";
293
+ import path2 from "path";
294
+
295
+ // libs/sdk/create-app/src/utils/validation.ts
296
+ import chalk2 from "chalk";
297
+ import path from "path";
298
+ var MAX_PROJECT_NAME_LENGTH = 255;
299
+ var FRAMEWORK_ALIASES = {
300
+ next: "nextjs",
301
+ "next.js": "nextjs",
302
+ ng: "angular",
303
+ "angular-server": "angular-ssr"
304
+ };
305
+ function validateAndNormalizeFramework(framework) {
306
+ if (!framework)
307
+ return void 0;
308
+ const normalized = framework.toLowerCase().replace(/\s+/g, "");
309
+ if (normalized in FRAMEWORK_ALIASES) {
310
+ return FRAMEWORK_ALIASES[normalized];
311
+ }
312
+ if (FRAMEWORKS.includes(normalized)) {
313
+ return normalized;
314
+ }
315
+ throw new Error(
316
+ chalk2.red(`\u274C Invalid framework: "${framework}"`) + "\n\n" + chalk2.white("Supported frameworks:\n") + FRAMEWORKS.map((f) => chalk2.cyan(` \u2022 ${f}`)).join("\n") + "\n\n" + chalk2.gray("\u{1F4A1} Tip: Framework names are case-insensitive\n") + chalk2.gray(" Aliases: next \u2192 nextjs, ng \u2192 angular")
317
+ );
318
+ }
319
+ function normalizeUrl(url) {
320
+ return url.replace(/\/+$/, "");
321
+ }
322
+ function validateUrl(url) {
323
+ if (!url || url.trim() === "")
324
+ return;
325
+ const normalizedUrl = normalizeUrl(url);
326
+ if (!normalizedUrl.includes("://")) {
327
+ throw new Error(
328
+ chalk2.red(`\u274C Invalid URL format: "${normalizedUrl}"`) + "\n\n" + chalk2.white("URLs must include the protocol (http:// or https://)\n\n") + chalk2.cyan("Example:\n https://demo.dotcms.com\n\n") + chalk2.gray("Need help?\n") + chalk2.gray(" \u2022 Use --local flag to run dotCMS in Docker\n") + chalk2.gray(" \u2022 Check our docs: https://dev.dotcms.com")
329
+ );
330
+ }
331
+ let parsed;
332
+ try {
333
+ parsed = new URL(normalizedUrl);
334
+ } catch (error) {
335
+ if (error instanceof TypeError) {
336
+ throw new Error(
337
+ chalk2.red(`\u274C Invalid URL format: "${normalizedUrl}"`) + "\n\n" + chalk2.white("Please provide a valid HTTP/HTTPS URL\n\n") + chalk2.cyan("Example:\n https://demo.dotcms.com")
338
+ );
339
+ }
340
+ throw error;
341
+ }
342
+ if (!["http:", "https:"].includes(parsed.protocol)) {
343
+ throw new Error(
344
+ chalk2.red(`\u274C Unsupported protocol: ${parsed.protocol}`) + "\n\n" + chalk2.white("Only HTTP and HTTPS are supported")
345
+ );
346
+ }
347
+ if (!parsed.hostname) {
348
+ throw new Error(chalk2.red("\u274C URL missing hostname"));
349
+ }
350
+ if (parsed.hostname === "localhost" && parsed.port !== "8082") {
351
+ console.log(
352
+ chalk2.yellow("\u26A0\uFE0F Warning: ") + chalk2.white("Using localhost but port is not 8082 (default dotCMS port)")
353
+ );
354
+ }
355
+ }
356
+ function validateProjectName(projectName) {
357
+ if (projectName === void 0 || projectName === null) {
358
+ return void 0;
359
+ }
360
+ const trimmed = projectName.trim();
361
+ if (trimmed.length === 0) {
362
+ throw new Error(
363
+ chalk2.red("\u274C Invalid project name: cannot be empty") + "\n\n" + chalk2.white("Project name must contain at least one character\n\n") + chalk2.cyan("Valid examples:\n") + chalk2.gray(" \u2022 my-dotcms-app\n") + chalk2.gray(" \u2022 my_project\n") + chalk2.gray(" \u2022 MyProject123")
364
+ );
365
+ }
366
+ if (trimmed === "." || trimmed === ".." || trimmed.includes("../") || trimmed.includes("..\\")) {
367
+ throw new Error(
368
+ chalk2.red(`\u274C Invalid project name: "${projectName}"`) + "\n\n" + chalk2.white("Project name cannot contain path traversal patterns (..)\n\n") + chalk2.gray("Use the --directory flag to specify the parent directory")
369
+ );
370
+ }
371
+ if (path.isAbsolute(trimmed)) {
372
+ throw new Error(
373
+ chalk2.red(`\u274C Invalid project name: "${projectName}"`) + "\n\n" + chalk2.white("Project name cannot be an absolute path\n\n") + chalk2.gray("Use the --directory flag to specify the location")
374
+ );
375
+ }
376
+ if (trimmed.startsWith("-")) {
377
+ throw new Error(
378
+ chalk2.red(`\u274C Invalid project name: "${projectName}"`) + "\n\n" + chalk2.white("Project name cannot start with a hyphen (-)\n\n") + chalk2.gray("This can be confused with command-line flags\n\n") + chalk2.cyan("Try:\n") + chalk2.gray(` \u2022 ${trimmed.slice(1)} (remove leading hyphen)
379
+ `) + chalk2.gray(` \u2022 my${trimmed} (add prefix)`)
380
+ );
381
+ }
382
+ if (trimmed.startsWith(".")) {
383
+ console.warn(
384
+ chalk2.yellow("\n\u26A0\uFE0F Warning: Project name starts with a dot\n") + chalk2.gray("This will create a hidden directory on Unix systems\n")
385
+ );
386
+ }
387
+ const validPattern = /^[a-zA-Z0-9._-]+$/;
388
+ if (!validPattern.test(trimmed)) {
389
+ throw new Error(
390
+ chalk2.red(`\u274C Invalid project name: "${projectName}"`) + "\n\n" + chalk2.white("Project names can only contain:\n") + chalk2.white(" \u2022 Letters (a-z, A-Z)\n") + chalk2.white(" \u2022 Numbers (0-9)\n") + chalk2.white(" \u2022 Hyphens (-)\n") + chalk2.white(" \u2022 Underscores (_)\n") + chalk2.white(" \u2022 Dots (.)\n\n") + chalk2.cyan("Valid examples:\n") + chalk2.gray(" \u2022 my-dotcms-app\n") + chalk2.gray(" \u2022 my_project\n") + chalk2.gray(" \u2022 MyProject.v2\n") + chalk2.gray(" \u2022 project-123\n\n") + chalk2.yellow("Invalid examples:\n") + chalk2.gray(" \u2022 test@#$%project (special characters)\n") + chalk2.gray(" \u2022 my project (spaces)\n") + chalk2.gray(" \u2022 project! (exclamation marks)")
391
+ );
392
+ }
393
+ const reservedNames = [
394
+ "CON",
395
+ "PRN",
396
+ "AUX",
397
+ "NUL",
398
+ "COM1",
399
+ "COM2",
400
+ "COM3",
401
+ "COM4",
402
+ "COM5",
403
+ "COM6",
404
+ "COM7",
405
+ "COM8",
406
+ "COM9",
407
+ "LPT1",
408
+ "LPT2",
409
+ "LPT3",
410
+ "LPT4",
411
+ "LPT5",
412
+ "LPT6",
413
+ "LPT7",
414
+ "LPT8",
415
+ "LPT9"
416
+ ];
417
+ if (reservedNames.includes(trimmed.toUpperCase())) {
418
+ throw new Error(
419
+ chalk2.red(`\u274C Invalid project name: "${projectName}"`) + "\n\n" + chalk2.white("This is a reserved system name on Windows\n\n") + chalk2.gray("Please choose a different name")
420
+ );
421
+ }
422
+ if (trimmed.length > MAX_PROJECT_NAME_LENGTH) {
423
+ throw new Error(
424
+ chalk2.red("\u274C Project name too long") + "\n\n" + chalk2.white(
425
+ `Maximum: ${MAX_PROJECT_NAME_LENGTH} characters (you provided: ${trimmed.length})
426
+
427
+ `
428
+ ) + chalk2.gray("Please use a shorter name")
429
+ );
430
+ }
431
+ return projectName;
432
+ }
433
+ function escapeShellPath(filePath) {
434
+ if (!filePath)
435
+ return '""';
436
+ if (filePath.startsWith('"') && filePath.endsWith('"') || filePath.startsWith("'") && filePath.endsWith("'")) {
437
+ return filePath;
438
+ }
439
+ const needsEscaping = process.platform === "win32" ? /[\s'"`$!&*(){};<>?|\n\r\t[\]]/.test(filePath) : /[\s'"`$!&*(){};<>?|\\\n\r\t[\]]/.test(filePath);
440
+ if (needsEscaping) {
441
+ const escaped = process.platform === "win32" ? filePath.replace(/"/g, '\\"') : filePath.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
442
+ return `"${escaped}"`;
443
+ }
444
+ return filePath;
445
+ }
446
+ function validateConflictingParameters(options) {
447
+ const isLocalSet = options.local === true || Boolean(options.starter);
448
+ if (!isLocalSet)
449
+ return;
450
+ const localModeSource = options.local ? "--local" : "--starter";
451
+ const cloudParams = [];
452
+ if (options.url)
453
+ cloudParams.push("--url");
454
+ if (options.username)
455
+ cloudParams.push("--username");
456
+ if (options.password)
457
+ cloudParams.push("--password");
458
+ if (cloudParams.length > 0) {
459
+ console.warn(
460
+ chalk2.yellow("\n\u26A0\uFE0F Warning: Conflicting parameters detected\n") + chalk2.white("You enabled local mode using ") + chalk2.cyan(localModeSource) + chalk2.white(" along with cloud parameters: ") + chalk2.cyan(cloudParams.join(", ")) + "\n\n" + chalk2.gray("Local mode selection will be used (Docker deployment)") + "\n" + chalk2.gray("Cloud parameters will be ignored\n")
461
+ );
462
+ }
463
+ }
464
+
465
+ // libs/sdk/create-app/src/asks.ts
466
+ async function askFramework() {
467
+ const ans = await inquirer.prompt([
468
+ {
469
+ type: "select",
470
+ name: "frameworks",
471
+ message: "Select your frontend framework:",
472
+ choices: FRAMEWORKS_CHOICES
473
+ }
474
+ ]);
475
+ return ans.frameworks;
476
+ }
477
+ async function askProjectName() {
478
+ const ans = await inquirer.prompt([
479
+ {
480
+ type: "input",
481
+ name: "projectName",
482
+ message: "What is your project name ?",
483
+ default: `my-dotcms-app`,
484
+ validate: (input) => {
485
+ try {
486
+ validateProjectName(input);
487
+ return true;
488
+ } catch (error) {
489
+ return error instanceof Error ? error.message : String(error);
490
+ }
491
+ }
492
+ }
493
+ ]);
494
+ return ans.projectName;
495
+ }
496
+ async function askDirectory() {
497
+ const ans = await inquirer.prompt([
498
+ {
499
+ type: "input",
500
+ name: "directory",
501
+ message: "Where should we create your project?",
502
+ default: `.`
503
+ }
504
+ ]);
505
+ return ans.directory;
506
+ }
507
+ async function askDotcmsCloudUrl() {
508
+ const ans = await inquirer.prompt([
509
+ {
510
+ type: "input",
511
+ name: "url",
512
+ message: "dotCMS instance URL:",
513
+ default: `https://demo.dotcms.com`,
514
+ validate: (input) => {
515
+ try {
516
+ validateUrl(input);
517
+ return true;
518
+ } catch (error) {
519
+ return error instanceof Error ? error.message : String(error);
520
+ }
521
+ }
522
+ }
523
+ ]);
524
+ return ans.url;
525
+ }
526
+ async function askUserNameForDotcmsCloud() {
527
+ const ans = await inquirer.prompt([
528
+ {
529
+ type: "input",
530
+ name: "username",
531
+ message: "Username:",
532
+ default: `admin@dotcms.com`,
533
+ validate: (input) => {
534
+ if (!input || input.trim() === "") {
535
+ return "Username cannot be empty";
536
+ }
537
+ return true;
538
+ }
539
+ }
540
+ ]);
541
+ return ans.username;
542
+ }
543
+ async function askPasswordForDotcmsCloud() {
544
+ const ans = await inquirer.prompt([
545
+ {
546
+ type: "password",
547
+ name: "password",
548
+ mask: "\u2022",
549
+ message: "Password:",
550
+ default: `admin`,
551
+ validate: (input) => {
552
+ if (!input || input.trim() === "") {
553
+ return "Password cannot be empty";
554
+ }
555
+ return true;
556
+ }
557
+ }
558
+ ]);
559
+ return ans.password;
560
+ }
561
+ async function askCloudOrLocalInstance() {
562
+ const ans = await inquirer.prompt([
563
+ {
564
+ type: "select",
565
+ name: "isCloud",
566
+ message: "Do you have an existing dotCMS instance?",
567
+ choices: [
568
+ { name: "Yes - I have a dotCMS instance URL", value: true },
569
+ { name: "No - Spin up dotCMS locally with Docker", value: false }
570
+ ]
571
+ }
572
+ ]);
573
+ return ans.isCloud;
574
+ }
575
+ async function prepareDirectory(basePath, projectName) {
576
+ const resolvedBasePath = path2.resolve(basePath);
577
+ const basePathDirName = path2.basename(resolvedBasePath);
578
+ let targetPath;
579
+ if (basePathDirName === projectName) {
580
+ targetPath = resolvedBasePath;
581
+ } else {
582
+ targetPath = path2.resolve(resolvedBasePath, projectName);
583
+ }
584
+ if (!fs.existsSync(targetPath)) {
585
+ fs.mkdirSync(targetPath, { recursive: true });
586
+ return targetPath;
587
+ }
588
+ const files = fs.readdirSync(targetPath);
589
+ if (files.length === 0) {
590
+ return targetPath;
591
+ }
592
+ const ans = await inquirer.prompt([
593
+ {
594
+ type: "confirm",
595
+ name: "confirm",
596
+ message: `\u26A0\uFE0F Directory "${targetPath}" is not empty. All files inside will be deleted. Continue?`,
597
+ default: false
598
+ }
599
+ ]);
600
+ if (!ans.confirm) {
601
+ console.log("\u274C Operation cancelled.");
602
+ process.exit(1);
603
+ }
604
+ await fs.emptyDir(targetPath);
605
+ return targetPath;
606
+ }
607
+
608
+ // libs/sdk/create-app/src/git/index.ts
609
+ import { execa as execa2 } from "execa";
610
+ import fs3 from "fs-extra";
611
+ import path4 from "path";
612
+
613
+ // libs/sdk/create-app/src/utils/index.ts
614
+ import axios2 from "axios";
615
+ import chalk3 from "chalk";
616
+ import { execa } from "execa";
617
+ import fs2 from "fs-extra";
618
+ import https from "https";
619
+ import net from "net";
620
+ import path3 from "path";
621
+ async function fetchWithRetry(url, retries = 5, delay = 5e3, requestTimeout = 1e4) {
622
+ const errors = [];
623
+ let lastError;
624
+ for (let i = 0; i < retries; i++) {
625
+ try {
626
+ return await axios2.get(url, {
627
+ timeout: requestTimeout,
628
+ // Accept any 2xx status code as success (health endpoints may return 200, 201, 204, etc.)
629
+ validateStatus: (status) => status >= 200 && status < 300
630
+ });
631
+ } catch (err) {
632
+ lastError = err;
633
+ let errorMsg = "";
634
+ if (axios2.isAxiosError(err)) {
635
+ if (err.code === "ECONNREFUSED") {
636
+ errorMsg = "Connection refused - service not accepting connections";
637
+ } else if (err.code === "ETIMEDOUT" || err.code === "ECONNABORTED") {
638
+ errorMsg = "Connection timeout - service too slow or not responding";
639
+ } else if (err.response) {
640
+ errorMsg = `HTTP ${err.response.status}: ${err.response.statusText}`;
641
+ } else {
642
+ errorMsg = err.code || err.message;
643
+ }
644
+ } else {
645
+ errorMsg = String(err);
646
+ }
647
+ errors.push(`Attempt ${i + 1}: ${errorMsg}`);
648
+ if (i === retries - 1) {
649
+ const errorType = axios2.isAxiosError(lastError) && lastError.code === "ECONNREFUSED" ? "Connection Refused" : axios2.isAxiosError(lastError) && (lastError.code === "ETIMEDOUT" || lastError.code === "ECONNABORTED") ? "Timeout" : "Connection Failed";
650
+ throw new Error(
651
+ chalk3.red(
652
+ `
653
+ \u274C Failed to connect to dotCMS after ${retries} attempts (${errorType})
654
+
655
+ `
656
+ ) + chalk3.white(`URL: ${url}
657
+ `) + chalk3.gray(`Request timeout: ${requestTimeout}ms per attempt
658
+ `) + chalk3.gray(
659
+ `Total retry window: ~${retries * (delay + requestTimeout) / 1e3}s
660
+
661
+ `
662
+ ) + chalk3.yellow("Common causes:\n") + chalk3.white(" \u2022 dotCMS is still starting up (may need more time)\n") + chalk3.white(" \u2022 Container crashed or failed to start\n") + chalk3.white(" \u2022 Port conflict (8082 already in use)\n") + chalk3.white(" \u2022 Network/firewall blocking connection\n") + chalk3.white(
663
+ ` \u2022 Request timeout too short (current: ${requestTimeout}ms)
664
+
665
+ `
666
+ ) + chalk3.gray(
667
+ "Detailed error history:\n" + errors.map((e) => ` \u2022 ${e}`).join("\n")
668
+ )
669
+ );
670
+ }
671
+ console.log(
672
+ chalk3.yellow(`\u23F3 dotCMS not ready (attempt ${i + 1}/${retries})`) + chalk3.gray(` - ${errorMsg}`) + chalk3.gray(` - Retrying in ${delay / 1e3}s...`)
673
+ );
674
+ await new Promise((r) => setTimeout(r, delay));
675
+ }
676
+ }
677
+ }
678
+ function getUVEConfigValue(frontEndUrl) {
679
+ return JSON.stringify({
680
+ config: [
681
+ {
682
+ pattern: ".*",
683
+ url: frontEndUrl
684
+ }
685
+ ]
686
+ });
687
+ }
688
+ function getPortByFramework(framework) {
689
+ switch (framework) {
690
+ case "angular":
691
+ return "4200";
692
+ case "angular-ssr":
693
+ return "4200";
694
+ case "nextjs":
695
+ return "3000";
696
+ case "astro":
697
+ return "4321";
698
+ default:
699
+ throw new Error(`Unsupported framework: ${framework}`);
700
+ }
701
+ }
702
+ function getDotcmsApisByBaseUrl(baseUrl) {
703
+ return {
704
+ // Note: Using /appconfiguration instead of /probes/alive because the probe endpoints
705
+ // have IP ACL restrictions that block requests from Docker host.
706
+ // See: https://github.com/dotCMS/core/issues/34509
707
+ DOTCMS_HEALTH_API: `${baseUrl}/api/v1/appconfiguration`,
708
+ DOTCMS_TOKEN_API: `${baseUrl}/api/v1/authentication/api-token`,
709
+ DOTCMS_EMA_CONFIG_API: `${baseUrl}/api/v1/apps/dotema-config-v2/`,
710
+ DOTCMS_SITE_API: `${baseUrl}/api/v1/site/`
711
+ };
712
+ }
713
+ function downloadFile(url, dest) {
714
+ return new Promise((resolve, reject) => {
715
+ const file = fs2.createWriteStream(dest);
716
+ https.get(url, (response) => {
717
+ if (response.statusCode !== 200) {
718
+ return reject(new Error(`Failed to download file: ${response.statusCode}`));
719
+ }
720
+ response.pipe(file);
721
+ file.on("finish", () => file.close(() => resolve()));
722
+ }).on("error", (err) => {
723
+ fs2.unlink(dest);
724
+ reject(err);
725
+ });
726
+ });
727
+ }
728
+ function finalStepsForNextjs({
729
+ projectPath,
730
+ urlDotCMSInstance,
731
+ siteId,
732
+ token
733
+ }) {
734
+ console.log("\n");
735
+ console.log(chalk3.white("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
736
+ console.log(chalk3.greenBright("\u{1F4CB} Next Steps:\n"));
737
+ console.log(
738
+ chalk3.white("1. Navigate to your project:\n") + chalk3.gray(` $ cd ${escapeShellPath(projectPath)}
739
+ `)
740
+ );
741
+ console.log(
742
+ chalk3.white("2. Create your environment file:\n") + chalk3.gray(" $ touch .env\n")
743
+ );
744
+ console.log(chalk3.white("3. Add your dotCMS configuration to ") + chalk3.green(".env") + ":\n");
745
+ console.log(chalk3.white("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
746
+ console.log(chalk3.white(getEnvVariablesForNextJS(urlDotCMSInstance, siteId, token)));
747
+ console.log(chalk3.white("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
748
+ console.log(chalk3.gray(" \u{1F4A1} Tip: Copy the block above and paste into your .env file\n"));
749
+ console.log(
750
+ chalk3.white("4. Start your development server:\n") + chalk3.gray(" $ npm run dev\n")
751
+ );
752
+ console.log(
753
+ chalk3.white("5. Open your browser:\n") + chalk3.gray(" \u2192 http://localhost:3000\n")
754
+ );
755
+ console.log(
756
+ chalk3.white("6. Edit your page content in dotCMS:\n") + chalk3.gray(` \u2192 ${urlDotCMSInstance}/dotAdmin/#/edit-page?url=/index
757
+ `)
758
+ );
759
+ console.log(chalk3.white("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
760
+ console.log(chalk3.blueBright("\u{1F4D6} Documentation: ") + chalk3.white("https://dev.dotcms.com"));
761
+ console.log(chalk3.blueBright("\u{1F4AC} Community: ") + chalk3.white("https://community.dotcms.com\n"));
762
+ }
763
+ function finalStepsForAstro({
764
+ projectPath,
765
+ urlDotCMSInstance,
766
+ siteId,
767
+ token
768
+ }) {
769
+ console.log("\n");
770
+ console.log(chalk3.white("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
771
+ console.log(chalk3.greenBright("\u{1F4CB} Next Steps:\n"));
772
+ console.log(
773
+ chalk3.white("1. Navigate to your project:\n") + chalk3.gray(` $ cd ${escapeShellPath(projectPath)}
774
+ `)
775
+ );
776
+ console.log(
777
+ chalk3.white("2. Create your environment file:\n") + chalk3.gray(" $ touch .env\n")
778
+ );
779
+ console.log(chalk3.white("3. Add your dotCMS configuration to ") + chalk3.green(".env") + ":\n");
780
+ console.log(chalk3.white("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
781
+ console.log(chalk3.white(getEnvVariablesForAstro(urlDotCMSInstance, siteId, token)));
782
+ console.log(chalk3.white("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
783
+ console.log(chalk3.gray(" \u{1F4A1} Tip: Copy the block above and paste into your .env file\n"));
784
+ console.log(
785
+ chalk3.white("4. Start your development server:\n") + chalk3.gray(" $ npm run dev\n")
786
+ );
787
+ console.log(
788
+ chalk3.white("5. Open your browser:\n") + chalk3.gray(" \u2192 http://localhost:3000\n")
789
+ );
790
+ console.log(
791
+ chalk3.white("6. Edit your page content in dotCMS:\n") + chalk3.gray(` \u2192 ${urlDotCMSInstance}/dotAdmin/#/edit-page?url=/index
792
+ `)
793
+ );
794
+ console.log(chalk3.white("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
795
+ console.log(chalk3.blueBright("\u{1F4D6} Documentation: ") + chalk3.white("https://dev.dotcms.com"));
796
+ console.log(chalk3.blueBright("\u{1F4AC} Community: ") + chalk3.white("https://community.dotcms.com\n"));
797
+ }
798
+ function finalStepsForAngularAndAngularSSR({
799
+ projectPath,
800
+ urlDotCMSInstance,
801
+ siteId,
802
+ token
803
+ }) {
804
+ console.log("\n");
805
+ console.log(chalk3.white("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
806
+ console.log(chalk3.greenBright("\u{1F4CB} Next Steps:\n"));
807
+ console.log(
808
+ chalk3.white("1. Navigate to your environments directory:\n") + chalk3.gray(` $ cd ${escapeShellPath(projectPath)}/src/environments
809
+ `)
810
+ );
811
+ console.log(
812
+ chalk3.white("2. Update the environment files:\n") + chalk3.gray(
813
+ " Replace the contents of the following files:\n \u2022 environment.ts\n \u2022 environment.development.ts\n\n"
814
+ )
815
+ );
816
+ console.log(chalk3.white("3. Add your dotCMS configuration:\n"));
817
+ console.log(chalk3.white("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
818
+ console.log(chalk3.white(getEnvVariablesForAngular(urlDotCMSInstance, siteId, token)));
819
+ console.log(chalk3.white("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
820
+ console.log(
821
+ chalk3.gray(" \u{1F4A1} Tip: Copy the block above and paste it into both environment files\n")
822
+ );
823
+ console.log(chalk3.white("4. Start your development server:\n") + chalk3.gray(" $ ng serve\n"));
824
+ console.log(
825
+ chalk3.white("5. Open your browser:\n") + chalk3.gray(" \u2192 http://localhost:4200\n")
826
+ );
827
+ console.log(
828
+ chalk3.white("6. Edit your page content in dotCMS:\n") + chalk3.gray(` \u2192 ${urlDotCMSInstance}/dotAdmin/#/edit-page?url=/index
829
+ `)
830
+ );
831
+ console.log(chalk3.white("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
832
+ console.log(chalk3.blueBright("\u{1F4D6} Documentation: ") + chalk3.white("https://dev.dotcms.com"));
833
+ console.log(chalk3.blueBright("\u{1F4AC} Community: ") + chalk3.white("https://community.dotcms.com\n"));
834
+ }
835
+ function getEnvVariablesForNextJS(host, siteId, token) {
836
+ return `
837
+ NEXT_PUBLIC_DOTCMS_AUTH_TOKEN=${token}
838
+ NEXT_PUBLIC_DOTCMS_HOST=${host}
839
+ NEXT_PUBLIC_DOTCMS_SITE_ID=${siteId}
840
+ NEXT_PUBLIC_DOTCMS_MODE='production'
841
+ `;
842
+ }
843
+ function getEnvVariablesForAstro(host, siteId, token) {
844
+ return `
845
+ PUBLIC_DOTCMS_AUTH_TOKEN=${token}
846
+ PUBLIC_DOTCMS_HOST=${host}
847
+ PUBLIC_DOTCMS_SITE_ID=${siteId}
848
+ PUBLIC_EXPERIMENTS_API_KEY=analytic-api-key-from-dotcms-portlet
849
+ PUBLIC_EXPERIMENTS_DEBUG=true
850
+ `;
851
+ }
852
+ function getEnvVariablesForAngular(host, siteId, token) {
853
+ return `
854
+ export const environment = {
855
+ dotcmsUrl: '${host}',
856
+ authToken: '${token}',
857
+ siteId: '${siteId}',
858
+ };
859
+ `;
860
+ }
861
+ async function installDependenciesForProject(projectPath) {
862
+ try {
863
+ await execa("npm", ["install"], {
864
+ cwd: projectPath
865
+ // stdio: 'inherit', // optional: shows npm output in terminal
866
+ });
867
+ return Ok(true);
868
+ } catch {
869
+ return Err("Failed to install dependencies. Please make sure npm is installed");
870
+ }
871
+ }
872
+ function displayDependencies(selectedFrameWork) {
873
+ switch (selectedFrameWork) {
874
+ case "nextjs":
875
+ return formatDependencies(NEXTJS_DEPENDENCIES, NEXTJS_DEPENDENCIES_DEV);
876
+ case "astro":
877
+ return formatDependencies(ASTRO_DEPENDENCIES, ASTRO_DEPENDENCIES_DEV);
878
+ case "angular":
879
+ return formatDependencies(ANGULAR_DEPENDENCIES, ANGULAR_DEPENDENCIES_DEV);
880
+ case "angular-ssr":
881
+ return formatDependencies(ANGULAR_SSR_DEPENDENCIES, ANGULAR_SSR_DEPENDENCIES_DEV);
882
+ default:
883
+ return "";
884
+ }
885
+ }
886
+ function formatDependencies(dependencies, devDependencies) {
887
+ const lines = [];
888
+ lines.push(chalk3.white("Dependencies:"));
889
+ dependencies.forEach((item) => lines.push(chalk3.grey(`- ${item}`)));
890
+ lines.push("");
891
+ lines.push(chalk3.white("Dev Dependencies:"));
892
+ devDependencies.forEach((item) => lines.push(chalk3.grey(`- ${item}`)));
893
+ return lines.join("\n");
894
+ }
895
+ async function checkDockerAvailability() {
896
+ try {
897
+ await execa("docker", ["info"]);
898
+ return Ok(true);
899
+ } catch {
900
+ const errorMsg = chalk3.red("\n\u274C Docker is not available\n\n") + chalk3.white("Docker is required to run dotCMS locally.\n\n") + chalk3.yellow("How to fix:\n") + chalk3.white(" 1. Install Docker Desktop:\n") + chalk3.cyan(" \u2192 https://www.docker.com/products/docker-desktop\n\n") + chalk3.white(" 2. Start Docker Desktop\n") + chalk3.white(
901
+ " 3. Wait for Docker to be running (check the Docker icon in your system tray)\n"
902
+ ) + chalk3.white(" 4. Run this command again\n\n") + chalk3.gray("Alternative: Use --url flag to connect to an existing dotCMS instance");
903
+ return Err(errorMsg);
904
+ }
905
+ }
906
+ function isPortAvailable(port) {
907
+ return new Promise((resolve) => {
908
+ const server = net.createServer();
909
+ server.once("error", (err) => {
910
+ if (err.code === "EADDRINUSE") {
911
+ resolve(false);
912
+ } else {
913
+ console.warn(
914
+ chalk3.yellow(
915
+ `Warning: Unexpected error while checking port ${port}: ${err.message}`
916
+ )
917
+ );
918
+ resolve(false);
919
+ }
920
+ });
921
+ server.once("listening", () => {
922
+ server.close();
923
+ resolve(true);
924
+ });
925
+ server.listen(port, "0.0.0.0");
926
+ });
927
+ }
928
+ async function checkPortsAvailability() {
929
+ const requiredPorts = [
930
+ { port: 8082, service: "dotCMS HTTP" },
931
+ { port: 8443, service: "dotCMS HTTPS" },
932
+ { port: 9200, service: "Elasticsearch HTTP" },
933
+ { port: 9600, service: "Elasticsearch Transport" }
934
+ ];
935
+ const busyPorts = [];
936
+ for (const { port, service } of requiredPorts) {
937
+ const available = await isPortAvailable(port);
938
+ if (!available) {
939
+ busyPorts.push({ port, service });
940
+ }
941
+ }
942
+ if (busyPorts.length > 0) {
943
+ const errorMsg = chalk3.red("\n\u274C Required ports are already in use\n\n") + chalk3.white("The following ports are busy:\n") + busyPorts.map(
944
+ ({ port, service }) => chalk3.yellow(` \u2022 Port ${port}`) + chalk3.gray(` (${service})`)
945
+ ).join("\n") + "\n\n" + chalk3.yellow("How to fix:\n") + chalk3.white(" 1. Stop services using these ports:\n") + chalk3.gray(" \u2022 Check what's using the ports: ") + chalk3.cyan(
946
+ process.platform === "win32" ? `netstat -ano | findstr ":<port>"` : `lsof -i :<port>`
947
+ ) + "\n" + chalk3.gray(" \u2022 Stop the conflicting service\n\n") + chalk3.white(" 2. Or stop existing dotCMS containers:\n") + chalk3.cyan(" $ docker compose down\n\n") + chalk3.white(" 3. Run this command again\n\n") + chalk3.gray("Alternative: Use --url flag to connect to an existing dotCMS instance");
948
+ return Err(errorMsg);
949
+ }
950
+ return Ok(true);
951
+ }
952
+ async function getDockerDiagnostics(directory) {
953
+ const diagnostics = [];
954
+ const dockerAvailable = await checkDockerAvailability();
955
+ if (!dockerAvailable.ok) {
956
+ return dockerAvailable.val;
957
+ }
958
+ try {
959
+ const { stdout: psOutput } = await execa(
960
+ "docker",
961
+ ["ps", "-a", "--format", "{{.Names}} {{.Status}} {{.Ports}}"],
962
+ { cwd: directory }
963
+ );
964
+ if (!psOutput.trim()) {
965
+ diagnostics.push(chalk3.yellow("\n\u26A0\uFE0F No Docker containers found"));
966
+ diagnostics.push(
967
+ chalk3.white("The docker-compose.yml may not have been started correctly\n")
968
+ );
969
+ return diagnostics.join("\n");
970
+ }
971
+ diagnostics.push(chalk3.cyan("\n\u{1F4CB} Container Status:"));
972
+ const containers = psOutput.trim().split("\n");
973
+ for (const container of containers) {
974
+ const [name, status, ports] = container.split(" ");
975
+ const isHealthy = status.includes("Up") && !status.includes("unhealthy");
976
+ const icon = isHealthy ? "\u2705" : "\u274C";
977
+ diagnostics.push(` ${icon} ${chalk3.white(name)}: ${chalk3.gray(status)}`);
978
+ if (ports) {
979
+ diagnostics.push(` ${chalk3.gray("Ports:")} ${chalk3.white(ports)}`);
980
+ }
981
+ }
982
+ const unhealthyContainers = containers.filter(
983
+ (c) => !c.includes("Up") || c.includes("unhealthy") || c.includes("Exited")
984
+ );
985
+ if (unhealthyContainers.length > 0) {
986
+ diagnostics.push(chalk3.yellow("\n\u{1F50D} Recent logs from problematic containers:\n"));
987
+ for (const container of unhealthyContainers) {
988
+ const name = container.split(" ")[0];
989
+ try {
990
+ const { stdout: logs } = await execa("docker", ["logs", "--tail", "20", name], {
991
+ cwd: directory,
992
+ reject: false
993
+ });
994
+ diagnostics.push(chalk3.white(`
995
+ --- ${name} ---`));
996
+ diagnostics.push(chalk3.gray(logs.split("\n").slice(-10).join("\n")));
997
+ } catch {
998
+ diagnostics.push(chalk3.gray(` Unable to fetch logs for ${name}`));
999
+ }
1000
+ }
1001
+ }
1002
+ } catch (error) {
1003
+ diagnostics.push(chalk3.red("\n\u274C Failed to get Docker diagnostics"));
1004
+ diagnostics.push(chalk3.gray(String(error)));
1005
+ }
1006
+ diagnostics.push(chalk3.yellow("\n\u{1F4A1} Troubleshooting steps:"));
1007
+ diagnostics.push(chalk3.white(" 1. Check if all containers are running:"));
1008
+ diagnostics.push(chalk3.gray(" docker ps"));
1009
+ diagnostics.push(chalk3.white(" 2. View logs for a specific container:"));
1010
+ diagnostics.push(chalk3.gray(" docker logs <container-name>"));
1011
+ diagnostics.push(chalk3.white(" 3. Restart the containers:"));
1012
+ diagnostics.push(chalk3.gray(" docker compose down && docker compose up -d"));
1013
+ diagnostics.push(chalk3.white(" 4. Check if ports 8082, 8443, 9200, and 9600 are available\n"));
1014
+ return diagnostics.join("\n");
1015
+ }
1016
+ function getDisplayPath(targetPath, cwd) {
1017
+ const relativePath = path3.relative(cwd, targetPath);
1018
+ const parentDirCount = (relativePath.match(/\.\.\//g) || []).length;
1019
+ if (parentDirCount >= 3) {
1020
+ return targetPath;
1021
+ }
1022
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
1023
+ }
1024
+
1025
+ // libs/sdk/create-app/src/git/index.ts
1026
+ var cloneFrontEndSample = async ({
1027
+ framework,
1028
+ directory
1029
+ }) => {
1030
+ await execa2(
1031
+ "git",
1032
+ ["clone", "--filter=blob:none", "--sparse", "https://github.com/dotCMS/core.git", "."],
1033
+ { cwd: directory }
1034
+ );
1035
+ await execa2("git", ["sparse-checkout", "set", `examples/${framework}`], {
1036
+ cwd: directory
1037
+ });
1038
+ await execa2("git", ["checkout", "main"], {
1039
+ cwd: directory
1040
+ // stdio: 'inherit'
1041
+ });
1042
+ const src = path4.join(directory, "examples", `${framework}`);
1043
+ const dest = directory;
1044
+ const items = await fs3.readdir(directory);
1045
+ for (const item of items) {
1046
+ if (item !== "examples") {
1047
+ await fs3.remove(path4.join(directory, item));
1048
+ }
1049
+ }
1050
+ await fs3.copy(src, dest, { overwrite: true });
1051
+ const allItems = await fs3.readdir(directory);
1052
+ for (const item of allItems) {
1053
+ if (item === "examples") {
1054
+ await fs3.remove(path4.join(directory, item));
1055
+ }
1056
+ }
1057
+ };
1058
+ async function downloadDockerCompose(directory) {
1059
+ const dockerUrl = "https://raw.githubusercontent.com/dotCMS/core/main/docker/docker-compose-examples/single-node-demo-site/docker-compose.yml";
1060
+ const dockerComposePath = path4.join(directory, "docker-compose.yml");
1061
+ await downloadFile(dockerUrl, dockerComposePath);
1062
+ }
1063
+ async function moveDockerComposeOneLevelUp(directory) {
1064
+ const sourcePath = path4.join(directory, "docker-compose.yml");
1065
+ const targetPath = path4.join(directory, "..", "docker-compose.yml");
1066
+ await fs3.rename(sourcePath, targetPath);
1067
+ }
1068
+ async function moveDockerComposeBack(directory) {
1069
+ const sourcePath = path4.join(directory, "..", "docker-compose.yml");
1070
+ const targetPath = path4.join(directory, "docker-compose.yml");
1071
+ await fs3.rename(sourcePath, targetPath);
1072
+ }
1073
+
1074
+ // libs/sdk/create-app/src/index.ts
1075
+ var program = new Command();
1076
+ program.name("create-dotcms-app").description("dotCMS CLI for creating applications").version("0.1.0-beta");
1077
+ program.argument("[projectName]", "Name of the project").option("-f, --framework <framework>", "Framework to use [nextjs,astro,angular,angular-ssr]").option("-d, --directory <path>", "Project directory").option("--local", "Use local dotCMS instance using docker").option("--url <url>", "DotCMS instance url (skip in case of local)").option("-u, --username <username>", "DotCMS instance username (skip in case of local)").option("-p, --password <password>", "DotCMS instance password (skip in case of local)").option(
1078
+ "--starter <url>",
1079
+ "Custom starter URL for the local dotCMS Docker instance (sets CUSTOM_STARTER_URL)"
1080
+ ).action(async (projectName, options) => {
1081
+ printWelcomeScreen();
1082
+ try {
1083
+ const validatedFramework = validateAndNormalizeFramework(options.framework);
1084
+ validateUrl(options.starter);
1085
+ validateUrl(options.url);
1086
+ validateConflictingParameters(options);
1087
+ validateProjectName(projectName);
1088
+ const starterOnlyMode = Boolean(options.starter);
1089
+ const isLocalModeRequested = options.local === true || starterOnlyMode;
1090
+ const isCloudExplicit = Boolean(options.url) && !isLocalModeRequested;
1091
+ const projectNameFinal = projectName ?? await askProjectName();
1092
+ const directoryInput = options.directory ?? await askDirectory();
1093
+ const finalDirectory = await prepareDirectory(directoryInput, projectNameFinal);
1094
+ const isCloudInstanceSelected = isLocalModeRequested ? false : isCloudExplicit || await askCloudOrLocalInstance();
1095
+ if (isCloudInstanceSelected) {
1096
+ const urlInput = options.url ?? await askDotcmsCloudUrl();
1097
+ validateUrl(urlInput);
1098
+ const urlDotcmsInstance = normalizeUrl(urlInput);
1099
+ const healthApiURL = getDotcmsApisByBaseUrl(urlDotcmsInstance).DOTCMS_HEALTH_API;
1100
+ const emaConfigApiURL = getDotcmsApisByBaseUrl(urlDotcmsInstance).DOTCMS_EMA_CONFIG_API;
1101
+ const siteApiURL = getDotcmsApisByBaseUrl(urlDotcmsInstance).DOTCMS_SITE_API;
1102
+ const tokenApiUrl = getDotcmsApisByBaseUrl(urlDotcmsInstance).DOTCMS_TOKEN_API;
1103
+ const spinner2 = ora(`\u23F3 Connecting to dotCMS...`).start();
1104
+ const healthCheckResult2 = await isDotcmsRunning(
1105
+ healthApiURL,
1106
+ CLOUD_HEALTH_CHECK_RETRIES
1107
+ );
1108
+ if (!healthCheckResult2.ok) {
1109
+ spinner2.fail(
1110
+ "dotCMS is not running on the following url " + urlDotcmsInstance + ". Please check the url and try again."
1111
+ );
1112
+ console.error(healthCheckResult2.val);
1113
+ process.exit(1);
1114
+ }
1115
+ spinner2.succeed("Connected to dotCMS successfully");
1116
+ let dotcmsToken2;
1117
+ let authAttempts = 0;
1118
+ const MAX_AUTH_ATTEMPTS = 3;
1119
+ while (authAttempts < MAX_AUTH_ATTEMPTS) {
1120
+ authAttempts++;
1121
+ const userNameDotCmsInstance = options.username ?? await askUserNameForDotcmsCloud();
1122
+ const passwordDotCmsInstance = options.password ?? await askPasswordForDotcmsCloud();
1123
+ dotcmsToken2 = await DotCMSApi.getAuthToken({
1124
+ payload: {
1125
+ user: userNameDotCmsInstance,
1126
+ password: passwordDotCmsInstance,
1127
+ expirationDays: "30",
1128
+ label: "token for frontend app"
1129
+ },
1130
+ url: tokenApiUrl
1131
+ });
1132
+ if (dotcmsToken2.ok) {
1133
+ spinner2.succeed("Generated API authentication token");
1134
+ break;
1135
+ } else {
1136
+ spinner2.fail("Authentication failed");
1137
+ console.error(dotcmsToken2.val);
1138
+ if (authAttempts < MAX_AUTH_ATTEMPTS) {
1139
+ console.log(
1140
+ chalk4.yellow(
1141
+ `
1142
+ Attempt ${authAttempts}/${MAX_AUTH_ATTEMPTS} - Please try again
1143
+ `
1144
+ )
1145
+ );
1146
+ } else {
1147
+ console.log(
1148
+ chalk4.red(
1149
+ `
1150
+ Maximum authentication attempts (${MAX_AUTH_ATTEMPTS}) reached. Exiting.
1151
+ `
1152
+ )
1153
+ );
1154
+ process.exit(1);
1155
+ }
1156
+ }
1157
+ }
1158
+ if (!dotcmsToken2 || !dotcmsToken2.ok) {
1159
+ process.exit(1);
1160
+ }
1161
+ const defaultSite2 = await DotCMSApi.getDefaultSite({
1162
+ authenticationToken: dotcmsToken2.val,
1163
+ url: siteApiURL
1164
+ });
1165
+ if (!defaultSite2.ok) {
1166
+ spinner2.fail("Failed to get default site identifier from Dotcms.");
1167
+ process.exit(1);
1168
+ } else {
1169
+ spinner2.succeed(
1170
+ `Retrieved default site (${defaultSite2.val.entity.identifier})`
1171
+ );
1172
+ }
1173
+ const selectedFramework2 = validatedFramework ?? await askFramework();
1174
+ const setUpUVE2 = await DotCMSApi.setupUVEConfig({
1175
+ payload: {
1176
+ configuration: {
1177
+ hidden: false,
1178
+ value: getUVEConfigValue(
1179
+ `http://localhost:${getPortByFramework(selectedFramework2)}`
1180
+ )
1181
+ }
1182
+ },
1183
+ siteId: defaultSite2.val.entity.identifier,
1184
+ authenticationToken: dotcmsToken2.val,
1185
+ url: emaConfigApiURL
1186
+ });
1187
+ if (!setUpUVE2.ok) {
1188
+ spinner2.fail("Failed to setup UVE configuration in Dotcms.");
1189
+ process.exit(1);
1190
+ } else {
1191
+ spinner2.succeed(`Configured the Universal Visual Editor`);
1192
+ }
1193
+ await startScaffoldingFrontEnd({ spinner: spinner2, selectedFramework: selectedFramework2, finalDirectory });
1194
+ console.log(chalk4.white(`\u2705 Project setup complete!`));
1195
+ const relativePath2 = getDisplayPath(finalDirectory, process.cwd());
1196
+ displayFinalSteps({
1197
+ host: urlDotcmsInstance,
1198
+ relativePath: relativePath2,
1199
+ token: dotcmsToken2.val,
1200
+ siteId: defaultSite2.val.entity.identifier,
1201
+ selectedFramework: selectedFramework2
1202
+ });
1203
+ return;
1204
+ }
1205
+ const spinner = ora(`Checking Docker availability...`).start();
1206
+ const dockerAvailable = await checkDockerAvailability();
1207
+ if (!dockerAvailable.ok) {
1208
+ spinner.fail("Docker is not available");
1209
+ console.error(dockerAvailable.val);
1210
+ process.exit(1);
1211
+ }
1212
+ spinner.succeed("Docker is available");
1213
+ spinner.start("Checking port availability...");
1214
+ const portsAvailable = await checkPortsAvailability();
1215
+ if (!portsAvailable.ok) {
1216
+ spinner.fail("Required ports are busy");
1217
+ console.error(portsAvailable.val);
1218
+ process.exit(1);
1219
+ }
1220
+ spinner.succeed("All required ports are available");
1221
+ spinner.start("Downloading Docker Compose configuration...");
1222
+ const downloaded = await downloadTheDockerCompose({
1223
+ directory: finalDirectory
1224
+ });
1225
+ if (!downloaded.ok) {
1226
+ spinner.fail("Failed to download Docker Compose file.");
1227
+ process.exit(1);
1228
+ }
1229
+ spinner.succeed("Docker Compose configuration downloaded");
1230
+ spinner.start("Starting dotCMS containers...");
1231
+ const ran = await runDockerCompose({
1232
+ directory: finalDirectory,
1233
+ starterUrl: options.starter
1234
+ });
1235
+ if (!ran.ok) {
1236
+ spinner.fail("Failed to start Docker containers");
1237
+ const errorMessage = ran.val instanceof Error ? ran.val.message : String(ran.val);
1238
+ console.error(
1239
+ chalk4.red("\n\u274C Docker Compose failed to start\n\n") + chalk4.white("Error details:\n") + chalk4.gray(errorMessage) + "\n\n" + chalk4.yellow("Common solutions:\n") + chalk4.white(" \u2022 Ensure Docker Desktop is running\n") + chalk4.white(" \u2022 Try: ") + chalk4.cyan("docker compose down") + chalk4.white(" then run this command again\n") + chalk4.white(" \u2022 Check Docker logs for more details\n")
1240
+ );
1241
+ process.exit(1);
1242
+ }
1243
+ spinner.succeed("dotCMS containers started successfully.");
1244
+ spinner.start("Verifying if dotCMS is running...");
1245
+ const healthCheckResult = await isDotcmsRunning(
1246
+ DOTCMS_HEALTH_API,
1247
+ LOCAL_HEALTH_CHECK_RETRIES
1248
+ );
1249
+ if (!healthCheckResult.ok) {
1250
+ spinner.fail("dotCMS failed to start properly");
1251
+ console.error(healthCheckResult.val);
1252
+ console.error(await getDockerDiagnostics(finalDirectory));
1253
+ process.exit(1);
1254
+ }
1255
+ spinner.succeed("dotCMS is running locally at http://localhost:8082");
1256
+ spinner.succeed("Default credentials: admin@dotcms.com / admin");
1257
+ if (starterOnlyMode) {
1258
+ console.log(chalk4.white(`\u2705 Project setup complete!`));
1259
+ console.log(
1260
+ chalk4.gray(
1261
+ "Skipped frontend scaffolding and dotCMS UVE setup because --starter was provided."
1262
+ )
1263
+ );
1264
+ return;
1265
+ }
1266
+ const selectedFramework = validatedFramework ?? await askFramework();
1267
+ const dotcmsToken = await DotCMSApi.getAuthToken({
1268
+ payload: {
1269
+ user: DOTCMS_USER.username,
1270
+ password: DOTCMS_USER.password,
1271
+ expirationDays: "30",
1272
+ label: "token for frontend app"
1273
+ }
1274
+ });
1275
+ if (!dotcmsToken.ok) {
1276
+ spinner.fail("Failed to get authentication token from Dotcms.");
1277
+ process.exit(1);
1278
+ } else {
1279
+ spinner.succeed("Generated API authentication token");
1280
+ }
1281
+ const defaultSite = await DotCMSApi.getDefaultSite({
1282
+ authenticationToken: dotcmsToken.val
1283
+ });
1284
+ if (!defaultSite.ok) {
1285
+ spinner.fail("Failed to get default site identifier from Dotcms.");
1286
+ process.exit(1);
1287
+ } else {
1288
+ spinner.succeed(`Retrieved default site (${defaultSite.val.entity.identifier})`);
1289
+ }
1290
+ const setUpUVE = await DotCMSApi.setupUVEConfig({
1291
+ payload: {
1292
+ configuration: {
1293
+ hidden: false,
1294
+ value: getUVEConfigValue(
1295
+ `http://localhost:${getPortByFramework(selectedFramework)}`
1296
+ )
1297
+ }
1298
+ },
1299
+ siteId: defaultSite.val.entity.identifier,
1300
+ authenticationToken: dotcmsToken.val
1301
+ });
1302
+ if (!setUpUVE.ok) {
1303
+ spinner.fail("Failed to setup UVE configuration in Dotcms.");
1304
+ process.exit(1);
1305
+ } else {
1306
+ spinner.succeed(`Configured the Universal Visual Editor`);
1307
+ }
1308
+ moveDockerComposeOneLevelUp(finalDirectory);
1309
+ await startScaffoldingFrontEnd({ spinner, selectedFramework, finalDirectory });
1310
+ moveDockerComposeBack(finalDirectory);
1311
+ console.log(chalk4.white(`\u2705 Project setup complete!`));
1312
+ const relativePath = getDisplayPath(finalDirectory, process.cwd());
1313
+ displayFinalSteps({
1314
+ host: "http://localhost:8082",
1315
+ relativePath,
1316
+ token: dotcmsToken.val,
1317
+ siteId: defaultSite.val.entity.identifier,
1318
+ selectedFramework
1319
+ });
1320
+ } catch (error) {
1321
+ if (error instanceof Error) {
1322
+ console.error(error.message);
1323
+ if (process.env.DEBUG) {
1324
+ console.error("\n" + chalk4.gray("Stack trace:"));
1325
+ console.error(chalk4.gray(error.stack || "No stack trace available"));
1326
+ }
1327
+ } else {
1328
+ console.error(chalk4.red("\u274C An unexpected error occurred"));
1329
+ console.error(String(error));
1330
+ }
1331
+ process.exit(1);
1332
+ }
1333
+ });
1334
+ async function createApp() {
1335
+ program.parse();
1336
+ }
1337
+ async function scaffoldFrontendProject({
1338
+ framework,
1339
+ directory
1340
+ }) {
1341
+ try {
1342
+ await cloneFrontEndSample({ directory, framework });
1343
+ return Ok(void 0);
1344
+ } catch (err) {
1345
+ console.log(
1346
+ chalk4.red(
1347
+ `\u274C Failed to create ${framework} project. Please check git installation and network connection.` + JSON.stringify(err)
1348
+ )
1349
+ );
1350
+ return Err(new FailedToCreateFrontendProjectError(framework));
1351
+ }
1352
+ }
1353
+ async function downloadTheDockerCompose({
1354
+ directory
1355
+ }) {
1356
+ try {
1357
+ await downloadDockerCompose(directory);
1358
+ return Ok(void 0);
1359
+ } catch (err) {
1360
+ console.log(chalk4.red("\u274C Failed to download docker-compose.yml." + JSON.stringify(err)));
1361
+ return Err(new FailedToDownloadDockerComposeError());
1362
+ }
1363
+ }
1364
+ async function runDockerCompose({
1365
+ directory,
1366
+ starterUrl
1367
+ }) {
1368
+ try {
1369
+ if (starterUrl) {
1370
+ await updateDockerComposeStarterUrl({ directory, starterUrl });
1371
+ }
1372
+ const env = starterUrl ? { ...process.env, CUSTOM_STARTER_URL: starterUrl } : process.env;
1373
+ await execa3("docker", ["compose", "up", "-d"], { cwd: directory, env });
1374
+ await execa3("docker", ["ps"], { cwd: directory });
1375
+ return Ok(void 0);
1376
+ } catch (err) {
1377
+ return Err(err);
1378
+ }
1379
+ }
1380
+ async function updateDockerComposeStarterUrl({
1381
+ directory,
1382
+ starterUrl
1383
+ }) {
1384
+ const composePath = path5.join(directory, "docker-compose.yml");
1385
+ const composeContents = await fs4.readFile(composePath, "utf-8");
1386
+ const updatedContents = composeContents.replace(
1387
+ /^(\s*["']?CUSTOM_STARTER_URL["']?\s*:\s*).+$/m,
1388
+ `$1"${starterUrl}"`
1389
+ );
1390
+ if (updatedContents === composeContents) {
1391
+ throw new Error(
1392
+ "CUSTOM_STARTER_URL entry not found in docker-compose.yml. Unable to apply --starter value."
1393
+ );
1394
+ }
1395
+ await fs4.writeFile(composePath, updatedContents);
1396
+ }
1397
+ async function isDotcmsRunning(url, retries = 60) {
1398
+ try {
1399
+ const res = await fetchWithRetry(url ?? DOTCMS_HEALTH_API, retries, 5e3);
1400
+ if (res && res.status === 200) {
1401
+ return Ok(true);
1402
+ }
1403
+ return Err("dotCMS health check returned non-200 status");
1404
+ } catch (error) {
1405
+ const errorMessage = error instanceof Error ? error.message : String(error);
1406
+ return Err(errorMessage);
1407
+ }
1408
+ }
1409
+ function displayFinalSteps({
1410
+ selectedFramework,
1411
+ relativePath,
1412
+ token,
1413
+ siteId,
1414
+ host
1415
+ }) {
1416
+ switch (selectedFramework) {
1417
+ case "nextjs": {
1418
+ finalStepsForNextjs({
1419
+ projectPath: relativePath,
1420
+ token,
1421
+ siteId,
1422
+ urlDotCMSInstance: host
1423
+ });
1424
+ break;
1425
+ }
1426
+ case "angular": {
1427
+ finalStepsForAngularAndAngularSSR({
1428
+ projectPath: relativePath,
1429
+ token,
1430
+ siteId,
1431
+ urlDotCMSInstance: host
1432
+ });
1433
+ break;
1434
+ }
1435
+ case "angular-ssr": {
1436
+ finalStepsForAngularAndAngularSSR({
1437
+ projectPath: relativePath,
1438
+ token,
1439
+ siteId,
1440
+ urlDotCMSInstance: host
1441
+ });
1442
+ break;
1443
+ }
1444
+ case "astro": {
1445
+ finalStepsForAstro({
1446
+ projectPath: relativePath,
1447
+ token,
1448
+ siteId,
1449
+ urlDotCMSInstance: host
1450
+ });
1451
+ break;
1452
+ }
1453
+ }
1454
+ }
1455
+ async function startScaffoldingFrontEnd({
1456
+ spinner,
1457
+ selectedFramework,
1458
+ finalDirectory
1459
+ }) {
1460
+ spinner.start(`\u23F3 Scaffolding ${selectedFramework} project...`);
1461
+ const created = await scaffoldFrontendProject({
1462
+ framework: selectedFramework,
1463
+ directory: finalDirectory
1464
+ });
1465
+ if (!created.ok) {
1466
+ spinner.fail(`Failed to scaffold frontend project (${selectedFramework}).`);
1467
+ process.exit(1);
1468
+ }
1469
+ spinner.succeed(`Frontend project (${selectedFramework}) scaffolded successfully.`);
1470
+ spinner.start(
1471
+ `\u{1F4E6} Installing dependencies...
1472
+
1473
+ ${displayDependencies(selectedFramework)}`
1474
+ );
1475
+ const result = await installDependenciesForProject(finalDirectory);
1476
+ if (!result) {
1477
+ spinner.fail(
1478
+ `Failed to install dependencies. Please check if npm is installed in your system`
1479
+ );
1480
+ process.exit(1);
1481
+ } else {
1482
+ spinner.succeed(`Dependencies installed`);
1483
+ }
1484
+ console.log("\n\n");
1485
+ spinner.stop();
1486
+ }
1487
+ function printWelcomeScreen() {
1488
+ cfonts.say("DOTCMS", {
1489
+ font: "block",
1490
+ // define the font face
1491
+ align: "left",
1492
+ // define text alignment
1493
+ colors: ["system"],
1494
+ // define all colors
1495
+ background: "transparent",
1496
+ // define the background color, you can also use `backgroundColor` here as key
1497
+ letterSpacing: 1,
1498
+ // define letter spacing
1499
+ lineHeight: 1,
1500
+ // define the line height
1501
+ space: true,
1502
+ // define if the output text should have empty lines on top and on the bottom
1503
+ maxLength: "0",
1504
+ // define how many character can be on one line
1505
+ gradient: false,
1506
+ // define your two gradient colors
1507
+ independentGradient: false,
1508
+ // define if you want to recalculate the gradient for each new line
1509
+ transitionGradient: false,
1510
+ // define if this is a transition between colors directly
1511
+ rawMode: false,
1512
+ // define if the line breaks should be CRLF (`\r\n`) over the default LF (`\n`)
1513
+ env: "node"
1514
+ // define the environment cfonts is being executed in
1515
+ });
1516
+ console.log(chalk4.white("\nWelcome to dotCMS CLI"));
1517
+ console.log(chalk4.bgGrey.white("\n \u2139\uFE0F Beta: Features may change \n"));
1518
+ }
1519
+ createApp();
1520
+ export {
1521
+ createApp
1522
+ };