@ttsc/strip 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/config.go +74 -2
  2. package/package.json +1 -1
package/driver/config.go CHANGED
@@ -290,7 +290,7 @@ try {
290
290
  // project's transform/check plugins would be wasteful and could fail the
291
291
  // build against this deliberately lenient loader tsconfig.
292
292
  func loadStripTypeScriptConfigFile(location string) (any, error) {
293
- tempDir, err := os.MkdirTemp("", "ttsc-strip-config-")
293
+ tempDir, err := os.MkdirTemp(stripLoaderTempBase(location, os.TempDir()), "ttsc-strip-config-")
294
294
  if err != nil {
295
295
  return nil, fmt.Errorf("@ttsc/strip: create config loader tempdir: %w", err)
296
296
  }
@@ -366,7 +366,7 @@ func stripTypeScriptLoaderTsconfig(loader, location, outDir string) string {
366
366
  "noImplicitAny": false,
367
367
  "outDir": filepath.ToSlash(filepath.Join(outDir, "out")),
368
368
  "rewriteRelativeImportExtensions": true,
369
- "rootDir": "/",
369
+ "rootDir": stripLoaderRootDir(outDir),
370
370
  "skipLibCheck": true,
371
371
  "strict": false,
372
372
  "target": "ES2022",
@@ -383,6 +383,78 @@ func stripTypeScriptLoaderTsconfig(loader, location, outDir string) string {
383
383
  return string(body)
384
384
  }
385
385
 
386
+ // stripLoaderRootDir returns the widest rootDir that still contains the
387
+ // loader tsconfig's inputs: the volume root of the loader temp dir (`C:/` on
388
+ // Windows, `/` elsewhere). A literal "/" is not an ancestor of drive-letter
389
+ // paths, so tsgo rejects every input with TS6059 (#299, #304). The temp dir
390
+ // is created on the same volume as the config file (see stripLoaderTempBase),
391
+ // so its volume root spans both `files` entries.
392
+ func stripLoaderRootDir(outDir string) string {
393
+ vol := filepath.VolumeName(outDir)
394
+ if vol == "" {
395
+ return "/"
396
+ }
397
+ return filepath.ToSlash(vol + `\`)
398
+ }
399
+
400
+ // stripLoaderTempBase picks the parent directory for the ephemeral
401
+ // config-loader tree. The system temp dir is the default, but when it sits on
402
+ // a different volume than the config file (Windows: TEMP on `C:`, project on
403
+ // `D:`) the loader cannot work from there — no single tsconfig rootDir spans
404
+ // two volumes and filepath.Rel cannot produce a relative import across drives
405
+ // (#305) — so the tree is created under the config's nearest
406
+ // node_modules/.cache instead, falling back to the config's own directory
407
+ // when no node_modules exists (or its .cache cannot be created): any location
408
+ // on the config's volume beats the system temp dir, which is guaranteed to
409
+ // fail. Returns "" (the os.MkdirTemp default) when the volumes already match.
410
+ func stripLoaderTempBase(location, systemTemp string) string {
411
+ // A relative location has no volume; "" must not be read as "a volume
412
+ // other than the system temp's" — it keeps the historical default (and
413
+ // the Rel-failure contract for relative config paths).
414
+ vol := filepath.VolumeName(location)
415
+ if vol == "" || strings.EqualFold(filepath.VolumeName(systemTemp), vol) {
416
+ return ""
417
+ }
418
+ nodeModules := stripFindNearestNodeModules(filepath.Dir(location))
419
+ if nodeModules == "" {
420
+ return filepath.Dir(location)
421
+ }
422
+ // Resolve a linked node_modules (junction/symlink — common in managed
423
+ // setups) before descending into it: the ESM runtime realpaths the loader
424
+ // module at import time, and a relative config specifier computed from the
425
+ // link-form path would resolve against the wrong directory. NTFS junctions
426
+ // defeat filepath.EvalSymlinks, so the link component is chased by hand
427
+ // first. Realpathing may also land on another volume, which defeats the
428
+ // whole point — fall back to the config's directory then.
429
+ base := filepath.Join(stripResolveDirLink(nodeModules), ".cache")
430
+ if err := os.MkdirAll(base, 0o755); err != nil {
431
+ return filepath.Dir(location)
432
+ }
433
+ real, err := filepath.EvalSymlinks(base)
434
+ if err != nil || !strings.EqualFold(filepath.VolumeName(real), filepath.VolumeName(location)) {
435
+ return filepath.Dir(location)
436
+ }
437
+ return real
438
+ }
439
+
440
+ // stripResolveDirLink chases a directory that is itself a symlink or NTFS junction
441
+ // to its target (bounded against link cycles). os.Readlink is the probe:
442
+ // it resolves junctions, which report neither ModeSymlink nor an
443
+ // EvalSymlinks-traversable path.
444
+ func stripResolveDirLink(dir string) string {
445
+ for i := 0; i < 8; i++ {
446
+ target, err := os.Readlink(dir)
447
+ if err != nil {
448
+ return dir
449
+ }
450
+ if !filepath.IsAbs(target) {
451
+ target = filepath.Join(filepath.Dir(dir), target)
452
+ }
453
+ dir = target
454
+ }
455
+ return dir
456
+ }
457
+
386
458
  // stripTtsxCommandContext returns an exec.Cmd that runs ttsx with the given
387
459
  // arguments, routing through node when the resolved binary is a script file.
388
460
  func stripTtsxCommandContext(ctx context.Context, args ...string) *exec.Cmd {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/strip",
3
- "version": "0.16.7",
3
+ "version": "0.16.9",
4
4
  "description": "First-party ttsc plugin that removes configured calls and statements from emitted JavaScript.",
5
5
  "main": "src/index.cjs",
6
6
  "types": "src/index.d.ts",