create-ely 0.1.1 → 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 +126 -140
- 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/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/.env.example +0 -1
- 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,18 +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
|
|
|
124
|
+
// Step 1: Select project type
|
|
22
125
|
const projectType = await clack.select({
|
|
23
126
|
message: 'What would you like to create?',
|
|
24
127
|
options: [
|
|
25
128
|
{
|
|
26
|
-
value:
|
|
129
|
+
value: TEMPLATE_TYPES.BACKEND,
|
|
27
130
|
label: 'Backend only',
|
|
28
131
|
hint: 'ElysiaJS API with PostgreSQL',
|
|
29
132
|
},
|
|
30
133
|
{
|
|
31
|
-
value:
|
|
134
|
+
value: TEMPLATE_TYPES.MONOREPO,
|
|
32
135
|
label: 'Monorepo',
|
|
33
136
|
hint: 'Backend + Frontend (React + TanStack Router)',
|
|
34
137
|
},
|
|
@@ -40,140 +143,23 @@ async function main() {
|
|
|
40
143
|
process.exit(0);
|
|
41
144
|
}
|
|
42
145
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (projectNameArg && /^[a-z0-9-]+$/.test(projectNameArg)) {
|
|
47
|
-
projectName = projectNameArg;
|
|
48
|
-
} else {
|
|
49
|
-
// Otherwise, prompt for it
|
|
50
|
-
projectName = await clack.text({
|
|
51
|
-
message: 'Project name:',
|
|
52
|
-
placeholder: 'my-elysia-app',
|
|
53
|
-
validate: (value) => {
|
|
54
|
-
if (!value) return 'Project name is required';
|
|
55
|
-
if (!/^[a-z0-9-]+$/.test(value))
|
|
56
|
-
return 'Project name must contain only lowercase letters, numbers, and hyphens';
|
|
57
|
-
return undefined;
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (clack.isCancel(projectName)) {
|
|
62
|
-
clack.cancel('Operation cancelled');
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const targetDir = join(process.cwd(), projectName as string);
|
|
68
|
-
|
|
69
|
-
if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
|
|
70
|
-
const shouldOverwrite = await clack.confirm({
|
|
71
|
-
message: `Directory "${String(projectName)}" already exists and is not empty. Overwrite it?`,
|
|
72
|
-
initialValue: false,
|
|
73
|
-
});
|
|
146
|
+
// Step 2: Get project name
|
|
147
|
+
const projectName = await getProjectName(args);
|
|
148
|
+
const targetDir = join(process.cwd(), projectName);
|
|
74
149
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
process.exit(0);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
rmSync(targetDir, { recursive: true, force: true });
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const spinner = clack.spinner();
|
|
84
|
-
spinner.start('Creating project...');
|
|
150
|
+
// Step 3: Check if directory exists and handle it
|
|
151
|
+
await handleExistingDirectory(targetDir, projectName);
|
|
85
152
|
|
|
86
153
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
'..',
|
|
90
|
-
'templates',
|
|
91
|
-
projectType as string,
|
|
92
|
-
);
|
|
154
|
+
// Step 4: Setup template
|
|
155
|
+
await setupTemplate(projectType, targetDir);
|
|
93
156
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
const stats = statSync(src);
|
|
97
|
-
|
|
98
|
-
if (stats.isDirectory()) {
|
|
99
|
-
if (!existsSync(dest)) {
|
|
100
|
-
mkdirSync(dest, { recursive: true });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
for (const entry of readdirSync(src)) {
|
|
104
|
-
if (exclude.includes(entry) || entry.endsWith('.template')) continue;
|
|
105
|
-
|
|
106
|
-
const srcPath = join(src, entry);
|
|
107
|
-
const destPath = join(dest, entry);
|
|
108
|
-
copyRecursive(srcPath, destPath, exclude);
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
const destDir = dirname(dest);
|
|
112
|
-
if (!existsSync(destDir)) {
|
|
113
|
-
mkdirSync(destDir, { recursive: true });
|
|
114
|
-
}
|
|
115
|
-
copyFileSync(src, dest);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
copyRecursive(templateDir, targetDir, ['node_modules', '.git']);
|
|
120
|
-
|
|
121
|
-
// Handle template files: copy them to their intended locations
|
|
122
|
-
if (projectType === 'monorepo') {
|
|
123
|
-
const backendBiomeTemplate = join(
|
|
124
|
-
templateDir,
|
|
125
|
-
'apps',
|
|
126
|
-
'backend-biome.json.template',
|
|
127
|
-
);
|
|
128
|
-
const backendBiomeTarget = join(
|
|
129
|
-
targetDir,
|
|
130
|
-
'apps',
|
|
131
|
-
'backend',
|
|
132
|
-
'biome.json',
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
if (existsSync(backendBiomeTemplate)) {
|
|
136
|
-
copyFileSync(backendBiomeTemplate, backendBiomeTarget);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
spinner.stop('Project created successfully!');
|
|
141
|
-
|
|
142
|
-
const s = clack.spinner();
|
|
143
|
-
s.start('Installing dependencies...');
|
|
144
|
-
|
|
145
|
-
// Install dependencies
|
|
146
|
-
const installProc = Bun.spawn(['bun', 'install'], {
|
|
147
|
-
cwd: targetDir,
|
|
148
|
-
stdout: 'inherit',
|
|
149
|
-
stderr: 'inherit',
|
|
150
|
-
});
|
|
151
|
-
await installProc.exited;
|
|
152
|
-
|
|
153
|
-
if (installProc.exitCode !== 0) {
|
|
154
|
-
throw new Error('Failed to install dependencies');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
s.stop('Dependencies installed!');
|
|
158
|
-
|
|
159
|
-
const nextSteps =
|
|
160
|
-
projectType === 'monorepo'
|
|
161
|
-
? ` cd ${String(projectName)}
|
|
162
|
-
bun run dev:backend # Start backend on http://localhost:3000
|
|
163
|
-
bun run dev:frontend # Start frontend on http://localhost:5173`
|
|
164
|
-
: ` cd ${String(projectName)}
|
|
165
|
-
bun run dev # Start backend on http://localhost:3000`;
|
|
166
|
-
|
|
167
|
-
clack.outro(`
|
|
168
|
-
Success! Your ElysiaJS project is ready.
|
|
169
|
-
|
|
170
|
-
Next steps:
|
|
171
|
-
${nextSteps}
|
|
157
|
+
// Step 5: Initialize git
|
|
158
|
+
await initializeGit(targetDir);
|
|
172
159
|
|
|
173
|
-
|
|
174
|
-
|
|
160
|
+
// Step 6: Show success message
|
|
161
|
+
clack.outro(getNextStepsMessage(projectType, projectName, targetDir));
|
|
175
162
|
} catch (error) {
|
|
176
|
-
spinner.stop('Failed to create project');
|
|
177
163
|
clack.cancel(
|
|
178
164
|
error instanceof Error ? error.message : 'Unknown error occurred',
|
|
179
165
|
);
|
package/src/template.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { copyFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import * as clack from '@clack/prompts';
|
|
4
|
+
import {
|
|
5
|
+
EXCLUDED_COPY_PATTERNS,
|
|
6
|
+
TEMPLATE_PATHS,
|
|
7
|
+
TEMPLATE_TYPES,
|
|
8
|
+
} from './constants.ts';
|
|
9
|
+
import { copyRecursive } from './utils.ts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sets up the project template by copying files and installing dependencies
|
|
13
|
+
* @param projectType - The type of project template to use ('backend' or 'monorepo')
|
|
14
|
+
* @param targetDir - The directory to create the project in
|
|
15
|
+
* @returns Promise that resolves when template setup is complete
|
|
16
|
+
*/
|
|
17
|
+
export async function setupTemplate(
|
|
18
|
+
projectType: string,
|
|
19
|
+
targetDir: string,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const spinner = clack.spinner();
|
|
22
|
+
spinner.start('Creating project...');
|
|
23
|
+
|
|
24
|
+
const templateDir = join(import.meta.dir, '..', 'templates', projectType);
|
|
25
|
+
|
|
26
|
+
copyRecursive(templateDir, targetDir, EXCLUDED_COPY_PATTERNS);
|
|
27
|
+
|
|
28
|
+
// Handle template files: copy them to their intended locations
|
|
29
|
+
if (projectType === TEMPLATE_TYPES.MONOREPO) {
|
|
30
|
+
const backendBiomeTemplate = join(
|
|
31
|
+
templateDir,
|
|
32
|
+
TEMPLATE_PATHS.BACKEND_BIOME_TEMPLATE,
|
|
33
|
+
);
|
|
34
|
+
const backendBiomeTarget = join(
|
|
35
|
+
targetDir,
|
|
36
|
+
TEMPLATE_PATHS.BACKEND_BIOME_TARGET,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (existsSync(backendBiomeTemplate)) {
|
|
40
|
+
copyFileSync(backendBiomeTemplate, backendBiomeTarget);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
spinner.stop('Project structure created!');
|
|
45
|
+
|
|
46
|
+
const s = clack.spinner();
|
|
47
|
+
s.start('Installing dependencies...');
|
|
48
|
+
|
|
49
|
+
// Install dependencies
|
|
50
|
+
const installProc = Bun.spawn(['bun', 'install'], {
|
|
51
|
+
cwd: targetDir,
|
|
52
|
+
stdout: 'inherit',
|
|
53
|
+
stderr: 'inherit',
|
|
54
|
+
});
|
|
55
|
+
await installProc.exited;
|
|
56
|
+
|
|
57
|
+
if (installProc.exitCode !== 0) {
|
|
58
|
+
throw new Error('Failed to install dependencies');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
s.stop('Dependencies installed!');
|
|
62
|
+
}
|