Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aae54e9
fix(create-app): Fix critical CLI validation bugs and improve error h…
fmontes Jan 30, 2026
e0579be
feat(create-app): Enhance CLI validation and error handling
fmontes Jan 30, 2026
e8d3d75
refactor(create-app): Improve validation robustness and code quality
fmontes Jan 30, 2026
e528d47
fix: change create-app health check endpoint to avoid Docker ACL issue
fmontes Feb 5, 2026
b082d24
fix(create-app): improve error handling and fix typos
fmontes Feb 5, 2026
72761ff
feat(create-app): add specific Docker and port availability checks
fmontes Feb 5, 2026
3fb2286
Merge branch 'main' into issue-create-app-cli-critical-bugs
fmontes Feb 5, 2026
4ae52c0
refactor(create-app): improve code quality based on review feedback
fmontes Feb 5, 2026
492ec4f
docs(create-app): update README with validation and error handling fe…
fmontes Feb 5, 2026
5e6113c
refactor(create-app): remove duplicate Docker availability check
fmontes Feb 5, 2026
517f57c
refactor(create-app): move getDockerDiagnostics to utils module
fmontes Feb 5, 2026
a8bf732
Update core-web/libs/sdk/create-app/src/utils/index.ts
fmontes Feb 5, 2026
d8aea92
Update core-web/libs/sdk/create-app/src/constants/index.ts
fmontes Feb 5, 2026
cae45ef
Update core-web/libs/sdk/create-app/src/utils/validation.ts
fmontes Feb 5, 2026
92bc315
Update core-web/libs/sdk/create-app/README.md
fmontes Feb 5, 2026
40c643b
refactor(create-app): enhance project name validation and improve URL…
fmontes Feb 5, 2026
257877a
Merge branch 'main' into issue-create-app-cli-critical-bugs
fmontes Feb 5, 2026
f634a0b
fix(create-app): improve authentication validation and error handling
fmontes Feb 5, 2026
d7d0fdd
fix(create-app): strip trailing slashes from URLs to prevent double s…
fmontes Feb 5, 2026
df0c083
fix(create-app): enforce stricter project name validation
fmontes Feb 5, 2026
394d3ee
fix(create-app): ensure non-zero exit codes for all failure scenarios
fmontes Feb 5, 2026
7421c38
fix(create-app): improve directory path display in output messages
fmontes Feb 5, 2026
abf7dba
fix(create-app): prevent nested directory structure when path include…
fmontes Feb 5, 2026
72e11cd
Merge branch 'main' into issue-create-app-cli-critical-bugs
fmontes Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 156 additions & 17 deletions core-web/libs/sdk/create-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
> 🚧 **Beta Notice:**
> This CLI is currently in **beta**. Features and APIs may change as we continue improving the tool.

With a single command, you can bootstrap a fully functional frontend (Next.js,Vue,Angular, etc.) connected to dotCMS APIs β€” including the following:
With a single command, you can bootstrap a fully functional frontend (Next.js, Angular, Astro, etc.) connected to dotCMS APIs β€” including the following:

- Spinning up dotCMS using docker
- Universal Visual Editor pre configured
- Generating site id and authentication token (just copy paste them in frontend .env and enjoy).
- Spinning up dotCMS using Docker with intelligent pre-flight checks
- Universal Visual Editor pre-configured
- Generating site ID and authentication token (just copy paste them in frontend .env and enjoy)
- Comprehensive validation and helpful error messages

---

Expand All @@ -17,12 +18,13 @@ With a single command, you can bootstrap a fully functional frontend (Next.js,Vu

It automates the tedious work of:

- Setting up a framework
- Setting up a framework with best practices
- Connecting to dotCMS REST & GraphQL APIs
- Providing content-fetching helpers
- Adding example components & pages
- Creating environment variable templates
- Optional Setting up local dotCMS instance using docker.
- Optionally setting up local dotCMS instance using Docker
- Validating your environment (Docker, ports, URLs)

This tool lets you focus on **building**, not configuring.

Expand Down Expand Up @@ -65,8 +67,12 @@ npx @dotcms/create-app <project-name>
This will:

- Ask for the target directory
- Ask which frontend framework you want (Next.js,Angular,Vue, etc.)
- Ask if you’re using dotCMS Cloud or Local Docker dotCMS
- Ask which frontend framework you want (Next.js, Angular, Astro, etc.)
- Ask if you're using dotCMS Cloud or Local Docker dotCMS
- **For local Docker setup:**
- Check if Docker is installed and running
- Verify required ports are available (8082, 8443, 9200, 9600)
- Provide specific error messages and solutions if issues are found
- Automatically scaffold your project
- Configure UVE (Edit Mode Anywhere)
- Start dotCMS (if using Docker)
Expand All @@ -88,12 +94,145 @@ API Token : YOUR_API_TOKEN
create-dotcms-app <project-name> [options]
```

| Option | Description |
|--------------------------|-----------------------------------------------------------------------------------------------------------------|
| `-f, --framework <name>` | Skip prompts and directly choose a framework. Must be one of: `nextjs`, `angular`, `angular-ssr`, `astro`, etc. |
| `-d, --directory` | Project Directory |
| `--local` | Use local dotCMS instance using docker: |
| `-u, --username` | dotCMS instance username (skip in case of local) |
| `-p, --password` | dotCMS instance password (skip in case of local)
| `--url` | dotCMS instance url (skip in case of local) |
| `-V, --version` | Show CLI version |
| Option | Description |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-f, --framework <name>` | Skip prompts and directly choose a framework. Supported: `nextjs`, `angular`, `angular-ssr`, `astro`. Case-insensitive. Aliases: `next`, `ng`, `next.js`, `angular-server` |
| `-d, --directory` | Project Directory |
| `--local` | Use local dotCMS instance using docker |
| `-u, --username` | dotCMS instance username (skip in case of local) |
| `-p, --password` | dotCMS instance password (skip in case of local) |
| `--url` | dotCMS instance URL (skip in case of local). Must include protocol (http:// or https://) |
| `-V, --version` | Show CLI version |

### Framework Aliases

For convenience, the CLI accepts common framework name variations (case-insensitive):

- `next`, `next.js`, `Next.js` β†’ `nextjs`
- `ng`, `Angular` β†’ `angular`
- `angular-server` β†’ `angular-ssr`
- `angular-server` β†’ `angular-ssr`

### URL Format

When using `--url`, make sure to include the full URL with protocol:

βœ… **Valid:** `https://demo.dotcms.com`, `http://localhost:8082`
❌ **Invalid:** `demo.dotcms.com`, `localhost:8082` (missing protocol)

---

## πŸ›‘οΈ Built-in Validation & Error Handling

The CLI includes comprehensive validation to help you avoid common setup issues:

### Input Validation
- **Project names:** Validates against filesystem limitations, reserved names, special characters, and path traversal
- **Framework names:** Supports aliases and case-insensitive matching
- **URLs:** Ensures proper protocol and format
- **Conflicting parameters:** Warns when mixing `--local` with cloud parameters

### Docker Environment Checks
When using local Docker setup, the CLI performs pre-flight checks:

1. **Docker Availability**
- Verifies Docker is installed and running
- Provides installation instructions if not found
- Direct link to Docker Desktop download

2. **Port Availability**
- Checks all required ports before starting containers
- Lists which specific ports are busy
- Provides platform-specific commands to identify blocking processes
- Suggests `docker compose down` to stop existing containers

3. **Container Health**
- Monitors dotCMS startup with intelligent retries
- Shows detailed diagnostics if startup fails
- Displays container status and recent logs

### Error Messages
All error messages include:
- **Clear problem description** - What went wrong
- **Specific suggestions** - How to fix it
- **Alternative solutions** - Other ways to proceed
- **Platform-specific commands** - Commands tailored to your OS

### Debug Mode
Run with `DEBUG=1` to see detailed stack traces:
```sh
DEBUG=1 npx @dotcms/create-app my-project
```

---

## πŸ”§ Troubleshooting

### Docker Issues

**"Docker is not available"**
- Ensure Docker Desktop is installed and running
- Check the Docker icon in your system tray
- Download from: https://www.docker.com/products/docker-desktop

**"Required ports are already in use"**
- Check what's using the ports:
- macOS/Linux: `lsof -i :8082`
- Windows: `netstat -ano | findstr ":8082"`
- Stop existing dotCMS containers: `docker compose down`
- Stop conflicting services or choose a different port mapping

**"dotCMS failed to start properly"**
- Check container logs: `docker logs <container-name>`
- Verify sufficient system resources (RAM, disk space)
- Review Docker diagnostics output from the CLI

### Validation Errors

**"Invalid project name"**
- Avoid special characters: `< > : " | ? *`
- Don't use Windows reserved names: `CON`, `PRN`, `AUX`, `NUL`, etc.
- Maximum 255 characters
- No path traversal patterns (`..`)

**"Invalid URL format"**
- Include protocol: `https://` or `http://`
- Verify hostname is correct
- For localhost, default port is 8082

---

## πŸš€ Examples

### Quick Start with Interactive Prompts
```sh
npx @dotcms/create-app my-blog
```

### Using CLI Flags (Skip Prompts)
```sh
# Local Docker setup
npx @dotcms/create-app my-blog --framework nextjs --local

# Cloud instance
npx @dotcms/create-app my-blog \
--framework angular \
--url https://demo.dotcms.com \
--username admin@dotcms.com \
--password mypassword
```

### Debug Mode
```sh
DEBUG=1 npx @dotcms/create-app my-blog --framework nextjs --local
```

---

## πŸ”’ Security Notes

- Never commit API tokens or passwords to version control
- The CLI validates all inputs to prevent injection attacks
- Project names are sanitized to prevent path traversal
- Shell commands are properly escaped for cross-platform safety

46 changes: 37 additions & 9 deletions core-web/libs/sdk/create-app/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import axios from 'axios';
import chalk from 'chalk';
import { Ok, type Result, Err } from 'ts-results';

import { DOTCMS_DEMO_SITE, DOTCMS_EMA_CONFIG_API, DOTCMS_TOKEN_API } from '../constants';
import {
FailedToGetDemoSiteIdentifierError,
FailedToGetDotcmsTokenError,
FailedToSetUpUVEConfig
} from '../errors';
import { FailedToGetDemoSiteIdentifierError, FailedToSetUpUVEConfig } from '../errors';

import type {
DemoSiteResponse,
Expand All @@ -27,14 +24,45 @@ export class DotCMSApi {
}: {
payload: GetUserTokenRequest;
url?: string;
}): Promise<Result<string, FailedToGetDotcmsTokenError>> {
}): Promise<Result<string, string>> {
const endpoint = url || this.defaultTokenApi;

try {
const endpoint = url || this.defaultTokenApi;
const res = await axios.post<GetUserTokenResponse>(endpoint, payload);
return Ok(res.data.entity.token);
} catch (err) {
console.error('dotCMS failed to get token' + JSON.stringify(err));
return Err(new FailedToGetDotcmsTokenError());
// Provide specific error messages based on error type
if (axios.isAxiosError(err)) {
if (err.response?.status === 401) {
return Err(
chalk.red('\n❌ Authentication failed\n\n') +
chalk.white('Invalid username or password.\n\n') +
chalk.yellow('Please check your credentials and try again:\n') +
chalk.white(' β€’ Verify your username is correct\n') +
chalk.white(' β€’ Ensure your password is correct\n') +
chalk.white(' β€’ Check if your account is active\n')
);
} else if (err.code === 'ECONNREFUSED') {
return Err(
chalk.red('\n❌ Connection refused\n\n') +
chalk.white(`Could not connect to dotCMS at: ${endpoint}\n\n`) +
chalk.yellow('Please verify:\n') +
chalk.white(' β€’ The URL is correct\n') +
chalk.white(' β€’ The dotCMS instance is running\n') +
chalk.white(' β€’ There are no firewall issues\n')
);
} else if (err.response) {
return Err(
chalk.red(`\n❌ Server error (${err.response.status})\n\n`) +
chalk.white('The dotCMS server returned an error.\n') +
chalk.gray(`Details: ${err.response.statusText || 'Unknown error'}\n`)
);
}
}
return Err(
chalk.red('\n❌ Failed to get authentication token\n\n') +
chalk.gray(err instanceof Error ? err.message : String(err))
);
}
}

Expand Down
61 changes: 54 additions & 7 deletions core-web/libs/sdk/create-app/src/asks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import inquirer from 'inquirer';
import path from 'path';

import { FRAMEWORKS_CHOICES } from './constants';
import { validateProjectName, validateUrl } from './utils/validation';

import type { SupportedFrontEndFrameworks } from './types';

Expand Down Expand Up @@ -32,7 +33,15 @@ export async function askProjectName() {
type: 'input',
name: 'projectName',
message: 'What is your project name ?',
default: `my-dotcms-app`
default: `my-dotcms-app`,
validate: (input: string) => {
try {
validateProjectName(input);
return true;
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
}
]);
return ans.projectName;
Expand Down Expand Up @@ -62,7 +71,15 @@ export async function askDotcmsCloudUrl() {
type: 'input',
name: 'url',
message: 'dotCMS instance URL:',
default: `https://demo.dotcms.com`
default: `https://demo.dotcms.com`,
validate: (input: string) => {
try {
validateUrl(input);
return true;
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
}
]);
return ans.url;
Expand All @@ -77,14 +94,20 @@ export async function askUserNameForDotcmsCloud() {
type: 'input',
name: 'username',
message: 'Username:',
default: `admin@dotcms.com`
default: `admin@dotcms.com`,
validate: (input: string) => {
if (!input || input.trim() === '') {
return 'Username cannot be empty';
}
return true;
}
}
]);
return ans.username;
}

/**
* Ask user the ulsername of the dotCMS instance
* Ask user the password of the dotCMS instance
*/
export async function askPasswordForDotcmsCloud() {
const ans = await inquirer.prompt([
Expand All @@ -93,7 +116,13 @@ export async function askPasswordForDotcmsCloud() {
name: 'password',
mask: 'β€’',
message: 'Password:',
default: `admin`
default: `admin`,
validate: (input: string) => {
if (!input || input.trim() === '') {
return 'Password cannot be empty';
}
return true;
}
}
]);
return ans.password;
Expand Down Expand Up @@ -124,7 +153,7 @@ export async function askCloudOrLocalInstance(): Promise<boolean> {
{
type: 'select',
name: 'isCloud',
message: 'Do you have an exsisting dotCMS instance?',
message: 'Do you have an existing dotCMS instance?',
choices: [
{ name: 'Yes - I have a dotCMS instance URL', value: true },
{ name: 'No - Spin up dotCMS locally with Docker', value: false }
Expand All @@ -142,9 +171,27 @@ export async function askCloudOrLocalInstance(): Promise<boolean> {
* user enters: "."
* projectName: "my-app"
* final path becomes "./my-app"
*
* @remarks
* - Prevents nested directories when basePath already ends with projectName
* - Example: basePath="/tmp/my-app" + projectName="my-app" β†’ "/tmp/my-app" (not "/tmp/my-app/my-app")
* - Handles both absolute and relative paths correctly
*/
export async function prepareDirectory(basePath: string, projectName: string) {
const targetPath = path.resolve(basePath, projectName);
// Resolve basePath to absolute path for consistent comparison
const resolvedBasePath = path.resolve(basePath);
const basePathDirName = path.basename(resolvedBasePath);

// Check if basePath already ends with the project name
// This prevents nested directories like "/tmp/my-app/my-app"
let targetPath: string;
if (basePathDirName === projectName) {
// User specified full path including project name (e.g., "-d /tmp/my-app" with projectName="my-app")
targetPath = resolvedBasePath;
} else {
// User specified parent directory (e.g., "-d /tmp" with projectName="my-app")
targetPath = path.resolve(resolvedBasePath, projectName);
}

// If path doesn't exist β†’ create
if (!fs.existsSync(targetPath)) {
Expand Down
Loading
Loading