create-ej-app 20.0.0 → 24.0.0
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/cli.js +34 -36
- package/cli.ts +14 -2
- package/package.json +9 -7
- package/template/api/gitignore +4 -0
- package/template/api/package.json +7 -7
- package/template/nextjs/gitignore +7 -0
- package/template/nextjs/package.json +33 -34
- package/template/nextjs/src/components/ui/button.tsx +2 -2
- package/template/nextjs/src/components/ui/field.tsx +1 -1
- package/template/nextjs/src/db/index.ts +44 -1
- package/template/nextjs/src/emails/components/email-layout.tsx +1 -1
- package/template/nextjs/src/emails/magic-link.tsx +1 -1
- package/template/nextjs/src/emails/test.tsx +1 -1
- package/template/nextjs/src/emails/welcome.tsx +1 -1
- package/template/nextjs/terraform/gitignore +4 -0
- package/tsconfig.json +5 -3
package/cli.js
CHANGED
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
-
});
|
|
11
|
-
};
|
|
12
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
-
};
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
const child_process_1 = require("child_process");
|
|
17
|
-
const commander_1 = require("commander");
|
|
18
|
-
const enquirer_1 = require("enquirer");
|
|
19
|
-
const fs_1 = __importDefault(require("fs"));
|
|
20
|
-
const path_1 = __importDefault(require("path"));
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import enquirer from 'enquirer';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
21
7
|
const detectPackageManager = () => {
|
|
22
8
|
const userAgent = process.env.npm_config_user_agent;
|
|
23
9
|
if (userAgent) {
|
|
@@ -54,8 +40,8 @@ const getDevCommand = (packageManager) => {
|
|
|
54
40
|
return 'npm run dev';
|
|
55
41
|
}
|
|
56
42
|
};
|
|
57
|
-
|
|
58
|
-
const response =
|
|
43
|
+
program.action(async () => {
|
|
44
|
+
const response = await enquirer.prompt([
|
|
59
45
|
{
|
|
60
46
|
type: 'input',
|
|
61
47
|
name: 'projectName',
|
|
@@ -91,29 +77,41 @@ commander_1.program.action(() => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
91
77
|
}
|
|
92
78
|
]);
|
|
93
79
|
const { projectName, description, template, git } = response;
|
|
94
|
-
const projectDir =
|
|
95
|
-
if (
|
|
80
|
+
const projectDir = path.join(process.cwd(), projectName);
|
|
81
|
+
if (fs.existsSync(projectDir)) {
|
|
96
82
|
console.error(`Error: Directory '${projectName}' already exists.`);
|
|
97
83
|
process.exit(1);
|
|
98
84
|
}
|
|
99
|
-
|
|
100
|
-
const templateDir =
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
85
|
+
fs.mkdirSync(projectDir);
|
|
86
|
+
const templateDir = path.join(__dirname, 'template', template);
|
|
87
|
+
fs.cpSync(templateDir, projectDir, { recursive: true });
|
|
88
|
+
const renameGitignore = (dir) => {
|
|
89
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
90
|
+
const fullPath = path.join(dir, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
renameGitignore(fullPath);
|
|
93
|
+
}
|
|
94
|
+
else if (entry.name === 'gitignore') {
|
|
95
|
+
fs.renameSync(fullPath, path.join(dir, '.gitignore'));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
renameGitignore(projectDir);
|
|
100
|
+
const packageJsonFile = path.join(projectDir, 'package.json');
|
|
101
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8'));
|
|
104
102
|
packageJson.name = projectName;
|
|
105
103
|
packageJson.description = description;
|
|
106
|
-
|
|
107
|
-
const envExampleFile =
|
|
108
|
-
const envExample =
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));
|
|
105
|
+
const envExampleFile = path.join(projectDir, '.env.example');
|
|
106
|
+
const envExample = fs.readFileSync(envExampleFile, 'utf-8');
|
|
107
|
+
fs.writeFileSync(path.join(projectDir, '.env'), envExample);
|
|
108
|
+
fs.writeFileSync(path.join(projectDir, 'README.md'), `# ${projectName}
|
|
111
109
|
|
|
112
110
|
${description}
|
|
113
111
|
`);
|
|
114
112
|
if (git === 'yes') {
|
|
115
113
|
try {
|
|
116
|
-
|
|
114
|
+
execSync('git init', { cwd: projectDir, stdio: 'inherit' });
|
|
117
115
|
}
|
|
118
116
|
catch (err) {
|
|
119
117
|
console.error('Error initializing git repository:', err);
|
|
@@ -139,5 +137,5 @@ ${devCommand}
|
|
|
139
137
|
|
|
140
138
|
Happy coding! 🚀
|
|
141
139
|
`);
|
|
142
|
-
})
|
|
143
|
-
|
|
140
|
+
});
|
|
141
|
+
program.parse(process.argv);
|
package/cli.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { execSync } from 'child_process'
|
|
4
4
|
import { program } from 'commander'
|
|
5
|
-
import
|
|
5
|
+
import enquirer from 'enquirer'
|
|
6
6
|
import fs from 'fs'
|
|
7
7
|
import path from 'path'
|
|
8
8
|
|
|
@@ -52,7 +52,7 @@ const getDevCommand = (packageManager: string) => {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
program.action(async () => {
|
|
55
|
-
const response: Response = await prompt([
|
|
55
|
+
const response: Response = await enquirer.prompt([
|
|
56
56
|
{
|
|
57
57
|
type: 'input',
|
|
58
58
|
name: 'projectName',
|
|
@@ -103,6 +103,18 @@ program.action(async () => {
|
|
|
103
103
|
const templateDir = path.join(__dirname, 'template', template)
|
|
104
104
|
fs.cpSync(templateDir, projectDir, { recursive: true })
|
|
105
105
|
|
|
106
|
+
const renameGitignore = (dir: string) => {
|
|
107
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
108
|
+
const fullPath = path.join(dir, entry.name)
|
|
109
|
+
if (entry.isDirectory()) {
|
|
110
|
+
renameGitignore(fullPath)
|
|
111
|
+
} else if (entry.name === 'gitignore') {
|
|
112
|
+
fs.renameSync(fullPath, path.join(dir, '.gitignore'))
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
renameGitignore(projectDir)
|
|
117
|
+
|
|
106
118
|
const packageJsonFile = path.join(projectDir, 'package.json')
|
|
107
119
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8'))
|
|
108
120
|
packageJson.name = projectName
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ej-app",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "24.0.0",
|
|
4
4
|
"description": "Bootstrap a next.js app with my preferred setup",
|
|
5
5
|
"main": "cli.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"build": "tsc",
|
|
8
9
|
"start": "node ."
|
|
@@ -14,10 +15,11 @@
|
|
|
14
15
|
"create-ej-app",
|
|
15
16
|
"nextjs",
|
|
16
17
|
"typescript",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
18
|
+
"better-auth",
|
|
19
|
+
"drizzle",
|
|
19
20
|
"tailwind",
|
|
20
|
-
"shadcn"
|
|
21
|
+
"shadcn",
|
|
22
|
+
"starter"
|
|
21
23
|
],
|
|
22
24
|
"author": "Ekansh Jain",
|
|
23
25
|
"license": "MIT",
|
|
@@ -30,11 +32,11 @@
|
|
|
30
32
|
},
|
|
31
33
|
"homepage": "https://github.com/ejekanshjain/create-ej-app#readme",
|
|
32
34
|
"dependencies": {
|
|
33
|
-
"commander": "^
|
|
35
|
+
"commander": "^15.0.0",
|
|
34
36
|
"enquirer": "^2.4.1"
|
|
35
37
|
},
|
|
36
38
|
"devDependencies": {
|
|
37
|
-
"@types/node": "^25.
|
|
38
|
-
"typescript": "^
|
|
39
|
+
"@types/node": "^25.9.1",
|
|
40
|
+
"typescript": "^6.0.3"
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -14,18 +14,18 @@
|
|
|
14
14
|
"db:seed": "bun ./src/scripts/seed.ts"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@elysiajs/openapi": "1.4.
|
|
17
|
+
"@elysiajs/openapi": "1.4.15",
|
|
18
18
|
"@t3-oss/env-core": "0.13.11",
|
|
19
|
-
"drizzle-orm": "0.45.
|
|
19
|
+
"drizzle-orm": "0.45.2",
|
|
20
20
|
"elysia": "1.4.28",
|
|
21
|
-
"zod": "4.3
|
|
21
|
+
"zod": "4.4.3"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@types/bun": "1.3.
|
|
25
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
24
|
+
"@types/bun": "1.3.14",
|
|
25
|
+
"@typescript/native-preview": "7.0.0-dev.20260603.1",
|
|
26
26
|
"drizzle-kit": "0.31.10",
|
|
27
|
-
"postgres": "3.4.
|
|
28
|
-
"prettier": "3.8.
|
|
27
|
+
"postgres": "3.4.9",
|
|
28
|
+
"prettier": "3.8.3",
|
|
29
29
|
"prettier-plugin-organize-imports": "4.3.0"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -22,56 +22,55 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@ejekanshjain/simple-email-validator": "1.0.4",
|
|
25
|
-
"@hookform/resolvers": "5.
|
|
26
|
-
"@next/third-parties": "16.2.
|
|
25
|
+
"@hookform/resolvers": "5.4.0",
|
|
26
|
+
"@next/third-parties": "16.2.7",
|
|
27
27
|
"@opentelemetry/api": "1.9.1",
|
|
28
|
-
"@opentelemetry/sdk-trace-base": "2.
|
|
28
|
+
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
29
29
|
"@paralleldrive/cuid2": "3.3.0",
|
|
30
|
-
"@react-email/
|
|
31
|
-
"@react-email/render": "2.0.4",
|
|
30
|
+
"@react-email/render": "2.0.8",
|
|
32
31
|
"@t3-oss/env-nextjs": "0.13.11",
|
|
33
|
-
"@tanstack/react-query": "5.
|
|
34
|
-
"@vercel/otel": "2.1.
|
|
35
|
-
"better-auth": "1.
|
|
32
|
+
"@tanstack/react-query": "5.101.0",
|
|
33
|
+
"@vercel/otel": "2.1.2",
|
|
34
|
+
"better-auth": "1.6.14",
|
|
36
35
|
"class-variance-authority": "0.7.1",
|
|
37
36
|
"clsx": "2.1.1",
|
|
38
|
-
"drizzle-orm": "0.45.
|
|
39
|
-
"lucide-react": "1.
|
|
40
|
-
"next": "16.2.
|
|
41
|
-
"next-safe-action": "8.
|
|
42
|
-
"next-themes": "0.4.6",
|
|
37
|
+
"drizzle-orm": "0.45.2",
|
|
38
|
+
"lucide-react": "1.17.0",
|
|
39
|
+
"next": "16.2.7",
|
|
40
|
+
"next-safe-action": "8.5.3",
|
|
41
|
+
"next-themes": "^0.4.6",
|
|
43
42
|
"nextjs-toploader": "3.9.17",
|
|
44
|
-
"nodemailer": "8.0.
|
|
45
|
-
"postgres": "3.4.
|
|
43
|
+
"nodemailer": "8.0.10",
|
|
44
|
+
"postgres": "3.4.9",
|
|
46
45
|
"radix-ui": "1.4.3",
|
|
47
|
-
"react": "19.2.
|
|
48
|
-
"react-dom": "19.2.
|
|
49
|
-
"react-hook-form": "7.
|
|
50
|
-
"sonner": "2.0.7",
|
|
51
|
-
"tailwind-merge": "3.
|
|
46
|
+
"react": "19.2.7",
|
|
47
|
+
"react-dom": "19.2.7",
|
|
48
|
+
"react-hook-form": "7.77.0",
|
|
49
|
+
"sonner": "^2.0.7",
|
|
50
|
+
"tailwind-merge": "3.6.0",
|
|
52
51
|
"tw-animate-css": "1.4.0",
|
|
53
|
-
"workflow": "4.
|
|
54
|
-
"zod": "4.3
|
|
52
|
+
"workflow": "4.3.1",
|
|
53
|
+
"zod": "4.4.3"
|
|
55
54
|
},
|
|
56
55
|
"devDependencies": {
|
|
57
|
-
"@next/bundle-analyzer": "16.2.
|
|
56
|
+
"@next/bundle-analyzer": "16.2.7",
|
|
58
57
|
"@react-email/preview-server": "5.2.10",
|
|
59
|
-
"@tailwindcss/postcss": "4.
|
|
60
|
-
"@types/bun": "1.3.
|
|
61
|
-
"@types/nodemailer": "
|
|
62
|
-
"@types/react": "19.2.
|
|
58
|
+
"@tailwindcss/postcss": "4.3.0",
|
|
59
|
+
"@types/bun": "1.3.14",
|
|
60
|
+
"@types/nodemailer": "8.0.0",
|
|
61
|
+
"@types/react": "19.2.16",
|
|
63
62
|
"@types/react-dom": "19.2.3",
|
|
64
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
63
|
+
"@typescript/native-preview": "7.0.0-dev.20260603.1",
|
|
65
64
|
"babel-plugin-react-compiler": "1.0.0",
|
|
66
65
|
"drizzle-kit": "0.31.10",
|
|
67
66
|
"eslint": "9.39.4",
|
|
68
|
-
"eslint-config-next": "16.2.
|
|
69
|
-
"prettier": "3.8.
|
|
67
|
+
"eslint-config-next": "16.2.7",
|
|
68
|
+
"prettier": "3.8.3",
|
|
70
69
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
71
|
-
"prettier-plugin-tailwindcss": "0.
|
|
72
|
-
"react-email": "5.
|
|
73
|
-
"tailwindcss": "4.
|
|
74
|
-
"typescript": "6.0.
|
|
70
|
+
"prettier-plugin-tailwindcss": "0.8.0",
|
|
71
|
+
"react-email": "6.5.0",
|
|
72
|
+
"tailwindcss": "4.3.0",
|
|
73
|
+
"typescript": "6.0.3"
|
|
75
74
|
},
|
|
76
75
|
"trustedDependencies": [
|
|
77
76
|
"@nestjs/core",
|
|
@@ -5,7 +5,7 @@ import * as React from 'react'
|
|
|
5
5
|
import { cn } from '~/lib/cn'
|
|
6
6
|
|
|
7
7
|
const buttonVariants = cva(
|
|
8
|
-
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
8
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
@@ -25,7 +25,7 @@ const buttonVariants = cva(
|
|
|
25
25
|
'h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
|
26
26
|
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
27
27
|
sm: 'h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5',
|
|
28
|
-
lg: 'h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-
|
|
28
|
+
lg: 'h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
|
29
29
|
icon: 'size-9',
|
|
30
30
|
'icon-xs':
|
|
31
31
|
"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
@@ -120,7 +120,7 @@ function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
120
120
|
<div
|
|
121
121
|
data-slot="field-label"
|
|
122
122
|
className={cn(
|
|
123
|
-
'flex w-fit items-center gap-2 text-sm
|
|
123
|
+
'flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50',
|
|
124
124
|
className
|
|
125
125
|
)}
|
|
126
126
|
{...props}
|
|
@@ -1,7 +1,50 @@
|
|
|
1
1
|
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
2
|
+
import postgres from 'postgres'
|
|
2
3
|
import { env } from '~/env'
|
|
3
4
|
import * as schema from './schema'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// In development, Next.js hot-reloading can re-evaluate modules frequently.
|
|
7
|
+
// Keep a single postgres-js client on `globalThis` to avoid opening multiple
|
|
8
|
+
// database connections during local development.
|
|
9
|
+
const globalForDb = globalThis as unknown as {
|
|
10
|
+
client?: ReturnType<typeof postgres>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Prefer the cached client (dev) and fall back to creating a new one.
|
|
14
|
+
// `max: 1` limits the pool size, and `prepare: false` disables prepared
|
|
15
|
+
// statements for compatibility with environments where prepared statements
|
|
16
|
+
// can be problematic.
|
|
17
|
+
const client =
|
|
18
|
+
globalForDb.client ??
|
|
19
|
+
postgres(env.DATABASE_URL, {
|
|
20
|
+
max: 1,
|
|
21
|
+
prepare: false
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Only cache the client outside production to avoid unexpected cross-request
|
|
25
|
+
// state retention in long-lived production processes.
|
|
26
|
+
if (env.APP_ENV !== 'production') globalForDb.client = client
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Drizzle ORM database client for PostgreSQL.
|
|
30
|
+
*
|
|
31
|
+
* Backed by a postgres-js client configured via `env.DATABASE_URL`, and wired
|
|
32
|
+
* up with schema + relations so you get typed query helpers.
|
|
33
|
+
*
|
|
34
|
+
* Use this instance throughout the app for all database operations.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const users = await db.query.usersTable.findMany()
|
|
38
|
+
* const newUser = await db.insert(usersTable).values({ email: 'user@example.com' })
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* await db.transaction(async (tx) => {
|
|
42
|
+
* await tx.insert(usersTable).values({ email: 'user@example.com' })
|
|
43
|
+
* })
|
|
44
|
+
*/
|
|
45
|
+
export const db = drizzle(client, {
|
|
6
46
|
schema: { ...schema }
|
|
7
47
|
})
|
|
48
|
+
|
|
49
|
+
/** The transaction client type passed into `db.transaction(async (tx) => ...)`. */
|
|
50
|
+
export type DbTx = Parameters<Parameters<(typeof db)['transaction']>[0]>[0]
|
package/tsconfig.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"outDir": "./",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"module": "
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"module": "Node16",
|
|
7
|
+
"moduleResolution": "Node16",
|
|
8
|
+
"types": ["node"],
|
|
7
9
|
"esModuleInterop": true,
|
|
8
10
|
"forceConsistentCasingInFileNames": true,
|
|
9
11
|
"strict": true,
|