Skip to content

Commit c1be8fe

Browse files
refactor(cli): only install and start project after all questions (#77)
1 parent eca5f22 commit c1be8fe

File tree

11 files changed

+211
-146
lines changed

11 files changed

+211
-146
lines changed

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@babel/types": "7.24.5",
3232
"@clack/prompts": "^0.7.0",
3333
"chalk": "^5.3.0",
34+
"execa": "^9.2.0",
3435
"ignore": "^5.3.1",
3536
"lookpath": "^1.2.2",
3637
"yargs-parser": "^21.1.1"
@@ -43,7 +44,6 @@
4344
"@types/yargs-parser": "^21.0.3",
4445
"esbuild": "^0.20.2",
4546
"esbuild-node-externals": "^1.13.0",
46-
"execa": "^9.2.0",
4747
"fs-extra": "^11.2.0",
4848
"vitest": "^1.6.0"
4949
},

packages/cli/src/commands/create/dependencies.ts

Lines changed: 0 additions & 115 deletions
This file was deleted.

packages/cli/src/commands/create/enterprise.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import { parseAstroConfig, replaceArgs } from './astro-config.js';
55
import { generate } from './babel.js';
66

77
export async function setupEnterpriseConfig(dest: string, flags: CreateOptions) {
8-
let editorOrigin = flags.enterprise;
9-
108
if (!flags.defaults && flags.enterprise === undefined) {
9+
// early exit if `--defaults` is provided without `--enterprise`
1110
return;
12-
} else if (editorOrigin) {
11+
}
12+
13+
let editorOrigin = flags.enterprise;
14+
15+
if (editorOrigin) {
1316
const error = validateEditorOrigin(editorOrigin);
1417

1518
if (error) {

packages/cli/src/commands/create/git.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { pkg } from '../../pkg.js';
66
import { warnLabel } from '../../utils/messages.js';
77
import { runShellCommand } from '../../utils/shell.js';
88
import { assertNotCanceled, runTask } from '../../utils/tasks.js';
9-
import { DEFAULT_VALUES, type CreateOptions } from './options.js';
9+
import { DEFAULT_VALUES, readFlag, type CreateOptions } from './options.js';
1010

1111
export async function initGitRepo(cwd: string, flags: CreateOptions) {
12-
let shouldInitGitRepo = flags.git ?? DEFAULT_VALUES.git;
12+
let shouldInitGitRepo = readFlag(flags, 'git');
1313

14-
if (!flags.defaults && flags.git === undefined) {
14+
if (shouldInitGitRepo === undefined) {
1515
const answer = await prompts.confirm({
1616
message: 'Initialize a new git repository?',
1717
initialValue: DEFAULT_VALUES.git,

packages/cli/src/commands/create/index.ts

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ import chalk from 'chalk';
33
import fs from 'node:fs';
44
import path from 'node:path';
55
import yargs from 'yargs-parser';
6+
import { execa } from 'execa';
67
import { pkg } from '../../pkg.js';
7-
import { errorLabel, primaryLabel, printHelp } from '../../utils/messages.js';
8+
import { errorLabel, primaryLabel, printHelp, warnLabel } from '../../utils/messages.js';
89
import { generateProjectName } from '../../utils/project.js';
910
import { assertNotCanceled } from '../../utils/tasks.js';
10-
import { installDependencies, type PackageManager } from './dependencies.js';
1111
import { initGitRepo } from './git.js';
1212
import { DEFAULT_VALUES, type CreateOptions } from './options.js';
1313
import { setupEnterpriseConfig } from './enterprise.js';
1414
import { copyTemplate } from './template.js';
15+
import { selectPackageManager, type PackageManager } from './package-manager.js';
16+
import { installAndStart } from './install-start.js';
1517

1618
const TUTORIALKIT_VERSION = pkg.version;
1719

@@ -24,6 +26,7 @@ export async function createTutorial(flags: yargs.Arguments) {
2426
Options: [
2527
['--dir, -d', 'The folder in which the tutorial gets created'],
2628
['--install, --no-install', `Install dependencies (default ${chalk.yellow(DEFAULT_VALUES.install)})`],
29+
['--start, --no-start', `Start project (default ${chalk.yellow(DEFAULT_VALUES.start)})`],
2730
['--git, --no-git', `Initialize a local git repository (default ${chalk.yellow(DEFAULT_VALUES.git)})`],
2831
['--dry-run', `Walk through steps without executing (default ${chalk.yellow(DEFAULT_VALUES.dryRun)})`],
2932
[
@@ -46,6 +49,16 @@ export async function createTutorial(flags: yargs.Arguments) {
4649
return 0;
4750
}
4851

52+
applyAliases(flags);
53+
54+
try {
55+
verifyFlags(flags);
56+
} catch (error) {
57+
console.error(`${errorLabel()} ${error.message}`);
58+
59+
process.exit(1);
60+
}
61+
4962
try {
5063
return _createTutorial(flags);
5164
} catch (error) {
@@ -60,8 +73,6 @@ export async function createTutorial(flags: yargs.Arguments) {
6073
}
6174

6275
async function _createTutorial(flags: CreateOptions) {
63-
applyAliases(flags);
64-
6576
prompts.intro(primaryLabel(pkg.name));
6677

6778
let tutorialName = flags._[1] !== undefined ? String(flags._[1]) : undefined;
@@ -131,21 +142,55 @@ async function _createTutorial(flags: CreateOptions) {
131142

132143
updatePackageJson(resolvedDest, tutorialName, flags);
133144

134-
const { selectedPackageManager, dependenciesInstalled } = await installDependencies(resolvedDest, flags);
145+
const selectedPackageManager = await selectPackageManager(flags);
135146

136147
updateReadme(resolvedDest, selectedPackageManager, flags);
137148

138149
await setupEnterpriseConfig(resolvedDest, flags);
139150

140151
await initGitRepo(resolvedDest, flags);
141152

153+
const { install, start } = await installAndStart(flags);
154+
142155
prompts.log.success(chalk.green('Tutorial successfully created!'));
143156

144-
printNextSteps(dest, selectedPackageManager, dependenciesInstalled);
157+
if (install || start) {
158+
let message = 'Please wait while we install the dependencies and start your project...';
145159

146-
prompts.outro(`You're all set!`);
160+
if (install && !start) {
161+
// change the message if we're only installing dependencies
162+
message = 'Please wait while we install the dependencies...';
147163

148-
console.log('Until next time 👋');
164+
// print the next steps without the install step in case we only install dependencies
165+
printNextSteps(dest, selectedPackageManager, true);
166+
}
167+
168+
prompts.outro(message);
169+
170+
await startProject(resolvedDest, selectedPackageManager, flags, start);
171+
} else {
172+
printNextSteps(dest, selectedPackageManager, false);
173+
174+
prompts.outro(`You're all set!`);
175+
176+
console.log('Until next time 👋');
177+
}
178+
}
179+
180+
async function startProject(cwd: string, packageManager: PackageManager, flags: CreateOptions, startProject: boolean) {
181+
if (flags.dryRun) {
182+
const message = startProject
183+
? 'Skipped dependency installation and project start'
184+
: 'Skipped dependency installation';
185+
186+
console.warn(`${warnLabel('DRY RUN')} ${message}`);
187+
} else {
188+
await execa(packageManager, ['install'], { cwd, stdio: 'inherit' });
189+
190+
if (startProject) {
191+
await execa(packageManager, ['run', 'dev'], { cwd, stdio: 'inherit' });
192+
}
193+
}
149194
}
150195

151196
async function getTutorialDirectory(tutorialName: string, flags: CreateOptions) {
@@ -264,3 +309,9 @@ function applyAliases(flags: CreateOptions & Record<string, any>) {
264309
flags.enterprise = flags.e;
265310
}
266311
}
312+
313+
function verifyFlags(flags: CreateOptions) {
314+
if (flags.install === false && flags.start) {
315+
throw new Error('Cannot start project without installing dependencies.');
316+
}
317+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as prompts from '@clack/prompts';
2+
import { DEFAULT_VALUES, readFlag, type CreateOptions } from './options.js';
3+
import { assertNotCanceled } from '../../utils/tasks.js';
4+
5+
export async function installAndStart(flags: CreateOptions) {
6+
let installDeps = readFlag(flags, 'install');
7+
let startProject = readFlag(flags, 'start');
8+
9+
if (installDeps === false) {
10+
// the user doesn't want to install the dependencies, which means we can't start the project either
11+
return { install: false, start: false };
12+
}
13+
14+
if (startProject) {
15+
// the user wants to start the project, so we also have to install the dependencies
16+
return { install: true, start: true };
17+
}
18+
19+
if (installDeps) {
20+
if (startProject === false) {
21+
// the user wants to install the dependencies but expicitly not start the project
22+
return { install: true, start: false };
23+
} else {
24+
// the user explicitly wants to install the dependencies, but we don't know if they want to start the project
25+
const answer = await prompts.confirm({
26+
message: 'Start project?',
27+
initialValue: DEFAULT_VALUES.install,
28+
});
29+
30+
assertNotCanceled(answer);
31+
32+
return { install: true, start: answer };
33+
}
34+
}
35+
36+
const answer = await prompts.confirm({
37+
message: 'Install dependencies and start project?',
38+
initialValue: DEFAULT_VALUES.install,
39+
});
40+
41+
assertNotCanceled(answer);
42+
43+
return { install: answer, start: answer };
44+
}

packages/cli/src/commands/create/options.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
44
export interface CreateOptions {
55
_: Array<string | number>;
66
install?: boolean;
7+
start?: boolean;
78
git?: boolean;
89
enterprise?: string;
910
dir?: string;
@@ -20,7 +21,20 @@ export const templatePath = path.resolve(__dirname, process.env.TUTORIALKIT_TEMP
2021
export const DEFAULT_VALUES = {
2122
git: process.env.CI ? false : true,
2223
install: true,
24+
start: true,
2325
dryRun: false,
2426
force: false,
2527
packageManager: 'npm',
2628
};
29+
30+
type Flags = Omit<CreateOptions, '_'>;
31+
32+
export function readFlag<Flag extends keyof Flags>(flags: Flags, flag: Flag): Flags[Flag] {
33+
let value = flags[flag];
34+
35+
if (flags.defaults) {
36+
value ??= (DEFAULT_VALUES as Flags)[flag];
37+
}
38+
39+
return value;
40+
}

0 commit comments

Comments
 (0)