@ttsc/banner 0.16.7 → 0.16.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/driver/banner.go +74 -2
  2. package/package.json +2 -2
package/driver/banner.go CHANGED
@@ -364,7 +364,7 @@ function toSerializableBanner(value) {
364
364
  // build runs with `--no-plugins` so evaluating the config never triggers the
365
365
  // host project's transform/check plugins against the loader tsconfig.
366
366
  func loadBannerTypeScriptConfigFile(location string) (any, error) {
367
- tempDir, err := os.MkdirTemp("", "ttsc-banner-config-")
367
+ tempDir, err := os.MkdirTemp(loaderTempBase(location, os.TempDir()), "ttsc-banner-config-")
368
368
  if err != nil {
369
369
  return nil, fmt.Errorf("@ttsc/banner: create config loader tempdir: %w", err)
370
370
  }
@@ -507,7 +507,7 @@ func typeScriptConfigLoaderTsconfig(loader, location, outDir string) string {
507
507
  "moduleResolution": "bundler",
508
508
  "outDir": filepath.ToSlash(filepath.Join(outDir, "out")),
509
509
  "rewriteRelativeImportExtensions": true,
510
- "rootDir": "/",
510
+ "rootDir": loaderRootDir(outDir),
511
511
  "skipLibCheck": true,
512
512
  "strict": true,
513
513
  "target": "ES2022",
@@ -521,6 +521,78 @@ func typeScriptConfigLoaderTsconfig(loader, location, outDir string) string {
521
521
  return string(body)
522
522
  }
523
523
 
524
+ // loaderRootDir returns the widest rootDir that still contains the loader
525
+ // tsconfig's inputs: the volume root of the loader temp dir (`C:/` on
526
+ // Windows, `/` elsewhere). A literal "/" is not an ancestor of drive-letter
527
+ // paths, so tsgo rejects every input with TS6059 (#299, #304). The temp dir
528
+ // is created on the same volume as the config file (see loaderTempBase), so
529
+ // its volume root spans both `files` entries.
530
+ func loaderRootDir(outDir string) string {
531
+ vol := filepath.VolumeName(outDir)
532
+ if vol == "" {
533
+ return "/"
534
+ }
535
+ return filepath.ToSlash(vol + `\`)
536
+ }
537
+
538
+ // loaderTempBase picks the parent directory for the ephemeral config-loader
539
+ // tree. The system temp dir is the default, but when it sits on a different
540
+ // volume than the config file (Windows: TEMP on `C:`, project on `D:`) the
541
+ // loader cannot work from there — no single tsconfig rootDir spans two
542
+ // volumes and filepath.Rel cannot produce a relative import across drives
543
+ // (#305) — so the tree is created under the config's nearest
544
+ // node_modules/.cache instead, falling back to the config's own directory
545
+ // when no node_modules exists (or its .cache cannot be created): any location
546
+ // on the config's volume beats the system temp dir, which is guaranteed to
547
+ // fail. Returns "" (the os.MkdirTemp default) when the volumes already match.
548
+ func loaderTempBase(location, systemTemp string) string {
549
+ // A relative location has no volume; "" must not be read as "a volume
550
+ // other than the system temp's" — it keeps the historical default (and
551
+ // the Rel-failure contract for relative config paths).
552
+ vol := filepath.VolumeName(location)
553
+ if vol == "" || strings.EqualFold(filepath.VolumeName(systemTemp), vol) {
554
+ return ""
555
+ }
556
+ nodeModules := findNearestNodeModules(filepath.Dir(location))
557
+ if nodeModules == "" {
558
+ return filepath.Dir(location)
559
+ }
560
+ // Resolve a linked node_modules (junction/symlink — common in managed
561
+ // setups) before descending into it: the ESM runtime realpaths the loader
562
+ // module at import time, and a relative config specifier computed from the
563
+ // link-form path would resolve against the wrong directory. NTFS junctions
564
+ // defeat filepath.EvalSymlinks, so the link component is chased by hand
565
+ // first. Realpathing may also land on another volume, which defeats the
566
+ // whole point — fall back to the config's directory then.
567
+ base := filepath.Join(resolveDirLink(nodeModules), ".cache")
568
+ if err := os.MkdirAll(base, 0o755); err != nil {
569
+ return filepath.Dir(location)
570
+ }
571
+ real, err := filepath.EvalSymlinks(base)
572
+ if err != nil || !strings.EqualFold(filepath.VolumeName(real), filepath.VolumeName(location)) {
573
+ return filepath.Dir(location)
574
+ }
575
+ return real
576
+ }
577
+
578
+ // resolveDirLink chases a directory that is itself a symlink or NTFS junction
579
+ // to its target (bounded against link cycles). os.Readlink is the probe:
580
+ // it resolves junctions, which report neither ModeSymlink nor an
581
+ // EvalSymlinks-traversable path.
582
+ func resolveDirLink(dir string) string {
583
+ for i := 0; i < 8; i++ {
584
+ target, err := os.Readlink(dir)
585
+ if err != nil {
586
+ return dir
587
+ }
588
+ if !filepath.IsAbs(target) {
589
+ target = filepath.Join(filepath.Dir(dir), target)
590
+ }
591
+ dir = target
592
+ }
593
+ return dir
594
+ }
595
+
524
596
  // ttsxCommand builds an exec.Cmd that runs ttsx with the given args.
525
597
  // When TTSC_TTSX_BINARY has a script extension (.js, .ts, …) the binary is
526
598
  // invoked via the Node runtime so it is executed correctly on all platforms.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/banner",
3
- "version": "0.16.7",
3
+ "version": "0.16.9",
4
4
  "description": "First-party ttsc plugin that adds package-documentation JSDoc banners during emit.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  "@types/node": "^25.3.0",
36
36
  "rimraf": "^6.1.2",
37
37
  "typescript": "7.0.1-rc",
38
- "ttsc": "0.16.7"
38
+ "ttsc": "0.16.9"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",