create-ely 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -27
- package/package.json +14 -4
- package/scripts/postinstall.ts +43 -0
- package/src/constants.ts +20 -0
- package/src/git.ts +95 -0
- package/src/index.ts +123 -251
- package/src/template.ts +62 -0
- package/src/utils.ts +64 -0
- package/templates.zip +0 -0
- package/templates/monorepo/README.md +0 -75
- package/templates/monorepo/apps/backend/.cursor/mcp.json +0 -8
- package/templates/monorepo/apps/backend/.dockerignore +0 -60
- package/templates/monorepo/apps/backend/.env.example +0 -19
- package/templates/monorepo/apps/backend/.github/workflows/lint.yml +0 -31
- package/templates/monorepo/apps/backend/.github/workflows/tests.yml +0 -36
- package/templates/monorepo/apps/backend/AGENTS.md +0 -79
- package/templates/monorepo/apps/backend/CHANGELOG.md +0 -190
- package/templates/monorepo/apps/backend/CLAUDE.md +0 -149
- package/templates/monorepo/apps/backend/Dockerfile +0 -35
- package/templates/monorepo/apps/backend/LICENSE +0 -21
- package/templates/monorepo/apps/backend/README.md +0 -274
- package/templates/monorepo/apps/backend/biome.json +0 -58
- package/templates/monorepo/apps/backend/bun.lock +0 -303
- package/templates/monorepo/apps/backend/bunfig.toml +0 -4
- package/templates/monorepo/apps/backend/docker-compose.yml +0 -37
- package/templates/monorepo/apps/backend/drizzle.config.ts +0 -14
- package/templates/monorepo/apps/backend/package.json +0 -42
- package/templates/monorepo/apps/backend/src/common/config.ts +0 -29
- package/templates/monorepo/apps/backend/src/common/logger.ts +0 -18
- package/templates/monorepo/apps/backend/src/db/index.ts +0 -31
- package/templates/monorepo/apps/backend/src/db/migrations/20251111132328_curly_spectrum.sql +0 -8
- package/templates/monorepo/apps/backend/src/db/migrations/meta/20251111132328_snapshot.json +0 -70
- package/templates/monorepo/apps/backend/src/db/migrations/meta/_journal.json +0 -13
- package/templates/monorepo/apps/backend/src/db/schema/users.ts +0 -39
- package/templates/monorepo/apps/backend/src/main.ts +0 -67
- package/templates/monorepo/apps/backend/src/middleware/error-handler.ts +0 -36
- package/templates/monorepo/apps/backend/src/modules/users/index.ts +0 -61
- package/templates/monorepo/apps/backend/src/modules/users/model.ts +0 -48
- package/templates/monorepo/apps/backend/src/modules/users/service.ts +0 -46
- package/templates/monorepo/apps/backend/src/tests/users.test.ts +0 -102
- package/templates/monorepo/apps/backend/src/util/graceful-shutdown.ts +0 -37
- package/templates/monorepo/apps/backend/tsconfig.json +0 -35
- package/templates/monorepo/apps/backend-biome.json.template +0 -14
- package/templates/monorepo/apps/frontend/README.md +0 -59
- package/templates/monorepo/apps/frontend/biome.json +0 -16
- package/templates/monorepo/apps/frontend/components.json +0 -21
- package/templates/monorepo/apps/frontend/index.html +0 -15
- package/templates/monorepo/apps/frontend/package.json +0 -48
- package/templates/monorepo/apps/frontend/public/favicon.ico +0 -0
- package/templates/monorepo/apps/frontend/src/assets/fonts/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/assets/images/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/features/layout/Header.tsx +0 -73
- package/templates/monorepo/apps/frontend/src/main.tsx +0 -36
- package/templates/monorepo/apps/frontend/src/routeTree.gen.ts +0 -95
- package/templates/monorepo/apps/frontend/src/routes/__root.tsx +0 -25
- package/templates/monorepo/apps/frontend/src/routes/index.tsx +0 -34
- package/templates/monorepo/apps/frontend/src/routes/users/index.tsx +0 -79
- package/templates/monorepo/apps/frontend/src/routes/users/new.tsx +0 -148
- package/templates/monorepo/apps/frontend/src/shared/api/client.ts +0 -6
- package/templates/monorepo/apps/frontend/src/shared/components/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/shared/constants/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/shared/hooks/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/shared/types/.gitkeep +0 -0
- package/templates/monorepo/apps/frontend/src/shared/utils/utils.ts +0 -6
- package/templates/monorepo/apps/frontend/src/styles.css +0 -138
- package/templates/monorepo/apps/frontend/src/vite-env.d.ts +0 -13
- package/templates/monorepo/apps/frontend/tsconfig.json +0 -27
- package/templates/monorepo/apps/frontend/vite.config.ts +0 -27
- package/templates/monorepo/biome.json +0 -65
- package/templates/monorepo/bun.lock +0 -1044
- package/templates/monorepo/package.json +0 -13
package/README.md
CHANGED
|
@@ -1,49 +1,58 @@
|
|
|
1
1
|
# create-ely
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/truehazker/create-ely/actions/workflows/lint.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/create-ely)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
[](https://bun.sh)
|
|
8
|
+
[](https://elysiajs.com)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
[](https://www.postgresql.org/)
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
The fastest way to scaffold production-ready [ElysiaJS](https://elysiajs.com) projects with [Bun](https://bun.sh).
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
Create a new project:
|
|
8
19
|
|
|
9
20
|
```bash
|
|
10
|
-
|
|
21
|
+
bun create ely
|
|
11
22
|
```
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
Or with a project name:
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
```bash
|
|
27
|
+
bun create ely my-project
|
|
28
|
+
```
|
|
17
29
|
|
|
18
|
-
|
|
30
|
+
You'll be prompted to choose:
|
|
19
31
|
|
|
20
|
-
|
|
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
|
-
|
|
35
|
+
## What's Included
|
|
23
36
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
+
**Monorepo Template:**
|
|
34
46
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
47
|
+
- Everything from Backend template
|
|
48
|
+
- React frontend with TanStack Router and Vite
|
|
49
|
+
- Bun workspaces for seamless monorepo management
|
|
38
50
|
|
|
39
|
-
##
|
|
51
|
+
## Contributing
|
|
40
52
|
|
|
41
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.3",
|
|
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": [
|
|
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
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -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
|
-
|
|
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:
|
|
129
|
+
value: TEMPLATE_TYPES.BACKEND,
|
|
28
130
|
label: 'Backend only',
|
|
29
131
|
hint: 'ElysiaJS API with PostgreSQL',
|
|
30
132
|
},
|
|
31
133
|
{
|
|
32
|
-
value:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
157
|
+
// Step 5: Initialize git
|
|
158
|
+
await initializeGit(targetDir);
|
|
177
159
|
|
|
178
|
-
// Step
|
|
179
|
-
|
|
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
|
);
|