create-ej-app 30.0.0 → 31.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/README.md +21 -1
- package/cli.js +146 -33
- package/cli.ts +190 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,8 @@ You will be prompted for:
|
|
|
59
59
|
- Project name
|
|
60
60
|
- Description
|
|
61
61
|
- Template (`nextjs`, `saas-starter`, or `api`)
|
|
62
|
-
- Whether to initialize git
|
|
62
|
+
- Whether to initialize git. If yes, the CLI also makes the initial commit, as long as `.env` is ignored.
|
|
63
|
+
- Whether to update packages to their latest versions. With Bun it runs `bun update -i`, so you pick which packages to update.
|
|
63
64
|
|
|
64
65
|
Example:
|
|
65
66
|
|
|
@@ -68,8 +69,27 @@ Example:
|
|
|
68
69
|
? Enter a description for the project: Production-ready Next.js app
|
|
69
70
|
? Select a template: nextjs
|
|
70
71
|
? Initialize a git repository? yes
|
|
72
|
+
? Update packages to their latest versions? The template can lag behind. yes
|
|
71
73
|
```
|
|
72
74
|
|
|
75
|
+
### Without Prompts
|
|
76
|
+
|
|
77
|
+
Pass the answers as flags to skip the prompts, for example in scripts, CI or coding agents. Any answer you leave out is still asked:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bunx create-ej-app@latest --name my-app --description "Production-ready Next.js app" --template nextjs --git --no-update
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
| Flag | Answers |
|
|
84
|
+
| -------------------------------- | --------------------------------------------- |
|
|
85
|
+
| `-n, --name <name>` | Project name, also the folder name |
|
|
86
|
+
| `-d, --description <text>` | Description |
|
|
87
|
+
| `-t, --template <template>` | Template: `nextjs`, `saas-starter`, or `api` |
|
|
88
|
+
| `--git` or `--no-git` | Whether to initialize git and commit |
|
|
89
|
+
| `--update` or `--no-update` | Whether to update packages to latest versions |
|
|
90
|
+
|
|
91
|
+
Without a terminal, the CLI can't ask, so it exits and names the flags you still need to pass. `--update` without a terminal updates every package to its latest version, which can include major versions: run the type-check and tests afterwards.
|
|
92
|
+
|
|
73
93
|
---
|
|
74
94
|
|
|
75
95
|
## What you get
|
package/cli.js
CHANGED
|
@@ -48,54 +48,149 @@ const getInstallCommand = (packageManager) => {
|
|
|
48
48
|
return 'npm i';
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Commands that update dependencies to their latest versions: `interactive`
|
|
53
|
+
* lets you pick packages in a terminal, `all` updates everything. Only Bun and
|
|
54
|
+
* pnpm have both; other package managers get instructions instead.
|
|
55
|
+
*/
|
|
56
|
+
const getUpdateCommands = (packageManager) => {
|
|
57
|
+
switch (packageManager) {
|
|
58
|
+
case 'bun':
|
|
59
|
+
return { interactive: 'bun update -i', all: 'bun update --latest' };
|
|
60
|
+
case 'pnpm':
|
|
61
|
+
return {
|
|
62
|
+
interactive: 'pnpm update -i --latest',
|
|
63
|
+
all: 'pnpm update --latest'
|
|
64
|
+
};
|
|
65
|
+
default:
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Commits the new project once. Skips the commit when .env is not ignored, so
|
|
71
|
+
* the generated secret never lands in history, and explains a missing git
|
|
72
|
+
* identity instead of failing silently.
|
|
73
|
+
*/
|
|
74
|
+
const commitEverything = (projectDir) => {
|
|
75
|
+
const run = (command) => execSync(command, { cwd: projectDir, stdio: 'pipe' });
|
|
76
|
+
try {
|
|
77
|
+
run('git check-ignore -q .env');
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
console.error('Skipped the initial commit: .env is not in .gitignore, and committing it would publish its secrets.');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
run('git add -A');
|
|
85
|
+
run('git commit -q -m "initial commit: bootstrap new project with create-ej-app"');
|
|
86
|
+
console.log('Created the initial commit.');
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
90
|
+
console.error(/user\.(name|email)|identity/i.test(message)
|
|
91
|
+
? "Skipped the initial commit: git doesn't know who you are. Set git config user.name and user.email, then run 'git add -A && git commit'."
|
|
92
|
+
: `The initial commit failed: ${message}`);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
51
95
|
const getRunCommand = (packageManager, script) => packageManager === 'npm'
|
|
52
96
|
? `npm run ${script}`
|
|
53
97
|
: `${packageManager} run ${script}`;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
98
|
+
// The name becomes a directory and the package.json name, so keep it to
|
|
99
|
+
// characters that are safe for both.
|
|
100
|
+
const PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,213}$/;
|
|
101
|
+
const PROJECT_NAME_HINT = 'Use lowercase letters, numbers, dots, hyphens, and underscores.';
|
|
102
|
+
const TEMPLATES = [
|
|
103
|
+
{
|
|
104
|
+
name: 'nextjs',
|
|
105
|
+
message: 'Full-stack Next.js 16 app with Better Auth, an admin panel, shadcn/ui, and Drizzle'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'saas-starter',
|
|
109
|
+
message: 'SaaS starter with organizations, Stripe, support tickets, Better Auth, shadcn/ui, and Drizzle'
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'api',
|
|
113
|
+
message: 'API server with Elysia, Drizzle, and TypeScript'
|
|
114
|
+
}
|
|
115
|
+
];
|
|
116
|
+
program
|
|
117
|
+
.name('create-ej-app')
|
|
118
|
+
.description('Create a new app from a template. Flags answer the prompts; anything left out is asked.')
|
|
119
|
+
.option('-n, --name <name>', 'project name, also the folder name')
|
|
120
|
+
.option('-d, --description <text>', 'project description')
|
|
121
|
+
.option('-t, --template <template>', `template: ${TEMPLATES.map(t => t.name).join(', ')}`)
|
|
122
|
+
.option('--git', 'initialize a git repository')
|
|
123
|
+
.option('--no-git', 'skip git initialization')
|
|
124
|
+
.option('--update', 'update packages to their latest versions after creating the project')
|
|
125
|
+
.option('--no-update', 'keep the template package versions');
|
|
126
|
+
program.action(async (options) => {
|
|
127
|
+
if (options.name !== undefined && !PROJECT_NAME_PATTERN.test(options.name)) {
|
|
128
|
+
console.error(`Invalid project name '${options.name}'. ${PROJECT_NAME_HINT}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
if (options.template !== undefined &&
|
|
132
|
+
!TEMPLATES.some(t => t.name === options.template)) {
|
|
133
|
+
console.error(`Unknown template '${options.template}'. Use one of: ${TEMPLATES.map(t => t.name).join(', ')}.`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
const questions = [
|
|
137
|
+
options.name === undefined && {
|
|
57
138
|
type: 'input',
|
|
58
139
|
name: 'projectName',
|
|
59
140
|
message: 'Enter the project name:',
|
|
60
141
|
initial: 'my-project',
|
|
61
|
-
|
|
62
|
-
// to characters that are safe for both.
|
|
63
|
-
validate: (value) => /^[a-z0-9][a-z0-9._-]{0,213}$/.test(value) ||
|
|
64
|
-
'Use lowercase letters, numbers, dots, hyphens, and underscores.'
|
|
142
|
+
validate: (value) => PROJECT_NAME_PATTERN.test(value) || PROJECT_NAME_HINT
|
|
65
143
|
},
|
|
66
|
-
{
|
|
144
|
+
options.description === undefined && {
|
|
67
145
|
type: 'input',
|
|
68
146
|
name: 'description',
|
|
69
147
|
message: 'Enter a description for the project:',
|
|
70
148
|
initial: 'A Legendary Project'
|
|
71
149
|
},
|
|
72
|
-
{
|
|
150
|
+
options.template === undefined && {
|
|
73
151
|
type: 'select',
|
|
74
152
|
name: 'template',
|
|
75
153
|
message: 'Select a template:',
|
|
76
|
-
choices:
|
|
77
|
-
{
|
|
78
|
-
name: 'nextjs',
|
|
79
|
-
message: 'Full-stack Next.js 16 app with Better Auth, an admin panel, shadcn/ui, and Drizzle'
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'saas-starter',
|
|
83
|
-
message: 'SaaS starter with organizations, Stripe, support tickets, Better Auth, shadcn/ui, and Drizzle'
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'api',
|
|
87
|
-
message: 'API server with Elysia, Drizzle, and TypeScript'
|
|
88
|
-
}
|
|
89
|
-
]
|
|
154
|
+
choices: TEMPLATES
|
|
90
155
|
},
|
|
91
|
-
{
|
|
156
|
+
options.git === undefined && {
|
|
92
157
|
type: 'select',
|
|
93
158
|
name: 'git',
|
|
94
159
|
message: 'Initialize a git repository?',
|
|
95
160
|
choices: ['yes', 'no']
|
|
161
|
+
},
|
|
162
|
+
options.update === undefined && {
|
|
163
|
+
type: 'select',
|
|
164
|
+
name: 'update',
|
|
165
|
+
message: 'Update packages to their latest versions? The template can lag behind.',
|
|
166
|
+
choices: ['yes', 'no']
|
|
96
167
|
}
|
|
97
|
-
]);
|
|
98
|
-
|
|
168
|
+
].filter(q => q !== false);
|
|
169
|
+
// Without a terminal the prompts would wait forever, for example when an
|
|
170
|
+
// agent or a script runs the CLI, so name the missing flags instead.
|
|
171
|
+
if (questions.length && !process.stdin.isTTY) {
|
|
172
|
+
const flags = {
|
|
173
|
+
projectName: '--name',
|
|
174
|
+
description: '--description',
|
|
175
|
+
template: '--template',
|
|
176
|
+
git: '--git or --no-git',
|
|
177
|
+
update: '--update or --no-update'
|
|
178
|
+
};
|
|
179
|
+
console.error(`No terminal to ask questions in. Pass ${questions.map(q => flags[q.name]).join(', ')}.`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
const answers = questions.length
|
|
183
|
+
? await enquirer.prompt(questions)
|
|
184
|
+
: {};
|
|
185
|
+
const projectName = options.name ?? answers.projectName;
|
|
186
|
+
const description = options.description ?? answers.description;
|
|
187
|
+
const template = options.template ?? answers.template;
|
|
188
|
+
const git = options.git === undefined ? answers.git : options.git ? 'yes' : 'no';
|
|
189
|
+
const update = options.update === undefined
|
|
190
|
+
? answers.update
|
|
191
|
+
: options.update
|
|
192
|
+
? 'yes'
|
|
193
|
+
: 'no';
|
|
99
194
|
const projectDir = path.join(process.cwd(), projectName);
|
|
100
195
|
if (fs.existsSync(projectDir)) {
|
|
101
196
|
console.error(`Directory '${projectName}' already exists. Choose another project name or remove the directory.`);
|
|
@@ -146,18 +241,38 @@ program.action(async () => {
|
|
|
146
241
|
if (!fs.existsSync(readmeFile)) {
|
|
147
242
|
fs.writeFileSync(readmeFile, `# ${projectName}\n\n${description}\n`);
|
|
148
243
|
}
|
|
244
|
+
const pm = detectPackageManager();
|
|
245
|
+
const installCommand = getInstallCommand(pm);
|
|
246
|
+
const devCommand = getRunCommand(pm, 'dev');
|
|
247
|
+
const dbPushCommand = getRunCommand(pm, 'db:push');
|
|
248
|
+
// Updating also installs, so the initial commit includes the lockfile
|
|
249
|
+
let installed = false;
|
|
250
|
+
if (update === 'yes') {
|
|
251
|
+
const commands = getUpdateCommands(pm);
|
|
252
|
+
if (!commands) {
|
|
253
|
+
console.log(`\nAutomatic updates support Bun and pnpm. After '${installCommand}', update packages with your package manager.`);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
const command = process.stdin.isTTY ? commands.interactive : commands.all;
|
|
257
|
+
console.log(`\nUpdating packages with '${command}'…`);
|
|
258
|
+
try {
|
|
259
|
+
execSync(command, { cwd: projectDir, stdio: 'inherit' });
|
|
260
|
+
installed = true;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
console.error(`Updating packages failed, so the template versions stay. Run '${command}' in the project to try again.`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
149
267
|
if (git === 'yes') {
|
|
150
268
|
try {
|
|
151
269
|
execSync('git init', { cwd: projectDir, stdio: 'inherit' });
|
|
270
|
+
commitEverything(projectDir);
|
|
152
271
|
}
|
|
153
272
|
catch (err) {
|
|
154
273
|
console.error('Error initializing git repository:', err);
|
|
155
274
|
}
|
|
156
275
|
}
|
|
157
|
-
const pm = detectPackageManager();
|
|
158
|
-
const installCommand = getInstallCommand(pm);
|
|
159
|
-
const devCommand = getRunCommand(pm, 'dev');
|
|
160
|
-
const dbPushCommand = getRunCommand(pm, 'db:push');
|
|
161
276
|
console.log(`
|
|
162
277
|
Created a new app called '${projectName}' 🎉
|
|
163
278
|
|
|
@@ -168,9 +283,7 @@ code ${projectName}
|
|
|
168
283
|
|
|
169
284
|
To get started, run these commands:
|
|
170
285
|
cd ${projectName}
|
|
171
|
-
|
|
172
|
-
${installCommand}
|
|
173
|
-
|
|
286
|
+
${installed ? '' : `\n${installCommand}\n`}
|
|
174
287
|
Fill in the values in .env, then create the database tables:
|
|
175
288
|
|
|
176
289
|
${dbPushCommand}
|
package/cli.ts
CHANGED
|
@@ -15,6 +15,7 @@ interface Response {
|
|
|
15
15
|
description: string
|
|
16
16
|
template: string
|
|
17
17
|
git: string
|
|
18
|
+
update: string
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -61,60 +62,192 @@ const getInstallCommand = (packageManager: string) => {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Commands that update dependencies to their latest versions: `interactive`
|
|
67
|
+
* lets you pick packages in a terminal, `all` updates everything. Only Bun and
|
|
68
|
+
* pnpm have both; other package managers get instructions instead.
|
|
69
|
+
*/
|
|
70
|
+
const getUpdateCommands = (packageManager: string) => {
|
|
71
|
+
switch (packageManager) {
|
|
72
|
+
case 'bun':
|
|
73
|
+
return { interactive: 'bun update -i', all: 'bun update --latest' }
|
|
74
|
+
case 'pnpm':
|
|
75
|
+
return {
|
|
76
|
+
interactive: 'pnpm update -i --latest',
|
|
77
|
+
all: 'pnpm update --latest'
|
|
78
|
+
}
|
|
79
|
+
default:
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Commits the new project once. Skips the commit when .env is not ignored, so
|
|
86
|
+
* the generated secret never lands in history, and explains a missing git
|
|
87
|
+
* identity instead of failing silently.
|
|
88
|
+
*/
|
|
89
|
+
const commitEverything = (projectDir: string) => {
|
|
90
|
+
const run = (command: string) =>
|
|
91
|
+
execSync(command, { cwd: projectDir, stdio: 'pipe' })
|
|
92
|
+
try {
|
|
93
|
+
run('git check-ignore -q .env')
|
|
94
|
+
} catch {
|
|
95
|
+
console.error(
|
|
96
|
+
'Skipped the initial commit: .env is not in .gitignore, and committing it would publish its secrets.'
|
|
97
|
+
)
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
run('git add -A')
|
|
102
|
+
run('git commit -q -m "initial commit: bootstrap new project with create-ej-app"')
|
|
103
|
+
console.log('Created the initial commit.')
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
106
|
+
console.error(
|
|
107
|
+
/user\.(name|email)|identity/i.test(message)
|
|
108
|
+
? "Skipped the initial commit: git doesn't know who you are. Set git config user.name and user.email, then run 'git add -A && git commit'."
|
|
109
|
+
: `The initial commit failed: ${message}`
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
64
114
|
const getRunCommand = (packageManager: string, script: string) =>
|
|
65
115
|
packageManager === 'npm'
|
|
66
116
|
? `npm run ${script}`
|
|
67
117
|
: `${packageManager} run ${script}`
|
|
68
118
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
119
|
+
// The name becomes a directory and the package.json name, so keep it to
|
|
120
|
+
// characters that are safe for both.
|
|
121
|
+
const PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,213}$/
|
|
122
|
+
const PROJECT_NAME_HINT =
|
|
123
|
+
'Use lowercase letters, numbers, dots, hyphens, and underscores.'
|
|
124
|
+
|
|
125
|
+
const TEMPLATES = [
|
|
126
|
+
{
|
|
127
|
+
name: 'nextjs',
|
|
128
|
+
message:
|
|
129
|
+
'Full-stack Next.js 16 app with Better Auth, an admin panel, shadcn/ui, and Drizzle'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'saas-starter',
|
|
133
|
+
message:
|
|
134
|
+
'SaaS starter with organizations, Stripe, support tickets, Better Auth, shadcn/ui, and Drizzle'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'api',
|
|
138
|
+
message: 'API server with Elysia, Drizzle, and TypeScript'
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
interface Options {
|
|
143
|
+
name?: string
|
|
144
|
+
description?: string
|
|
145
|
+
template?: string
|
|
146
|
+
git?: boolean
|
|
147
|
+
update?: boolean
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
program
|
|
151
|
+
.name('create-ej-app')
|
|
152
|
+
.description(
|
|
153
|
+
'Create a new app from a template. Flags answer the prompts; anything left out is asked.'
|
|
154
|
+
)
|
|
155
|
+
.option('-n, --name <name>', 'project name, also the folder name')
|
|
156
|
+
.option('-d, --description <text>', 'project description')
|
|
157
|
+
.option(
|
|
158
|
+
'-t, --template <template>',
|
|
159
|
+
`template: ${TEMPLATES.map(t => t.name).join(', ')}`
|
|
160
|
+
)
|
|
161
|
+
.option('--git', 'initialize a git repository')
|
|
162
|
+
.option('--no-git', 'skip git initialization')
|
|
163
|
+
.option(
|
|
164
|
+
'--update',
|
|
165
|
+
'update packages to their latest versions after creating the project'
|
|
166
|
+
)
|
|
167
|
+
.option('--no-update', 'keep the template package versions')
|
|
168
|
+
|
|
169
|
+
program.action(async (options: Options) => {
|
|
170
|
+
if (options.name !== undefined && !PROJECT_NAME_PATTERN.test(options.name)) {
|
|
171
|
+
console.error(`Invalid project name '${options.name}'. ${PROJECT_NAME_HINT}`)
|
|
172
|
+
process.exit(1)
|
|
173
|
+
}
|
|
174
|
+
if (
|
|
175
|
+
options.template !== undefined &&
|
|
176
|
+
!TEMPLATES.some(t => t.name === options.template)
|
|
177
|
+
) {
|
|
178
|
+
console.error(
|
|
179
|
+
`Unknown template '${options.template}'. Use one of: ${TEMPLATES.map(t => t.name).join(', ')}.`
|
|
180
|
+
)
|
|
181
|
+
process.exit(1)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const questions = [
|
|
185
|
+
options.name === undefined && {
|
|
72
186
|
type: 'input',
|
|
73
187
|
name: 'projectName',
|
|
74
188
|
message: 'Enter the project name:',
|
|
75
189
|
initial: 'my-project',
|
|
76
|
-
// The name becomes a directory and the package.json name, so keep it
|
|
77
|
-
// to characters that are safe for both.
|
|
78
190
|
validate: (value: string) =>
|
|
79
|
-
|
|
80
|
-
'Use lowercase letters, numbers, dots, hyphens, and underscores.'
|
|
191
|
+
PROJECT_NAME_PATTERN.test(value) || PROJECT_NAME_HINT
|
|
81
192
|
},
|
|
82
|
-
{
|
|
193
|
+
options.description === undefined && {
|
|
83
194
|
type: 'input',
|
|
84
195
|
name: 'description',
|
|
85
196
|
message: 'Enter a description for the project:',
|
|
86
197
|
initial: 'A Legendary Project'
|
|
87
198
|
},
|
|
88
|
-
{
|
|
199
|
+
options.template === undefined && {
|
|
89
200
|
type: 'select',
|
|
90
201
|
name: 'template',
|
|
91
202
|
message: 'Select a template:',
|
|
92
|
-
choices:
|
|
93
|
-
{
|
|
94
|
-
name: 'nextjs',
|
|
95
|
-
message:
|
|
96
|
-
'Full-stack Next.js 16 app with Better Auth, an admin panel, shadcn/ui, and Drizzle'
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: 'saas-starter',
|
|
100
|
-
message:
|
|
101
|
-
'SaaS starter with organizations, Stripe, support tickets, Better Auth, shadcn/ui, and Drizzle'
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
name: 'api',
|
|
105
|
-
message: 'API server with Elysia, Drizzle, and TypeScript'
|
|
106
|
-
}
|
|
107
|
-
]
|
|
203
|
+
choices: TEMPLATES
|
|
108
204
|
},
|
|
109
|
-
{
|
|
205
|
+
options.git === undefined && {
|
|
110
206
|
type: 'select',
|
|
111
207
|
name: 'git',
|
|
112
208
|
message: 'Initialize a git repository?',
|
|
113
209
|
choices: ['yes', 'no']
|
|
210
|
+
},
|
|
211
|
+
options.update === undefined && {
|
|
212
|
+
type: 'select',
|
|
213
|
+
name: 'update',
|
|
214
|
+
message:
|
|
215
|
+
'Update packages to their latest versions? The template can lag behind.',
|
|
216
|
+
choices: ['yes', 'no']
|
|
217
|
+
}
|
|
218
|
+
].filter(q => q !== false)
|
|
219
|
+
|
|
220
|
+
// Without a terminal the prompts would wait forever, for example when an
|
|
221
|
+
// agent or a script runs the CLI, so name the missing flags instead.
|
|
222
|
+
if (questions.length && !process.stdin.isTTY) {
|
|
223
|
+
const flags: Record<string, string> = {
|
|
224
|
+
projectName: '--name',
|
|
225
|
+
description: '--description',
|
|
226
|
+
template: '--template',
|
|
227
|
+
git: '--git or --no-git',
|
|
228
|
+
update: '--update or --no-update'
|
|
114
229
|
}
|
|
115
|
-
|
|
230
|
+
console.error(
|
|
231
|
+
`No terminal to ask questions in. Pass ${questions.map(q => flags[q.name]).join(', ')}.`
|
|
232
|
+
)
|
|
233
|
+
process.exit(1)
|
|
234
|
+
}
|
|
116
235
|
|
|
117
|
-
const
|
|
236
|
+
const answers: Partial<Response> = questions.length
|
|
237
|
+
? await enquirer.prompt(questions)
|
|
238
|
+
: {}
|
|
239
|
+
|
|
240
|
+
const projectName = options.name ?? answers.projectName!
|
|
241
|
+
const description = options.description ?? answers.description!
|
|
242
|
+
const template = options.template ?? answers.template!
|
|
243
|
+
const git =
|
|
244
|
+
options.git === undefined ? answers.git! : options.git ? 'yes' : 'no'
|
|
245
|
+
const update =
|
|
246
|
+
options.update === undefined
|
|
247
|
+
? answers.update!
|
|
248
|
+
: options.update
|
|
249
|
+
? 'yes'
|
|
250
|
+
: 'no'
|
|
118
251
|
|
|
119
252
|
const projectDir = path.join(process.cwd(), projectName)
|
|
120
253
|
|
|
@@ -186,19 +319,42 @@ program.action(async () => {
|
|
|
186
319
|
fs.writeFileSync(readmeFile, `# ${projectName}\n\n${description}\n`)
|
|
187
320
|
}
|
|
188
321
|
|
|
322
|
+
const pm = detectPackageManager()
|
|
323
|
+
const installCommand = getInstallCommand(pm)
|
|
324
|
+
const devCommand = getRunCommand(pm, 'dev')
|
|
325
|
+
const dbPushCommand = getRunCommand(pm, 'db:push')
|
|
326
|
+
|
|
327
|
+
// Updating also installs, so the initial commit includes the lockfile
|
|
328
|
+
let installed = false
|
|
329
|
+
if (update === 'yes') {
|
|
330
|
+
const commands = getUpdateCommands(pm)
|
|
331
|
+
if (!commands) {
|
|
332
|
+
console.log(
|
|
333
|
+
`\nAutomatic updates support Bun and pnpm. After '${installCommand}', update packages with your package manager.`
|
|
334
|
+
)
|
|
335
|
+
} else {
|
|
336
|
+
const command = process.stdin.isTTY ? commands.interactive : commands.all
|
|
337
|
+
console.log(`\nUpdating packages with '${command}'…`)
|
|
338
|
+
try {
|
|
339
|
+
execSync(command, { cwd: projectDir, stdio: 'inherit' })
|
|
340
|
+
installed = true
|
|
341
|
+
} catch {
|
|
342
|
+
console.error(
|
|
343
|
+
`Updating packages failed, so the template versions stay. Run '${command}' in the project to try again.`
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
189
349
|
if (git === 'yes') {
|
|
190
350
|
try {
|
|
191
351
|
execSync('git init', { cwd: projectDir, stdio: 'inherit' })
|
|
352
|
+
commitEverything(projectDir)
|
|
192
353
|
} catch (err) {
|
|
193
354
|
console.error('Error initializing git repository:', err)
|
|
194
355
|
}
|
|
195
356
|
}
|
|
196
357
|
|
|
197
|
-
const pm = detectPackageManager()
|
|
198
|
-
const installCommand = getInstallCommand(pm)
|
|
199
|
-
const devCommand = getRunCommand(pm, 'dev')
|
|
200
|
-
const dbPushCommand = getRunCommand(pm, 'db:push')
|
|
201
|
-
|
|
202
358
|
console.log(
|
|
203
359
|
`
|
|
204
360
|
Created a new app called '${projectName}' 🎉
|
|
@@ -210,9 +366,7 @@ code ${projectName}
|
|
|
210
366
|
|
|
211
367
|
To get started, run these commands:
|
|
212
368
|
cd ${projectName}
|
|
213
|
-
|
|
214
|
-
${installCommand}
|
|
215
|
-
|
|
369
|
+
${installed ? '' : `\n${installCommand}\n`}
|
|
216
370
|
Fill in the values in .env, then create the database tables:
|
|
217
371
|
|
|
218
372
|
${dbPushCommand}
|