1+ import  EventEmitter  from  'node:events' ; 
12import  fs  from  'node:fs' ; 
23import  path  from  'node:path' ; 
34import  process  from  'node:process' ; 
@@ -6,8 +7,11 @@ import ejs from 'ejs';
67import  fse  from  'fs-extra' ; 
78import  {  glob  }  from  'glob' ; 
89import  *  as  colors  from  'picocolors' ; 
10+ import  type  {  W  }  from  'vitest/dist/chunks/reporters.D7Jzd9GS.js' ; 
11+ import  {  MiddleWare ,  type  MiddleWareCallback  }  from  './MiddleWare' ; 
12+ import  {  TypedEvents  }  from  './TypedEvents' ; 
913import  {  selectWriteMode  }  from  './prompts' ; 
10- import  {  execCommand ,  isDirectory  }  from  './utils' ; 
14+ import  {  execCommand ,  isDirectory ,   normalizePath  }  from  './utils' ; 
1115
1216export  type  Prompts  =  typeof  prompts ; 
1317export  type  Colors  =  typeof  colors ; 
@@ -90,10 +94,7 @@ export type CreatorBuiltinData = {
9094 */ 
9195export  type  CreatorData < T >  =  CreatorBuiltinData  &  T ; 
9296
93- /** 
94-  * Metadata about files being processed 
95-  */ 
96- export  type  WriteMeta  =  { 
97+ export  type  FileTypes  =  { 
9798  /** 
9899   * Whether file uses EJS templating 
99100   */ 
@@ -106,6 +107,12 @@ export type WriteMeta = {
106107   * Whether file uses dot prefix 
107108   */ 
108109  isDotFile : boolean ; 
110+ } ; 
111+ 
112+ /** 
113+  * Metadata about files being processed 
114+  */ 
115+ export  type  FileMeta  =  FileTypes  &  { 
109116  /** 
110117   * Full path to source file 
111118   */ 
@@ -149,33 +156,23 @@ export type CreatorOptions<T> = {
149156   * Root directory containing templates 
150157   */ 
151158  templatesRoot : string ; 
152-   /** 
153-    * Callback before template generation 
154-    */ 
155-   onStart ?: ( context : CreatorContext )  =>  unknown  |  Promise < unknown > ; 
156159  /** 
157160   * Extend template data with custom properties 
158161   */ 
159162  extendData ?: ( context : CreatorContext )  =>  T  |  Promise < T > ; 
160-   /** 
161-    * Control which files should be written 
162-    */ 
163-   canWrite ?: ( meta : WriteMeta ,  data : CreatorData < T > )  =>  boolean  |  Promise < boolean > ; 
163+ 
164+   canWrite ?: ( meta : FileMeta ,  data : CreatorData < T > )  =>  boolean  |  Promise < boolean > ; 
165+   canRender ?: ( meta : FileMeta ,  data : CreatorData < T > )  =>  boolean  |  Promise < boolean > ; 
164166  /** 
165167   * Custom file writing implementation 
166168   */ 
167-   doWrite ?: ( meta : WriteMeta ,  data : CreatorData < T > )  =>  unknown  |  Promise < unknown > ; 
169+   doWrite ?: ( meta : FileMeta ,  data : CreatorData < T > )  =>  unknown  |  Promise < unknown > ; 
168170  /** 
169171   * Callback after each file is written 
170172   */ 
171-   onWritten ?: ( meta : WriteMeta ,  data : CreatorData < T > )  =>  unknown  |  Promise < unknown > ; 
172-   /** 
173-    * Callback after template generation completes 
174-    */ 
175-   onEnd ?: ( context : CreatorContext )  =>  unknown  |  Promise < unknown > ; 
173+   onWritten ?: ( meta : FileMeta ,  data : CreatorData < T > )  =>  unknown  |  Promise < unknown > ; 
176174} ; 
177175
178- const  normalizePath  =  ( path : string )  =>  path . replace ( / \\ / g,  '/' ) ; 
179176const  UNDERSCORE_FILE_PREFIX  =  '__' ; 
180177const  DOT_FILE_PREFIX  =  '_' ; 
181178const  EJS_FILE_SUFFIX  =  '.ejs' ; 
@@ -185,7 +182,10 @@ const EJS_FILE_REGEX = /\.ejs$/i;
185182 * Main class for handling project creation 
186183 * @template  T - Type of custom data to extend with 
187184 */ 
188- class  Creator < T  extends  Record < string ,  unknown > >  { 
185+ export  class  Creator < T  extends  Record < string ,  unknown > >  extends  TypedEvents < { 
186+   start : [ context : CreatorContext ] ; 
187+   end : [ context : CreatorContext ] ; 
188+ } >  { 
189189  context : CreatorContext  =  { 
190190    cwd : '' , 
191191    templatesRoot : '' , 
@@ -202,11 +202,15 @@ class Creator<T extends Record<string, unknown>> {
202202  } ; 
203203  data : CreatorData < T > ; 
204204
205+   fileMetaMW : MiddleWare < [ meta : FileMeta ,  data : CreatorData < T > ] ,  Partial < FileTypes > > ; 
206+ 
205207  /** 
206208   * Create a new Creator instance 
207209   * @param  {CreatorOptions<T> } options - Configuration options 
208210   */ 
209211  constructor ( private  readonly  options : CreatorOptions < T > )  { 
212+     super ( ) ; 
213+ 
210214    const  cwd  =  normalizePath ( options . cwd  ||  process . cwd ( ) ) ; 
211215    const  projectRoot  =  normalizePath ( path . resolve ( cwd ,  options . projectPath  ||  '.' ) ) ; 
212216    const  {  context }  =  this ; 
@@ -221,6 +225,18 @@ class Creator<T extends Record<string, unknown>> {
221225      : `create-${ context . projectName }  ` ; 
222226
223227    this . data  =  {  ctx : context  }  as  CreatorData < T > ; 
228+ 
229+     this . fileMetaMW  =  new  MiddleWare ( { 
230+       cwd : context . templatesRoot , 
231+     } ) ; 
232+   } 
233+ 
234+   fileIntercept ( 
235+     paths : string  |  string [ ] , 
236+     interceptor : MiddleWareCallback < [ meta : FileMeta ,  data : CreatorData < T > ] ,  Partial < FileTypes > > , 
237+   )  { 
238+     this . fileMetaMW . match ( paths ,  interceptor ) ; 
239+     return  this ; 
224240  } 
225241
226242  async  #check( )  { 
@@ -266,44 +282,31 @@ class Creator<T extends Record<string, unknown>> {
266282
267283  async  #generate( )  { 
268284    const  {  context,  options }  =  this ; 
269-     const  files  =  await  glob ( '**/*' ,  { 
285+     const  paths  =  await  glob ( '**/*' ,  { 
270286      nodir : true , 
271287      cwd : context . templateRoot , 
272288      dot : false , 
273289    } ) ; 
274290
275-     if  ( files . length  ===  0 )  { 
291+     if  ( paths . length  ===  0 )  { 
276292      prompts . cancel ( `No files found in template(${ context . templateName }  )` ) ; 
277293      process . exit ( 1 ) ; 
278294    } 
279295
280-     for  ( const  sourcePath  of  files )  { 
296+     for  ( const  sourcePath  of  paths )  { 
281297      const  fileName  =  path . basename ( sourcePath ) ; 
282298      const  fileFolder  =  path . dirname ( sourcePath ) ; 
283299      const  sourceFile  =  normalizePath ( path . join ( context . templateRoot ,  sourcePath ) ) ; 
300+ 
284301      const  isEjsFile  =  EJS_FILE_REGEX . test ( sourcePath ) ; 
285302      const  isUnderscoreFile  =  sourcePath . startsWith ( UNDERSCORE_FILE_PREFIX ) ; 
286303      const  isDotFile  =  ! isUnderscoreFile  &&  sourcePath . startsWith ( DOT_FILE_PREFIX ) ; 
287304
288-       let  start  =  0 ; 
289-       let  end  =  undefined ; 
290-       let  prefix  =  '' ; 
291- 
292-       if  ( isEjsFile )  { 
293-         end  =  - EJS_FILE_SUFFIX . length ; 
294-       } 
295- 
296-       if  ( isUnderscoreFile )  { 
297-         start  =  UNDERSCORE_FILE_PREFIX . length ; 
298-         prefix  =  '_' ; 
299-       }  else  if  ( isDotFile )  { 
300-         start  =  DOT_FILE_PREFIX . length ; 
301-         prefix  =  '.' ; 
302-       } 
303- 
305+       const  {  prefix,  end,  start }  =  calculateFileMate ( {  isDotFile,  isEjsFile,  isUnderscoreFile } ) ; 
304306      const  targetPath  =  normalizePath ( path . join ( fileFolder ,  prefix  +  fileName . slice ( start ,  end ) ) ) ; 
305307      const  targetFile  =  normalizePath ( path . join ( context . projectRoot ,  targetPath ) ) ; 
306-       const  writeMeta : WriteMeta  =  { 
308+ 
309+       const  fileMeta : FileMeta  =  { 
307310        isDotFile, 
308311        isEjsFile, 
309312        isUnderscoreFile, 
@@ -314,25 +317,44 @@ class Creator<T extends Record<string, unknown>> {
314317        targetFile, 
315318        targetRoot : context . projectRoot , 
316319      } ; 
320+       const  fileTypes  =  await  this . fileMetaMW . when ( path . join ( context . templateName ,  sourcePath ) ,  fileMeta ,  this . data ) ; 
321+ 
322+       // 修改了 fileTypes 
323+       if  ( fileTypes )  { 
324+         const  {  isDotFile,  isEjsFile,  isUnderscoreFile }  =  fileTypes ; 
325+         const  {  prefix,  end,  start }  =  calculateFileMate ( {  isDotFile,  isEjsFile,  isUnderscoreFile } ) ; 
326+         const  targetPath  =  normalizePath ( path . join ( fileFolder ,  prefix  +  fileName . slice ( start ,  end ) ) ) ; 
327+         const  targetFile  =  normalizePath ( path . join ( context . projectRoot ,  targetPath ) ) ; 
328+         fileMeta . targetPath  =  targetPath ; 
329+         fileMeta . targetFile  =  targetFile ; 
330+         fileMeta . isDotFile  =  isDotFile  ||  false ; 
331+         fileMeta . isEjsFile  =  isEjsFile  ||  false ; 
332+         fileMeta . isUnderscoreFile  =  isUnderscoreFile  ||  false ; 
333+       } 
317334
318-       if  ( options . canWrite ?. call ( null ,  writeMeta ,  this . data )  ===  false )  { 
335+       if  ( ( await   options . canWrite ?. call ( null ,  fileMeta ,  this . data ) )  ===  false )  { 
319336        continue ; 
320337      } 
321338
322-       if  ( options . doWrite )  { 
323-         await  options . doWrite ( writeMeta ,  this . data ) ; 
324-       }  else  if  ( isEjsFile )  { 
325-         const  template  =  await  fse . readFile ( sourceFile ,  'utf8' ) ; 
326-         fse . outputFileSync ( targetFile ,  ejs . render ( template ,  this . data ) ) ; 
327-       }  else  { 
328-         fse . copySync ( sourceFile ,  targetFile ) ; 
329-       } 
339+       await  this . #write( fileMeta ) ; 
340+       options . onWritten ?. call ( null ,  fileMeta ,  this . data ) ; 
341+     } 
342+   } 
343+ 
344+   async  #write( fileMeta : FileMeta )  { 
345+     const  {  context,  options }  =  this ; 
330346
331-       options . onWritten ?. call ( null ,  writeMeta ,  this . data ) ; 
347+     if  ( options . doWrite )  { 
348+       await  options . doWrite ( fileMeta ,  this . data ) ; 
349+     }  else  if  ( fileMeta . isEjsFile )  { 
350+       const  template  =  await  fse . readFile ( fileMeta . sourceFile ,  'utf8' ) ; 
351+       fse . outputFileSync ( fileMeta . targetFile ,  ejs . render ( template ,  this . data ) ) ; 
352+     }  else  { 
353+       fse . copySync ( fileMeta . sourceFile ,  fileMeta . targetFile ) ; 
332354    } 
333355  } 
334356
335-   async  start ( )  { 
357+   async  create ( )  { 
336358    const  {  context,  options }  =  this ; 
337359
338360    if  ( isDirectory ( context . templatesRoot )  ===  false )  { 
@@ -349,7 +371,8 @@ class Creator<T extends Record<string, unknown>> {
349371      process . exit ( 1 ) ; 
350372    } 
351373
352-     await  options . onStart ?. call ( null ,  context ) ; 
374+     await  this . emit ( 'start' ,  context ) ; 
375+ 
353376    context . templateName  = 
354377      templateNames . length  ===  1 
355378        ? templateNames [ 0 ] 
@@ -365,17 +388,26 @@ class Creator<T extends Record<string, unknown>> {
365388    await  this . #check( ) ; 
366389    await  this . #extend( ) ; 
367390    await  this . #generate( ) ; 
368- 
369-     await  options . onEnd ?. call ( null ,  context ) ; 
391+     await  this . emit ( 'end' ,  context ) ; 
370392  } 
371393} 
372394
373- /** 
374-  * Build and start a new creator instance 
375-  * @template  T - Type of custom data to extend with 
376-  * @param  {CreatorOptions<T> } options - Configuration options 
377-  * @returns  {Promise<void> } 
378-  */ 
379- export  async  function  createCreator < T  extends  Record < string ,  unknown > > ( options : CreatorOptions < T > )  { 
380-   await  new  Creator < T > ( options ) . start ( ) ; 
395+ export  function  calculateFileMate ( {  isDotFile,  isEjsFile,  isUnderscoreFile } : Partial < FileTypes > )  { 
396+   let  start  =  0 ; 
397+   let  end  =  undefined ; 
398+   let  prefix  =  '' ; 
399+ 
400+   if  ( isEjsFile )  { 
401+     end  =  - EJS_FILE_SUFFIX . length ; 
402+   } 
403+ 
404+   if  ( isUnderscoreFile )  { 
405+     start  =  UNDERSCORE_FILE_PREFIX . length ; 
406+     prefix  =  '_' ; 
407+   }  else  if  ( isDotFile )  { 
408+     start  =  DOT_FILE_PREFIX . length ; 
409+     prefix  =  '.' ; 
410+   } 
411+ 
412+   return  {  start,  end,  prefix } ; 
381413} 
0 commit comments