1+ import { confirm , isCancel } from '@clack/prompts' ;
12import chalk from 'chalk' ;
23import { spawn } from 'child_process' ;
34import fs from 'fs-extra' ;
4- import ora from 'ora' ;
5+ import ora , { type Ora } from 'ora' ;
56import path from 'path' ;
6- import prompts from 'prompts' ;
77
88import { runPreflightChecks } from '../checks.js' ;
99import type { FeatureOptions } from '../constants.js' ;
@@ -47,6 +47,42 @@ interface CreateFlags {
4747 dryRun ?: boolean ;
4848}
4949
50+ function renderProgressBar ( current : number , total : number ) : string {
51+ const width = 28 ;
52+ const clampedTotal = Math . max ( total , 1 ) ;
53+ const ratio = Math . min ( Math . max ( current / clampedTotal , 0 ) , 1 ) ;
54+ const filled = Math . round ( width * ratio ) ;
55+ const empty = width - filled ;
56+ const filledBar = chalk . cyan ( '█' . repeat ( filled ) ) ;
57+ const emptyBar = chalk . dim ( '░' . repeat ( empty ) ) ;
58+ const percentage = `${ Math . round ( ratio * 100 ) } ` . padStart ( 3 , ' ' ) ;
59+ return `[${ filledBar } ${ emptyBar } ] ${ percentage } %` ;
60+ }
61+
62+ function renderStepTrack ( step : number , total : number ) : string {
63+ const segments : string [ ] = [ ] ;
64+
65+ for ( let index = 1 ; index <= total ; index ++ ) {
66+ if ( index < step ) {
67+ segments . push ( chalk . green ( '●' ) ) ;
68+ } else if ( index === step ) {
69+ segments . push ( chalk . cyan ( '◆' ) ) ;
70+ } else {
71+ segments . push ( chalk . dim ( '◇' ) ) ;
72+ }
73+ }
74+
75+ return segments . join ( chalk . dim ( '──' ) ) ;
76+ }
77+
78+ function printStepHeader ( step : number , total : number , title : string ) : void {
79+ const completed = step - 1 ;
80+ console . log ( ) ;
81+ console . log ( chalk . cyan ( ` Step ${ step } /${ total } ` ) , chalk . bold ( title ) ) ;
82+ console . log ( ` ${ renderProgressBar ( completed , total ) } ` ) ;
83+ console . log ( ` ${ renderStepTrack ( step , total ) } ` ) ;
84+ }
85+
5086function printDryRun ( options : {
5187 projectName : string ;
5288 projectSlug : string ;
@@ -139,14 +175,12 @@ export async function create(
139175 const files = await fs . readdir ( targetDir ) ;
140176 if ( files . length > 0 ) {
141177 if ( options . useCurrentDir ) {
142- const { confirm } = await prompts ( {
143- type : 'confirm' ,
144- name : 'confirm' ,
145- message : `Current directory is not empty. Continue?` ,
146- initial : false ,
178+ const shouldContinue = await confirm ( {
179+ message : 'Current directory is not empty. Continue?' ,
180+ initialValue : false ,
147181 } ) ;
148182
149- if ( ! confirm ) {
183+ if ( isCancel ( shouldContinue ) || ! shouldContinue ) {
150184 return ;
151185 }
152186 } else {
@@ -156,13 +190,24 @@ export async function create(
156190 }
157191 }
158192
159- const spinner = ora ( ) ;
193+ const shouldRunSetup = await promptAutomaticSetup ( ) ;
194+ const totalSteps =
195+ 2 +
196+ ( options . skipGit ? 0 : 1 ) +
197+ ( options . skipInstall ? 0 : 1 ) +
198+ ( shouldRunSetup ? 1 : 0 ) ;
199+ let currentStep = 0 ;
200+ let spinner : Ora | undefined ;
160201
161202 try {
162- spinner . start ( 'Downloading template from GitHub...' ) ;
203+ currentStep += 1 ;
204+ printStepHeader ( currentStep , totalSteps , 'Scaffold template' ) ;
205+ spinner = ora ( 'Downloading template from GitHub...' ) . start ( ) ;
163206 await downloadAndPrepareTemplate ( targetDir , spinner , options . features ) ;
164207
165- spinner . start ( 'Configuring project...' ) ;
208+ currentStep += 1 ;
209+ printStepHeader ( currentStep , totalSteps , 'Configure project files' ) ;
210+ spinner . start ( 'Applying template transforms...' ) ;
166211 await transformFiles (
167212 targetDir ,
168213 {
@@ -176,16 +221,24 @@ export async function create(
176221 spinner . succeed ( 'Configured project' ) ;
177222
178223 if ( ! options . skipGit && isGitInstalled ( ) ) {
224+ currentStep += 1 ;
225+ printStepHeader ( currentStep , totalSteps , 'Initialize git repository' ) ;
179226 spinner . start ( 'Initializing git repository...' ) ;
180227 const gitSuccess = initGit ( targetDir ) ;
181228 if ( gitSuccess ) {
182229 spinner . succeed ( 'Initialized git repository' ) ;
183230 } else {
184231 spinner . warn ( 'Failed to initialize git repository' ) ;
185232 }
233+ } else if ( ! options . skipGit ) {
234+ currentStep += 1 ;
235+ printStepHeader ( currentStep , totalSteps , 'Initialize git repository' ) ;
236+ spinner . warn ( 'Skipped git initialization (git not installed)' ) ;
186237 }
187238
188239 if ( ! options . skipInstall ) {
240+ currentStep += 1 ;
241+ printStepHeader ( currentStep , totalSteps , 'Install dependencies' ) ;
189242 spinner . start ( 'Installing dependencies...' ) ;
190243 const success = await runInstall ( targetDir ) ;
191244 if ( success ) {
@@ -197,12 +250,11 @@ export async function create(
197250 }
198251 }
199252
200- // Prompt for automatic setup
201253 let ranAutomaticSetup = false ;
202- const shouldRunSetup = await promptAutomaticSetup ( ) ;
203254
204255 if ( shouldRunSetup ) {
205- console . log ( ) ;
256+ currentStep += 1 ;
257+ printStepHeader ( currentStep , totalSteps , 'Run local database setup' ) ;
206258 spinner . start ( 'Starting PostgreSQL database...' ) ;
207259 const dockerSuccess = runDockerCompose ( targetDir ) ;
208260 if ( dockerSuccess ) {
@@ -231,7 +283,7 @@ export async function create(
231283 ranAutomaticSetup
232284 ) ;
233285 } catch ( error ) {
234- spinner . fail ( ) ;
286+ spinner ? .fail ( ) ;
235287 printError (
236288 error instanceof Error ? error . message : 'Unknown error occurred'
237289 ) ;
0 commit comments