@@ -294,7 +294,7 @@ function getRenderBinaryInfo() {
294294 return { platformKey, binName, binPath, candidates } ;
295295}
296296
297- const resolveProjectPath = ( filePath : string ) => {
297+ const normalizeInputPath = ( filePath : string ) => {
298298 let normalized = filePath ;
299299 if ( normalized . startsWith ( "file:" ) ) {
300300 try {
@@ -303,37 +303,32 @@ const resolveProjectPath = (filePath: string) => {
303303 normalized = normalized . replace ( / ^ f i l e : ( \/ \/ ) ? / , "" ) ;
304304 }
305305 }
306- const candidate = path . isAbsolute ( normalized )
307- ? normalized
308- : path . join ( PROJECT_ROOT , normalized ) ;
309- const resolved = path . resolve ( candidate ) ;
310- const root = path . resolve ( PROJECT_ROOT ) ;
311- if ( resolved === root || resolved . startsWith ( `${ root } ${ path . sep } ` ) ) {
312- return resolved ;
306+ if ( process . platform === "win32" ) {
307+ normalized = normalized . replace ( / ^ \/ ( [ a - z A - Z ] : [ \\ / ] ) / , "$1" ) ;
313308 }
314- throw new Error ( "Path is outside project root" ) ;
309+ return normalized ;
315310} ;
316311
317- const resolveWorkspacePath = ( filePath : string ) => {
318- let normalized = filePath ;
319- if ( normalized . startsWith ( "file:" ) ) {
320- try {
321- normalized = fileURLToPath ( normalized ) ;
322- } catch {
323- normalized = normalized . replace ( / ^ f i l e : ( \/ \/ ) ? / , "" ) ;
324- }
325- }
312+ const resolvePathWithinRoot = ( filePath : string , rootDir : string , errorMessage : string ) => {
313+ const normalized = normalizeInputPath ( filePath ) ;
326314 const candidate = path . isAbsolute ( normalized )
327315 ? normalized
328- : path . join ( WORKSPACE_ROOT , normalized ) ;
316+ : path . join ( rootDir , normalized ) ;
329317 const resolved = path . resolve ( candidate ) ;
330- const root = path . resolve ( WORKSPACE_ROOT ) ;
331- if ( resolved === root || resolved . startsWith ( `${ root } ${ path . sep } ` ) ) {
318+ const root = path . resolve ( rootDir ) ;
319+ const relative = path . relative ( root , resolved ) ;
320+ if ( ! relative || ( ! relative . startsWith ( ".." ) && ! path . isAbsolute ( relative ) ) ) {
332321 return resolved ;
333322 }
334- throw new Error ( "Path is outside workspace root" ) ;
323+ throw new Error ( errorMessage ) ;
335324} ;
336325
326+ const resolveProjectPath = ( filePath : string ) =>
327+ resolvePathWithinRoot ( filePath , PROJECT_ROOT , "Path is outside project root" ) ;
328+
329+ const resolveWorkspacePath = ( filePath : string ) =>
330+ resolvePathWithinRoot ( filePath , WORKSPACE_ROOT , "Path is outside workspace root" ) ;
331+
337332const resolveTypescriptLanguageServerPath = ( ) => {
338333 const binName = process . platform === "win32" ? "typescript-language-server.cmd" : "typescript-language-server" ;
339334 const localBin = path . join ( process . cwd ( ) , "node_modules" , ".bin" , binName ) ;
@@ -343,6 +338,37 @@ const resolveTypescriptLanguageServerPath = () => {
343338 return binName ;
344339} ;
345340
341+ const quoteCmdArg = ( value : string ) => {
342+ if ( ! value || value . length === 0 ) return "\"\"" ;
343+ if ( / [ \s " ] / g. test ( value ) ) {
344+ return `"${ value . replace ( / " / g, "\"\"" ) } "` ;
345+ }
346+ return value ;
347+ } ;
348+
349+ const spawnLanguageServer = ( command : string , args : string [ ] ) => {
350+ const options = {
351+ cwd : process . cwd ( ) ,
352+ stdio : "pipe" as const ,
353+ env : {
354+ ...process . env ,
355+ } ,
356+ } ;
357+
358+ if ( process . platform === "win32" ) {
359+ const lower = command . toLowerCase ( ) ;
360+ if ( lower . endsWith ( ".cmd" ) || lower . endsWith ( ".bat" ) ) {
361+ const cmd = [ quoteCmdArg ( command ) , ...args . map ( quoteCmdArg ) ] . join ( " " ) ;
362+ return spawn ( "cmd.exe" , [ "/d" , "/s" , "/c" , cmd ] , {
363+ ...options ,
364+ windowsHide : true ,
365+ } ) ;
366+ }
367+ }
368+
369+ return spawn ( command , args , options ) ;
370+ } ;
371+
346372const startLspServer = async ( ) => {
347373 if ( lspServer && lspPort ) return lspPort ;
348374
@@ -374,13 +400,7 @@ const startLspServer = async () => {
374400
375401 console . log ( "[lsp] spawn:" , command , args . join ( " " ) ) ;
376402
377- const proc = spawn ( command , args , {
378- cwd : process . cwd ( ) ,
379- stdio : "pipe" ,
380- env : {
381- ...process . env ,
382- } ,
383- } ) ;
403+ const proc = spawnLanguageServer ( command , args ) ;
384404
385405 if ( ! proc . stdout || ! proc . stdin ) {
386406 console . error ( "[lsp] stdio unavailable for language server" ) ;
0 commit comments