create-ely 0.1.2 → 0.1.4

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 (71) hide show
  1. package/README.md +36 -27
  2. package/package.json +14 -4
  3. package/scripts/postinstall.ts +43 -0
  4. package/src/constants.ts +20 -0
  5. package/src/git.ts +95 -0
  6. package/src/index.ts +123 -251
  7. package/src/template.ts +106 -0
  8. package/src/utils.ts +64 -0
  9. package/templates.zip +0 -0
  10. package/templates/monorepo/README.md +0 -75
  11. package/templates/monorepo/apps/backend/.cursor/mcp.json +0 -8
  12. package/templates/monorepo/apps/backend/.dockerignore +0 -60
  13. package/templates/monorepo/apps/backend/.env.example +0 -19
  14. package/templates/monorepo/apps/backend/.github/workflows/lint.yml +0 -31
  15. package/templates/monorepo/apps/backend/.github/workflows/tests.yml +0 -36
  16. package/templates/monorepo/apps/backend/AGENTS.md +0 -79
  17. package/templates/monorepo/apps/backend/CHANGELOG.md +0 -190
  18. package/templates/monorepo/apps/backend/CLAUDE.md +0 -149
  19. package/templates/monorepo/apps/backend/Dockerfile +0 -35
  20. package/templates/monorepo/apps/backend/LICENSE +0 -21
  21. package/templates/monorepo/apps/backend/README.md +0 -274
  22. package/templates/monorepo/apps/backend/biome.json +0 -58
  23. package/templates/monorepo/apps/backend/bun.lock +0 -303
  24. package/templates/monorepo/apps/backend/bunfig.toml +0 -4
  25. package/templates/monorepo/apps/backend/docker-compose.yml +0 -37
  26. package/templates/monorepo/apps/backend/drizzle.config.ts +0 -14
  27. package/templates/monorepo/apps/backend/package.json +0 -42
  28. package/templates/monorepo/apps/backend/src/common/config.ts +0 -29
  29. package/templates/monorepo/apps/backend/src/common/logger.ts +0 -18
  30. package/templates/monorepo/apps/backend/src/db/index.ts +0 -31
  31. package/templates/monorepo/apps/backend/src/db/migrations/20251111132328_curly_spectrum.sql +0 -8
  32. package/templates/monorepo/apps/backend/src/db/migrations/meta/20251111132328_snapshot.json +0 -70
  33. package/templates/monorepo/apps/backend/src/db/migrations/meta/_journal.json +0 -13
  34. package/templates/monorepo/apps/backend/src/db/schema/users.ts +0 -39
  35. package/templates/monorepo/apps/backend/src/main.ts +0 -67
  36. package/templates/monorepo/apps/backend/src/middleware/error-handler.ts +0 -36
  37. package/templates/monorepo/apps/backend/src/modules/users/index.ts +0 -61
  38. package/templates/monorepo/apps/backend/src/modules/users/model.ts +0 -48
  39. package/templates/monorepo/apps/backend/src/modules/users/service.ts +0 -46
  40. package/templates/monorepo/apps/backend/src/tests/users.test.ts +0 -102
  41. package/templates/monorepo/apps/backend/src/util/graceful-shutdown.ts +0 -37
  42. package/templates/monorepo/apps/backend/tsconfig.json +0 -35
  43. package/templates/monorepo/apps/backend-biome.json.template +0 -14
  44. package/templates/monorepo/apps/frontend/README.md +0 -59
  45. package/templates/monorepo/apps/frontend/biome.json +0 -16
  46. package/templates/monorepo/apps/frontend/components.json +0 -21
  47. package/templates/monorepo/apps/frontend/index.html +0 -15
  48. package/templates/monorepo/apps/frontend/package.json +0 -48
  49. package/templates/monorepo/apps/frontend/public/favicon.ico +0 -0
  50. package/templates/monorepo/apps/frontend/src/assets/fonts/.gitkeep +0 -0
  51. package/templates/monorepo/apps/frontend/src/assets/images/.gitkeep +0 -0
  52. package/templates/monorepo/apps/frontend/src/features/layout/Header.tsx +0 -73
  53. package/templates/monorepo/apps/frontend/src/main.tsx +0 -36
  54. package/templates/monorepo/apps/frontend/src/routeTree.gen.ts +0 -95
  55. package/templates/monorepo/apps/frontend/src/routes/__root.tsx +0 -25
  56. package/templates/monorepo/apps/frontend/src/routes/index.tsx +0 -34
  57. package/templates/monorepo/apps/frontend/src/routes/users/index.tsx +0 -79
  58. package/templates/monorepo/apps/frontend/src/routes/users/new.tsx +0 -148
  59. package/templates/monorepo/apps/frontend/src/shared/api/client.ts +0 -6
  60. package/templates/monorepo/apps/frontend/src/shared/components/.gitkeep +0 -0
  61. package/templates/monorepo/apps/frontend/src/shared/constants/.gitkeep +0 -0
  62. package/templates/monorepo/apps/frontend/src/shared/hooks/.gitkeep +0 -0
  63. package/templates/monorepo/apps/frontend/src/shared/types/.gitkeep +0 -0
  64. package/templates/monorepo/apps/frontend/src/shared/utils/utils.ts +0 -6
  65. package/templates/monorepo/apps/frontend/src/styles.css +0 -138
  66. package/templates/monorepo/apps/frontend/src/vite-env.d.ts +0 -13
  67. package/templates/monorepo/apps/frontend/tsconfig.json +0 -27
  68. package/templates/monorepo/apps/frontend/vite.config.ts +0 -27
  69. package/templates/monorepo/biome.json +0 -65
  70. package/templates/monorepo/bun.lock +0 -1044
  71. package/templates/monorepo/package.json +0 -13
package/README.md CHANGED
@@ -1,49 +1,58 @@
1
1
  # create-ely
2
2
 
3
- Scaffold ElysiaJS projects with ease using [Bun](https://bun.sh).
3
+ [![Lint](https://github.com/truehazker/create-ely/actions/workflows/lint.yml/badge.svg)](https://github.com/truehazker/create-ely/actions/workflows/lint.yml)
4
+ [![npm version](https://img.shields.io/npm/v/create-ely.svg)](https://www.npmjs.com/package/create-ely)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
6
 
5
- ## Usage
7
+ [![Bun](https://img.shields.io/badge/Bun-000000?logo=bun)](https://bun.sh)
8
+ [![ElysiaJS](https://img.shields.io/badge/ElysiaJS-6366f1?logo=elysia&logoColor=white)](https://elysiajs.com)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
10
+ [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-316192?logo=postgresql&logoColor=white)](https://www.postgresql.org/)
6
11
 
7
- Create a new ElysiaJS project:
12
+ The fastest way to scaffold production-ready [ElysiaJS](https://elysiajs.com) projects with [Bun](https://bun.sh).
13
+
14
+ ![Demo](https://github.com/user-attachments/assets/67386464-eb39-4b71-8b9b-34039e2861d0)
15
+
16
+ ## Quick Start
17
+
18
+ Create a new project:
8
19
 
9
20
  ```bash
10
- bunx create-ely my-project
21
+ bun create ely
11
22
  ```
12
23
 
13
- You'll be prompted to choose between:
24
+ Or with a project name:
14
25
 
15
- - **Backend only** - ElysiaJS API with PostgreSQL, Drizzle ORM
16
- - **Monorepo** - Backend + Frontend (React with TanStack Router)
26
+ ```bash
27
+ bun create ely my-project
28
+ ```
17
29
 
18
- ## Templates
30
+ You'll be prompted to choose:
19
31
 
20
- ### Backend Only
32
+ - **Backend Only** - API-first ElysiaJS backend with PostgreSQL, Drizzle ORM, and OpenAPI docs
33
+ - **Monorepo** - Full-stack setup with React frontend, TanStack Router, and shared workspace
21
34
 
22
- A production-ready ElysiaJS backend with:
35
+ ## What's Included
23
36
 
24
- - PostgreSQL database with Drizzle ORM
25
- - Type-safe API with OpenAPI documentation
26
- - Global error handling
27
- - Structured logging with Pino
28
- - Docker support
29
- - Environment validation
37
+ **Backend Template:**
30
38
 
31
- ### Monorepo
39
+ - PostgreSQL + Drizzle ORM for type-safe database access
40
+ - OpenAPI documentation out of the box
41
+ - Global error handling and structured logging (Pino)
42
+ - Docker support for development and production
43
+ - Environment validation with type safety
32
44
 
33
- Full-stack setup with:
45
+ **Monorepo Template:**
34
46
 
35
- - Backend: Everything from Backend Only template
36
- - Frontend: React + TanStack Router + Vite
37
- - Workspace configuration with Bun
47
+ - Everything from Backend template
48
+ - React frontend with TanStack Router and Vite
49
+ - Bun workspaces for seamless monorepo management
38
50
 
39
- ## Development
51
+ ## Contributing
40
52
 
41
- To test the CLI locally:
53
+ > **⚠️ Important:** This project uses Git submodules for templates. Make sure to clone with `git clone --recurse-submodules` or run `git submodule update --init --recursive` after cloning.
42
54
 
43
- ```bash
44
- bun link
45
- bunx create-ely
46
- ```
55
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
47
56
 
48
57
  ## License
49
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ely",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Scaffold production-ready ElysiaJS projects with ease using Bun - choose between backend-only or full-stack monorepo templates",
5
5
  "type": "module",
6
6
  "author": {
@@ -38,10 +38,18 @@
38
38
  "bin": {
39
39
  "create-ely": "./src/index.ts"
40
40
  },
41
- "files": ["src", "templates", "README.md", "LICENSE"],
41
+ "files": [
42
+ "src",
43
+ "templates.zip",
44
+ "scripts/postinstall.ts",
45
+ "README.md",
46
+ "LICENSE"
47
+ ],
42
48
  "scripts": {
43
49
  "dev": "bun run src/index.ts",
44
- "build": "bun build src/index.ts --compile --outfile dist/create-ely"
50
+ "build": "bun build src/index.ts --compile --outfile dist/create-ely",
51
+ "prepare": "bun run scripts/prepare.ts",
52
+ "postinstall": "bun run scripts/postinstall.ts"
45
53
  },
46
54
  "engines": {
47
55
  "bun": ">=1.0.0"
@@ -51,10 +59,12 @@
51
59
  "registry": "https://registry.npmjs.org/"
52
60
  },
53
61
  "dependencies": {
54
- "@clack/prompts": "^0.11.0"
62
+ "@clack/prompts": "^0.11.0",
63
+ "adm-zip": "^0.5.16"
55
64
  },
56
65
  "devDependencies": {
57
66
  "@biomejs/biome": "2.3.10",
67
+ "@types/adm-zip": "^0.5.7",
58
68
  "@types/bun": "^1.3.5"
59
69
  }
60
70
  }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import AdmZip from 'adm-zip';
5
+
6
+ /**
7
+ * Postinstall script that runs after package installation
8
+ * Unzips the templates folder to restore .gitignore files
9
+ * See: https://johnnyreilly.com/smuggling-gitignore-npmrc-in-npm-packages
10
+ */
11
+ const templatesPath = join(import.meta.dir, '..', 'templates');
12
+ const zipPath = join(import.meta.dir, '..', 'templates.zip');
13
+
14
+ // Skip if templates folder already exists (source repo or already extracted)
15
+ if (existsSync(templatesPath)) {
16
+ process.exit(0);
17
+ }
18
+
19
+ // Zip file must exist in installed package
20
+ if (!existsSync(zipPath)) {
21
+ console.error(
22
+ 'ERROR: templates.zip not found. Installation may be corrupted.',
23
+ );
24
+ process.exit(1);
25
+ }
26
+
27
+ try {
28
+ const zip = new AdmZip(zipPath);
29
+ zip.extractAllTo(templatesPath, true);
30
+
31
+ if (!existsSync(templatesPath)) {
32
+ throw new Error('Templates folder was not created after extraction');
33
+ }
34
+ } catch (error) {
35
+ console.error(
36
+ 'ERROR: Failed to extract templates:',
37
+ error instanceof Error ? error.message : error,
38
+ );
39
+ console.error(
40
+ 'Report this issue: https://github.com/truehazker/create-ely/issues',
41
+ );
42
+ process.exit(1);
43
+ }
@@ -0,0 +1,20 @@
1
+ export const TEMPLATE_TYPES = {
2
+ BACKEND: 'backend',
3
+ MONOREPO: 'monorepo',
4
+ } as const;
5
+
6
+ export const DEFAULT_PROJECT_NAME = 'my-ely-app';
7
+
8
+ export const PROJECT_NAME_REGEX = /^[a-z0-9-]+$/;
9
+
10
+ export const EXCLUDED_COPY_PATTERNS = ['node_modules', '.git'];
11
+
12
+ export const PORTS = {
13
+ BACKEND: 3000,
14
+ FRONTEND: 5173,
15
+ } as const;
16
+
17
+ export const TEMPLATE_PATHS = {
18
+ BACKEND_BIOME_TEMPLATE: 'apps/backend-biome.json.template',
19
+ BACKEND_BIOME_TARGET: 'apps/backend/biome.json',
20
+ } as const;
package/src/git.ts ADDED
@@ -0,0 +1,95 @@
1
+ import * as clack from '@clack/prompts';
2
+
3
+ /**
4
+ * Initializes a git repository in the target directory and creates an initial commit
5
+ * @param targetDir - The directory to initialize git in
6
+ * @returns Promise that resolves when git initialization is complete
7
+ */
8
+ export async function initializeGit(targetDir: string): Promise<void> {
9
+ const initGit = await clack.confirm({
10
+ message: 'Initialize git repository?',
11
+ initialValue: true,
12
+ });
13
+
14
+ if (clack.isCancel(initGit)) {
15
+ clack.cancel('Operation cancelled');
16
+ process.exit(0);
17
+ }
18
+
19
+ if (!initGit) {
20
+ return;
21
+ }
22
+
23
+ // Check if git is available
24
+ const gitCheckProc = Bun.spawn(['git', '--version'], {
25
+ stdout: 'pipe',
26
+ stderr: 'pipe',
27
+ });
28
+ await gitCheckProc.exited;
29
+
30
+ if (gitCheckProc.exitCode !== 0) {
31
+ clack.log.warn(
32
+ 'Git is not installed or not available. Skipping git initialization.',
33
+ );
34
+ return;
35
+ }
36
+
37
+ const gitSpinner = clack.spinner();
38
+ gitSpinner.start('Initializing git repository...');
39
+
40
+ try {
41
+ const gitInitProc = Bun.spawn(['git', 'init'], {
42
+ cwd: targetDir,
43
+ stdout: 'pipe',
44
+ stderr: 'pipe',
45
+ });
46
+ await gitInitProc.exited;
47
+
48
+ if (gitInitProc.exitCode !== 0) {
49
+ gitSpinner.stop('Failed to initialize git');
50
+ clack.log.warn(
51
+ 'Git initialization failed. You can initialize manually later.',
52
+ );
53
+ return;
54
+ }
55
+
56
+ gitSpinner.stop('Git repository initialized');
57
+
58
+ // Make initial commit
59
+ gitSpinner.start('Creating initial commit...');
60
+
61
+ const gitAddProc = Bun.spawn(['git', 'add', '.'], {
62
+ cwd: targetDir,
63
+ stdout: 'pipe',
64
+ stderr: 'pipe',
65
+ });
66
+ await gitAddProc.exited;
67
+
68
+ if (gitAddProc.exitCode !== 0) {
69
+ gitSpinner.stop('Git initialized (add failed)');
70
+ clack.log.warn('Failed to stage files. You can add them manually later.');
71
+ return;
72
+ }
73
+
74
+ const gitCommitProc = Bun.spawn(['git', 'commit', '-m', 'Initial commit'], {
75
+ cwd: targetDir,
76
+ stdout: 'pipe',
77
+ stderr: 'pipe',
78
+ });
79
+ await gitCommitProc.exited;
80
+
81
+ if (gitCommitProc.exitCode === 0) {
82
+ gitSpinner.stop('Initial commit created');
83
+ } else {
84
+ gitSpinner.stop('Git initialized (commit failed)');
85
+ clack.log.warn(
86
+ 'Failed to create initial commit. You can commit manually later.',
87
+ );
88
+ }
89
+ } catch {
90
+ gitSpinner.stop('Git initialization failed');
91
+ clack.log.warn(
92
+ 'An error occurred during git initialization. You can initialize manually later.',
93
+ );
94
+ }
95
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,117 @@
1
1
  #!/usr/bin/env bun
2
- import {
3
- copyFileSync,
4
- existsSync,
5
- mkdirSync,
6
- readdirSync,
7
- rmSync,
8
- statSync,
9
- } from 'node:fs';
10
- import { dirname, join } from 'node:path';
2
+ import { existsSync, readdirSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
11
4
  import * as clack from '@clack/prompts';
5
+ import {
6
+ DEFAULT_PROJECT_NAME,
7
+ PORTS,
8
+ PROJECT_NAME_REGEX,
9
+ TEMPLATE_TYPES,
10
+ } from './constants.ts';
11
+ import { initializeGit } from './git.ts';
12
+ import { setupTemplate } from './template.ts';
13
+ import { validateProjectName } from './utils.ts';
14
+
15
+ /**
16
+ * Gets the project name from command line arguments or prompts the user
17
+ * @param args - Command line arguments
18
+ * @returns Promise resolving to the project name
19
+ */
20
+ async function getProjectName(args: string[]): Promise<string> {
21
+ const projectNameArg = args[0];
22
+
23
+ // If project name was provided as argument and is valid, use it
24
+ if (projectNameArg && PROJECT_NAME_REGEX.test(projectNameArg)) {
25
+ return projectNameArg;
26
+ }
27
+
28
+ // Otherwise, prompt for it with default value
29
+ const projectName = await clack.text({
30
+ message: 'Project name:',
31
+ placeholder: DEFAULT_PROJECT_NAME,
32
+ initialValue: DEFAULT_PROJECT_NAME,
33
+ validate: validateProjectName,
34
+ });
35
+
36
+ if (clack.isCancel(projectName)) {
37
+ clack.cancel('Operation cancelled');
38
+ process.exit(0);
39
+ }
40
+
41
+ // Trim and normalize the project name
42
+ return projectName.trim();
43
+ }
44
+
45
+ /**
46
+ * Handles existing directory by prompting user to overwrite if not empty
47
+ * @param targetDir - The target directory path
48
+ * @param projectName - The project name
49
+ * @returns Promise that resolves when directory is ready
50
+ */
51
+ async function handleExistingDirectory(
52
+ targetDir: string,
53
+ projectName: string,
54
+ ): Promise<void> {
55
+ if (!existsSync(targetDir)) {
56
+ return;
57
+ }
58
+
59
+ const dirContents = readdirSync(targetDir);
60
+ const isEmpty = dirContents.length === 0;
61
+
62
+ if (isEmpty) {
63
+ return;
64
+ }
65
+
66
+ const shouldOverwrite = await clack.confirm({
67
+ message: `Directory "${projectName}" already exists and is not empty. Overwrite it?`,
68
+ initialValue: false,
69
+ });
70
+
71
+ if (clack.isCancel(shouldOverwrite) || !shouldOverwrite) {
72
+ clack.cancel('Operation cancelled');
73
+ process.exit(0);
74
+ }
75
+
76
+ rmSync(targetDir, { recursive: true, force: true });
77
+ }
78
+
79
+ /**
80
+ * Generates the next steps message based on project type
81
+ * @param projectType - The type of project created
82
+ * @param projectName - The project name
83
+ * @param targetDir - The target directory path
84
+ * @returns Formatted message with next steps
85
+ */
86
+ function getNextStepsMessage(
87
+ projectType: string,
88
+ projectName: string,
89
+ targetDir: string,
90
+ ): string {
91
+ const projectTypeLabel =
92
+ projectType === TEMPLATE_TYPES.MONOREPO ? 'monorepo' : 'backend';
93
+
94
+ const nextSteps =
95
+ projectType === TEMPLATE_TYPES.MONOREPO
96
+ ? ` cd ${projectName}
97
+ bun run dev:backend # Start backend on http://localhost:${PORTS.BACKEND}
98
+ bun run dev:frontend # Start frontend on http://localhost:${PORTS.FRONTEND}`
99
+ : ` cd ${projectName}
100
+ bun run dev # Start backend on http://localhost:${PORTS.BACKEND}`;
101
+
102
+ return `
103
+ ✨ Success! Your ElysiaJS ${projectTypeLabel} project is ready.
104
+
105
+ 📁 Project created at: ${targetDir}
106
+
107
+ 🚀 Next steps:
108
+ ${nextSteps}
109
+
110
+ 📚 Check out the README.md for more information.
111
+
112
+ Happy coding! 🎉
113
+ `;
114
+ }
12
115
 
13
116
  async function main() {
14
117
  console.clear();
@@ -17,19 +120,18 @@ async function main() {
17
120
 
18
121
  // Check if project name was passed as argument
19
122
  const args = process.argv.slice(2);
20
- const projectNameArg = args[0];
21
123
 
22
124
  // Step 1: Select project type
23
125
  const projectType = await clack.select({
24
126
  message: 'What would you like to create?',
25
127
  options: [
26
128
  {
27
- value: 'backend',
129
+ value: TEMPLATE_TYPES.BACKEND,
28
130
  label: 'Backend only',
29
131
  hint: 'ElysiaJS API with PostgreSQL',
30
132
  },
31
133
  {
32
- value: 'monorepo',
134
+ value: TEMPLATE_TYPES.MONOREPO,
33
135
  label: 'Monorepo',
34
136
  hint: 'Backend + Frontend (React + TanStack Router)',
35
137
  },
@@ -42,252 +144,22 @@ async function main() {
42
144
  }
43
145
 
44
146
  // Step 2: Get project name
45
- let projectName: string | symbol;
46
-
47
- // If project name was provided as argument and is valid, use it
48
- if (projectNameArg && /^[a-z0-9-]+$/.test(projectNameArg)) {
49
- projectName = projectNameArg;
50
- } else {
51
- // Otherwise, prompt for it with default value
52
- projectName = await clack.text({
53
- message: 'Project name:',
54
- placeholder: 'my-ely-app',
55
- initialValue: 'my-ely-app',
56
- validate: (value) => {
57
- if (!value || value.trim() === '') {
58
- return 'Project name is required';
59
- }
60
- const trimmed = value.trim();
61
- if (!/^[a-z0-9-]+$/.test(trimmed)) {
62
- return 'Project name must contain only lowercase letters, numbers, and hyphens';
63
- }
64
- if (trimmed.startsWith('-') || trimmed.endsWith('-')) {
65
- return 'Project name cannot start or end with a hyphen';
66
- }
67
- return undefined;
68
- },
69
- });
70
-
71
- if (clack.isCancel(projectName)) {
72
- clack.cancel('Operation cancelled');
73
- process.exit(0);
74
- }
75
-
76
- // Trim and normalize the project name
77
- projectName = (projectName as string).trim();
78
- }
79
-
80
- const targetDir = join(process.cwd(), projectName as string);
147
+ const projectName = await getProjectName(args);
148
+ const targetDir = join(process.cwd(), projectName);
81
149
 
82
150
  // Step 3: Check if directory exists and handle it
83
- if (existsSync(targetDir)) {
84
- const dirContents = readdirSync(targetDir);
85
- const isEmpty = dirContents.length === 0;
86
-
87
- if (!isEmpty) {
88
- const shouldOverwrite = await clack.confirm({
89
- message: `Directory "${String(projectName)}" already exists and is not empty. Overwrite it?`,
90
- initialValue: false,
91
- });
92
-
93
- if (clack.isCancel(shouldOverwrite) || !shouldOverwrite) {
94
- clack.cancel('Operation cancelled');
95
- process.exit(0);
96
- }
97
-
98
- rmSync(targetDir, { recursive: true, force: true });
99
- }
100
- }
101
-
102
- const spinner = clack.spinner();
103
- spinner.start('Creating project...');
151
+ await handleExistingDirectory(targetDir, projectName);
104
152
 
105
153
  try {
106
- const templateDir = join(
107
- import.meta.dir,
108
- '..',
109
- 'templates',
110
- projectType as string,
111
- );
112
-
113
- // Cross-platform recursive copy function
114
- function copyRecursive(src: string, dest: string, exclude: string[] = []) {
115
- const stats = statSync(src);
116
-
117
- if (stats.isDirectory()) {
118
- if (!existsSync(dest)) {
119
- mkdirSync(dest, { recursive: true });
120
- }
121
-
122
- for (const entry of readdirSync(src)) {
123
- if (exclude.includes(entry) || entry.endsWith('.template')) continue;
124
-
125
- const srcPath = join(src, entry);
126
- const destPath = join(dest, entry);
127
- copyRecursive(srcPath, destPath, exclude);
128
- }
129
- } else {
130
- const destDir = dirname(dest);
131
- if (!existsSync(destDir)) {
132
- mkdirSync(destDir, { recursive: true });
133
- }
134
- copyFileSync(src, dest);
135
- }
136
- }
137
-
138
- copyRecursive(templateDir, targetDir, ['node_modules', '.git']);
139
-
140
- // Handle template files: copy them to their intended locations
141
- if (projectType === 'monorepo') {
142
- const backendBiomeTemplate = join(
143
- templateDir,
144
- 'apps',
145
- 'backend-biome.json.template',
146
- );
147
- const backendBiomeTarget = join(
148
- targetDir,
149
- 'apps',
150
- 'backend',
151
- 'biome.json',
152
- );
153
-
154
- if (existsSync(backendBiomeTemplate)) {
155
- copyFileSync(backendBiomeTemplate, backendBiomeTarget);
156
- }
157
- }
158
-
159
- spinner.stop('Project structure created!');
160
-
161
- const s = clack.spinner();
162
- s.start('Installing dependencies...');
163
-
164
- // Install dependencies
165
- const installProc = Bun.spawn(['bun', 'install'], {
166
- cwd: targetDir,
167
- stdout: 'inherit',
168
- stderr: 'inherit',
169
- });
170
- await installProc.exited;
171
-
172
- if (installProc.exitCode !== 0) {
173
- throw new Error('Failed to install dependencies');
174
- }
154
+ // Step 4: Setup template
155
+ await setupTemplate(projectType, targetDir);
175
156
 
176
- s.stop('Dependencies installed!');
157
+ // Step 5: Initialize git
158
+ await initializeGit(targetDir);
177
159
 
178
- // Step 4: Ask if user wants to initialize git
179
- const initGit = await clack.confirm({
180
- message: 'Initialize git repository?',
181
- initialValue: true,
182
- });
183
-
184
- if (clack.isCancel(initGit)) {
185
- clack.cancel('Operation cancelled');
186
- process.exit(0);
187
- }
188
-
189
- if (initGit) {
190
- // Check if git is available
191
- const gitCheckProc = Bun.spawn(['git', '--version'], {
192
- stdout: 'pipe',
193
- stderr: 'pipe',
194
- });
195
- await gitCheckProc.exited;
196
-
197
- if (gitCheckProc.exitCode !== 0) {
198
- clack.log.warn(
199
- 'Git is not installed or not available. Skipping git initialization.',
200
- );
201
- } else {
202
- const gitSpinner = clack.spinner();
203
- gitSpinner.start('Initializing git repository...');
204
-
205
- try {
206
- const gitInitProc = Bun.spawn(['git', 'init'], {
207
- cwd: targetDir,
208
- stdout: 'pipe',
209
- stderr: 'pipe',
210
- });
211
- await gitInitProc.exited;
212
-
213
- if (gitInitProc.exitCode === 0) {
214
- gitSpinner.stop('Git repository initialized');
215
-
216
- // Make initial commit
217
- gitSpinner.start('Creating initial commit...');
218
-
219
- const gitAddProc = Bun.spawn(['git', 'add', '.'], {
220
- cwd: targetDir,
221
- stdout: 'pipe',
222
- stderr: 'pipe',
223
- });
224
- await gitAddProc.exited;
225
-
226
- if (gitAddProc.exitCode === 0) {
227
- const gitCommitProc = Bun.spawn(
228
- ['git', 'commit', '-m', 'Initial commit'],
229
- {
230
- cwd: targetDir,
231
- stdout: 'pipe',
232
- stderr: 'pipe',
233
- },
234
- );
235
- await gitCommitProc.exited;
236
-
237
- if (gitCommitProc.exitCode === 0) {
238
- gitSpinner.stop('Initial commit created');
239
- } else {
240
- gitSpinner.stop('Git initialized (commit failed)');
241
- clack.log.warn(
242
- 'Failed to create initial commit. You can commit manually later.',
243
- );
244
- }
245
- } else {
246
- gitSpinner.stop('Git initialized (add failed)');
247
- clack.log.warn(
248
- 'Failed to stage files. You can add them manually later.',
249
- );
250
- }
251
- } else {
252
- gitSpinner.stop('Failed to initialize git');
253
- clack.log.warn(
254
- 'Git initialization failed. You can initialize manually later.',
255
- );
256
- }
257
- } catch {
258
- gitSpinner.stop('Git initialization failed');
259
- clack.log.warn(
260
- 'An error occurred during git initialization. You can initialize manually later.',
261
- );
262
- }
263
- }
264
- }
265
-
266
- // Prepare next steps message
267
- const projectTypeLabel =
268
- projectType === 'monorepo' ? 'monorepo' : 'backend';
269
- const nextSteps =
270
- projectType === 'monorepo'
271
- ? ` cd ${String(projectName)}
272
- bun run dev:backend # Start backend on http://localhost:3000
273
- bun run dev:frontend # Start frontend on http://localhost:5173`
274
- : ` cd ${String(projectName)}
275
- bun run dev # Start backend on http://localhost:3000`;
276
-
277
- clack.outro(`
278
- ✨ Success! Your ElysiaJS ${projectTypeLabel} project is ready.
279
-
280
- 📁 Project created at: ${targetDir}
281
-
282
- 🚀 Next steps:
283
- ${nextSteps}
284
-
285
- 📚 Check out the README.md for more information.
286
-
287
- Happy coding! 🎉
288
- `);
160
+ // Step 6: Show success message
161
+ clack.outro(getNextStepsMessage(projectType, projectName, targetDir));
289
162
  } catch (error) {
290
- spinner.stop('Failed to create project');
291
163
  clack.cancel(
292
164
  error instanceof Error ? error.message : 'Unknown error occurred',
293
165
  );