11/* eslint-disable import/no-named-as-default-member */
2- import { access , mkdir , rename , rm , writeFile } from 'node:fs/promises' ;
2+ import { access , mkdir , rename , rm , writeFile , cp } from 'node:fs/promises' ;
33import { tmpdir } from 'node:os' ;
44import { randomUUID } from 'node:crypto' ;
55import { Command , program } from 'commander' ;
@@ -17,6 +17,26 @@ import TsTemplates from '../templates/typescript.js';
1717import { renderResultTree } from '../helper.js' ;
1818import { getGoModulePathProtoOption } from '../toolchain.js' ;
1919
20+ // The move function is a wrapper around the fs/promises rename operation.
21+ // This is necessary because the OS-level rename will fail with an EXDEV error
22+ // when trying to move a file or directory across different filesystems.
23+ // In such cases, move falls back to recursively copying the source to the destination
24+ // and then removing the original source directory or file.
25+ const move = async ( src : string , dest : string ) => {
26+ try {
27+ await rename ( src , dest ) ;
28+ } catch ( error : unknown ) {
29+ if ( typeof error === 'object' && error !== null && 'code' in error && error . code === 'EXDEV' ) {
30+ // fallback for cross-device moves
31+ await cp ( src , dest , { recursive : true } ) ;
32+ await rm ( src , { recursive : true , force : true } ) ;
33+ return ;
34+ }
35+
36+ throw error ;
37+ }
38+ } ;
39+
2040export default ( opts : BaseCommandOptions ) => {
2141 const command = new Command ( 'init' ) ;
2242 command . description ( 'Scaffold a new gRPC router plugin' ) ;
@@ -182,7 +202,7 @@ export default (opts: BaseCommandOptions) => {
182202 await mkdir ( projectDir , { recursive : true } ) ;
183203 }
184204
185- await rename ( tempDir , pluginDir ) ;
205+ await move ( tempDir , pluginDir ) ;
186206
187207 const endTime = performance . now ( ) ;
188208 const elapsedTimeMs = endTime - startTime ;
0 commit comments