@@ -39,6 +39,8 @@ export async function build(options: build.Options): Promise<build.ReturnType> {
3939 }
4040
4141 const sourceDir = getSourceDir ( { cwd, sources } )
42+
43+ if ( link ) await linkSourceFiles ( { cwd, outDir, sourceDir, sources } )
4244 const packageJson = await decoratePackageJson ( pkgJson , { cwd, link, outDir, sourceDir, assets } )
4345
4446 await writePackageJson ( cwd , packageJson )
@@ -262,23 +264,6 @@ export async function decoratePackageJson(
262264 const exports = Object . fromEntries (
263265 exps
264266 ? Object . entries ( exps ) . map ( ( [ key , value ] ) => {
265- function linkExports ( entry : string ) {
266- try {
267- const destJsAbsolute = path . resolve ( cwd , outFile ( entry , '.js' ) )
268- const destDtsAbsolute = path . resolve ( cwd , outFile ( entry , '.d.ts' ) )
269- const dir = path . dirname ( destJsAbsolute )
270-
271- if ( ! fsSync . existsSync ( dir ) ) fsSync . mkdirSync ( dir , { recursive : true } )
272-
273- const srcAbsolute = path . resolve ( cwd , entry )
274- const srcRelativeJs = path . relative ( path . dirname ( destJsAbsolute ) , srcAbsolute )
275- const srcRelativeDts = path . relative ( path . dirname ( destDtsAbsolute ) , srcAbsolute )
276-
277- fsSync . symlinkSync ( srcRelativeJs , destJsAbsolute , 'file' )
278- fsSync . symlinkSync ( srcRelativeDts , destDtsAbsolute , 'file' )
279- } catch { }
280- }
281-
282267 // Transform single `package.json#exports` entrypoints. They
283268 // must point to the source file. Otherwise, an error is thrown.
284269 //
@@ -297,7 +282,6 @@ export async function decoratePackageJson(
297282 if ( assets . includes ( absolutePath ) ) return [ key , outAsset ( absolutePath ) ]
298283 return [ key , value ]
299284 }
300- if ( link ) linkExports ( value )
301285 return [
302286 key ,
303287 {
@@ -335,7 +319,6 @@ export async function decoratePackageJson(
335319 }
336320 return [ key , value ]
337321 }
338- if ( link ) linkExports ( value . src )
339322 return [
340323 key ,
341324 {
@@ -383,6 +366,68 @@ export declare namespace decoratePackageJson {
383366 }
384367}
385368
369+ /**
370+ * Links source files to output directory for development mode.
371+ *
372+ * @param options - Options for linking source files.
373+ */
374+ // biome-ignore lint/correctness/noUnusedVariables: _
375+ async function linkSourceFiles ( options : linkSourceFiles . Options ) : Promise < void > {
376+ const { cwd, outDir, sourceDir, sources } = options
377+
378+ const relativeSourceDir = path . relative ( cwd , sourceDir )
379+ const sourceFiles : string [ ] = [ ]
380+
381+ async function collectFiles ( dir : string ) : Promise < void > {
382+ const entries = await fs . readdir ( dir , { withFileTypes : true } )
383+ for ( const entry of entries ) {
384+ const fullPath = path . join ( dir , entry . name )
385+ if ( entry . isDirectory ( ) ) await collectFiles ( fullPath )
386+ else if ( / \. ( m | c ) ? [ j t ] s x ? $ / . test ( entry . name ) ) sourceFiles . push ( fullPath )
387+ }
388+ }
389+
390+ // Collect source files from sourceDir if it exists and is a directory
391+ if ( sourceDir !== cwd && fsSync . existsSync ( sourceDir ) && fsSync . statSync ( sourceDir ) . isDirectory ( ) )
392+ await collectFiles ( sourceDir )
393+
394+ // Also add any root-level sources (e.g., ./index.ts)
395+ for ( const source of sources ) if ( ! sourceFiles . includes ( source ) ) sourceFiles . push ( source )
396+
397+ // Create symlinks for each source file
398+ for ( const sourceFile of sourceFiles ) {
399+ let relativePath = path . relative ( cwd , sourceFile )
400+ // Strip the sourceDir prefix if applicable
401+ if ( relativeSourceDir && relativePath . startsWith ( relativeSourceDir + path . sep ) )
402+ relativePath = relativePath . slice ( relativeSourceDir . length + 1 )
403+
404+ const destJs = path . resolve ( outDir , relativePath . replace ( / \. ( m | c ) ? [ j t ] s x ? $ / , '.js' ) )
405+ const destDts = path . resolve ( outDir , relativePath . replace ( / \. ( m | c ) ? [ j t ] s x ? $ / , '.d.ts' ) )
406+
407+ const dir = path . dirname ( destJs )
408+ if ( ! fsSync . existsSync ( dir ) ) await fs . mkdir ( dir , { recursive : true } )
409+
410+ const srcRelativeJs = path . relative ( path . dirname ( destJs ) , sourceFile )
411+ const srcRelativeDts = path . relative ( path . dirname ( destDts ) , sourceFile )
412+
413+ try {
414+ fsSync . symlinkSync ( srcRelativeJs , destJs , 'file' )
415+ } catch { }
416+ try {
417+ fsSync . symlinkSync ( srcRelativeDts , destDts , 'file' )
418+ } catch { }
419+ }
420+ }
421+
422+ declare namespace linkSourceFiles {
423+ type Options = {
424+ cwd : string
425+ outDir : string
426+ sourceDir : string
427+ sources : string [ ]
428+ }
429+ }
430+
386431/**
387432 * Gets entry files from package.json exports field or main field.
388433 *
@@ -455,28 +500,30 @@ export declare namespace getEntries {
455500export function getSourceDir ( options : getSourceDir . Options ) : string {
456501 const { cwd = process . cwd ( ) , sources } = options
457502
458- if ( sources . length === 0 ) return path . resolve ( cwd , 'src' )
459-
460- // Get directories of all entries
461- const dirs = sources . map ( ( source ) => path . dirname ( source ) )
503+ if ( sources . length === 0 ) return cwd
462504
463- // Split each directory into segments
464- const segments = dirs . map ( ( dir ) => dir . split ( path . sep ) )
505+ // Filter to only sources in subdirectories (not root-level files)
506+ const subdirSources = sources . filter ( ( source ) => {
507+ const rel = path . relative ( cwd , path . dirname ( source ) )
508+ return rel !== '' && ! rel . startsWith ( '..' )
509+ } )
465510
466- // Find common segments
467- const commonSegments : string [ ] = [ ]
468- const minLength = Math . min ( ...segments . map ( ( s ) => s . length ) )
511+ // If no subdirectory sources, return cwd (no prefix to strip)
512+ if ( subdirSources . length === 0 ) return cwd
469513
470- for ( let i = 0 ; i < minLength ; i ++ ) {
514+ // Get first directory segment for each subdirectory source
515+ const firstSegments = subdirSources . map ( ( source ) => {
516+ const rel = path . relative ( cwd , source )
471517 // biome-ignore lint/style/noNonNullAssertion: _
472- const segment = segments [ 0 ] ! [ i ] !
473- if ( segments . every ( ( s ) => s [ i ] === segment ) ) commonSegments . push ( segment )
474- else break
475- }
518+ return rel . split ( path . sep ) [ 0 ] !
519+ } )
476520
477- const commonPath = commonSegments . join ( path . sep )
521+ // Find the common first segment
478522 // biome-ignore lint/style/noNonNullAssertion: _
479- return path . resolve ( cwd , path . relative ( cwd , commonPath ) . split ( path . sep ) [ 0 ] ! )
523+ const firstSegment = firstSegments [ 0 ] !
524+ if ( firstSegments . every ( ( s ) => s === firstSegment ) ) return path . resolve ( cwd , firstSegment )
525+
526+ return cwd
480527}
481528
482529export declare namespace getSourceDir {
0 commit comments