@ttsc/banner 0.13.0 → 0.14.0-dev.20260528

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.
package/driver/banner.go CHANGED
@@ -2,16 +2,24 @@ package banner
2
2
 
3
3
  import (
4
4
  "bytes"
5
+ "context"
5
6
  "encoding/json"
6
7
  "fmt"
7
8
  "os"
8
9
  "os/exec"
9
10
  "path/filepath"
11
+ "runtime"
10
12
  "strings"
13
+ "time"
11
14
 
12
15
  "github.com/samchon/ttsc/packages/ttsc/driver"
13
16
  )
14
17
 
18
+ // configLoaderTimeout caps subprocesses that evaluate user-supplied banner
19
+ // config files. This matches the strip/lint loaders so a hanging config does
20
+ // not block the compiler indefinitely.
21
+ const configLoaderTimeout = 60 * time.Second
22
+
15
23
  func init() {
16
24
  driver.RegisterPlugin(plugin{})
17
25
  }
@@ -296,8 +304,18 @@ const { pathToFileURL } = require("node:url");
296
304
 
297
305
  (async () => {
298
306
  const mod = await import(pathToFileURL(process.argv[1]).href);
299
- const candidate = Object.prototype.hasOwnProperty.call(mod, "default") ? mod.default : mod;
300
- const value = typeof candidate === "function" ? await candidate() : candidate;
307
+ let current = Object.prototype.hasOwnProperty.call(mod, "default") ? mod.default : mod;
308
+ for (let i = 0; i < 8; i++) {
309
+ if (current !== null && typeof current === "object" && typeof current.text === "string") {
310
+ break;
311
+ }
312
+ if (current !== null && typeof current === "object" && Object.prototype.hasOwnProperty.call(current, "default")) {
313
+ current = current.default;
314
+ continue;
315
+ }
316
+ break;
317
+ }
318
+ const value = typeof current === "function" ? await current() : current;
301
319
  process.stdout.write(JSON.stringify(toSerializableBanner(value)));
302
320
  })().catch((error) => {
303
321
  process.stderr.write(error && error.stack ? error.stack : String(error));
@@ -315,10 +333,15 @@ function toSerializableBanner(value) {
315
333
  if node == "" {
316
334
  node = "node"
317
335
  }
318
- cmd := exec.Command(node, "-e", script, location)
336
+ ctx, cancel := context.WithTimeout(context.Background(), configLoaderTimeout)
337
+ defer cancel()
338
+ cmd := exec.CommandContext(ctx, node, "-e", script, location)
319
339
  cmd.Env = nodeConfigLoaderEnv(location)
320
340
  output, err := cmd.Output()
321
341
  if err != nil {
342
+ if ctx.Err() == context.DeadlineExceeded {
343
+ return nil, fmt.Errorf("@ttsc/banner: load config file %s: timed out after %s", location, configLoaderTimeout)
344
+ }
322
345
  stderr := ""
323
346
  if exit, ok := err.(*exec.ExitError); ok {
324
347
  stderr = strings.TrimSpace(string(exit.Stderr))
@@ -376,10 +399,15 @@ func loadBannerTypeScriptConfigFile(location string) (any, error) {
376
399
  }
377
400
  args = append(args, loader)
378
401
 
379
- cmd := ttsxCommand(args...)
402
+ ctx, cancel := context.WithTimeout(context.Background(), configLoaderTimeout)
403
+ defer cancel()
404
+ cmd := ttsxCommandContext(ctx, args...)
380
405
  cmd.Env = nodeConfigLoaderEnv(location)
381
406
  output, err := cmd.Output()
382
407
  if err != nil {
408
+ if ctx.Err() == context.DeadlineExceeded {
409
+ return nil, fmt.Errorf("@ttsc/banner: load TypeScript config file %s: timed out after %s", location, configLoaderTimeout)
410
+ }
383
411
  stderr := ""
384
412
  if exit, ok := err.(*exec.ExitError); ok {
385
413
  stderr = strings.TrimSpace(string(exit.Stderr))
@@ -431,8 +459,11 @@ try {
431
459
  }
432
460
 
433
461
  async function resolveConfig(value: unknown): Promise<unknown> {
434
- let current = value;
462
+ let current = isObject(value) && hasOwn(value, "default") ? value.default : value;
435
463
  for (let i = 0; i < 8; i++) {
464
+ if (isBannerObject(current)) {
465
+ break;
466
+ }
436
467
  if (isObject(current) && hasOwn(current, "default")) {
437
468
  current = current.default;
438
469
  continue;
@@ -449,6 +480,10 @@ function isObject(value: unknown): value is Record<string, unknown> {
449
480
  return value !== null && typeof value === "object";
450
481
  }
451
482
 
483
+ function isBannerObject(value: unknown): value is { text: string } {
484
+ return isObject(value) && typeof value.text === "string";
485
+ }
486
+
452
487
  function hasOwn(value: Record<string, unknown>, key: string): boolean {
453
488
  return Object.prototype.hasOwnProperty.call(value, key);
454
489
  }
@@ -490,6 +525,11 @@ func typeScriptConfigLoaderTsconfig(loader, location, outDir string) string {
490
525
  // When TTSC_TTSX_BINARY has a script extension (.js, .ts, …) the binary is
491
526
  // invoked via the Node runtime so it is executed correctly on all platforms.
492
527
  func ttsxCommand(args ...string) *exec.Cmd {
528
+ return ttsxCommandContext(context.Background(), args...)
529
+ }
530
+
531
+ // ttsxCommandContext is the timeout-aware variant used by config loaders.
532
+ func ttsxCommandContext(ctx context.Context, args ...string) *exec.Cmd {
493
533
  ttsx := os.Getenv("TTSC_TTSX_BINARY")
494
534
  if ttsx == "" {
495
535
  ttsx = "ttsx"
@@ -499,9 +539,9 @@ func ttsxCommand(args ...string) *exec.Cmd {
499
539
  if node == "" {
500
540
  node = "node"
501
541
  }
502
- return exec.Command(node, append([]string{ttsx}, args...)...)
542
+ return exec.CommandContext(ctx, node, append([]string{ttsx}, args...)...)
503
543
  }
504
- return exec.Command(ttsx, args...)
544
+ return exec.CommandContext(ctx, ttsx, args...)
505
545
  }
506
546
 
507
547
  // shouldRunTtsxThroughNode reports whether binary has a script file extension
@@ -541,8 +581,25 @@ func linkNearestNodeModules(tempDir, sourceDir string) error {
541
581
  return nil
542
582
  }
543
583
  link := filepath.Join(tempDir, "node_modules")
544
- if err := os.Symlink(nodeModules, link); err != nil {
545
- return fmt.Errorf("@ttsc/banner: link config node_modules %s: %w", nodeModules, err)
584
+ err := os.Symlink(nodeModules, link)
585
+ if err == nil {
586
+ return nil
587
+ }
588
+ if runtime.GOOS == "windows" {
589
+ jerr := createWindowsJunction(link, nodeModules)
590
+ if jerr == nil {
591
+ return nil
592
+ }
593
+ err = fmt.Errorf("%w (junction fallback: %v)", err, jerr)
594
+ }
595
+ return fmt.Errorf("@ttsc/banner: link config node_modules %s: %w", nodeModules, err)
596
+ }
597
+
598
+ // createWindowsJunction creates a directory junction on Windows.
599
+ func createWindowsJunction(link, target string) error {
600
+ cmd := exec.Command("cmd", "/c", "mklink", "/J", link, target)
601
+ if out, err := cmd.CombinedOutput(); err != nil {
602
+ return fmt.Errorf("mklink /J failed: %v: %s", err, strings.TrimSpace(string(out)))
546
603
  }
547
604
  return nil
548
605
  }
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,0DAA6B;AAI7B,qDAAmC;AA4CnC;;;;GAIG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAS;IACrC,SAAS;IACT,MAAM;IACN,OAAO;IACP,WAAW;CACZ,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,0BACE,OAA0D;IAE1D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAiC,CAAC;IACxD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YACrD,MAAM,IAAI,KAAK,CACb,gEAAgE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI;gBACrF,sFAAsF;gBACtF,iGAAiG,CACpG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,iEAAiE;QACjE,sDAAsD;QACtD,MAAM,EAAE,mBAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC;QAC/C,KAAK,EAAE,WAAW;KACnB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,0DAA6B;AAI7B,qDAAmC;AAyCnC;;;;GAIG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAS;IACrC,SAAS;IACT,MAAM;IACN,OAAO;IACP,WAAW;CACZ,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,0BACE,OAA0D;IAE1D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAiC,CAAC;IACxD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YACrD,MAAM,IAAI,KAAK,CACb,gEAAgE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI;gBACrF,sFAAsF;gBACtF,iGAAiG,CACpG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,iEAAiE;QACjE,sDAAsD;QACtD,MAAM,EAAE,mBAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC;QAC/C,KAAK,EAAE,WAAW;KACnB,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/banner",
3
- "version": "0.13.0",
3
+ "version": "0.14.0-dev.20260528",
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",
@@ -33,9 +33,9 @@
33
33
  ],
34
34
  "devDependencies": {
35
35
  "@types/node": "^25.3.0",
36
- "@typescript/native-preview": "7.0.0-dev.20260522.1",
36
+ "@typescript/native-preview": "7.0.0-dev.20260527.2",
37
37
  "rimraf": "^6.1.2",
38
- "ttsc": "0.13.0"
38
+ "ttsc": "0.14.0-dev.20260528"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",
package/src/index.ts CHANGED
@@ -7,9 +7,9 @@ export * from "./structures/index";
7
7
  /**
8
8
  * The shape returned by a ttsc plugin factory function.
9
9
  *
10
- * Mirrors the `driver.PluginDescriptor` contract in the Go host. The `source`
11
- * field points to the Go source directory that the host will compile into a
12
- * plugin binary (cached under `.ttsc/`).
10
+ * Mirrors the ttsc plugin descriptor contract. The `source` field points to the
11
+ * Go source directory that the host will compile and cache as either an
12
+ * executable sidecar or linked native source.
13
13
  */
14
14
  type TtscPluginDescriptor = {
15
15
  /** Human-readable plugin name used in logs and error messages. */
@@ -18,7 +18,7 @@ type TtscPluginDescriptor = {
18
18
  source: string;
19
19
  /**
20
20
  * Pipeline stage. `"transform"` plugins may rewrite source files; `"check"`
21
- * plugins only produce diagnostics. Defaults to `"check"`.
21
+ * plugins only produce diagnostics. The framework default is `"transform"`.
22
22
  */
23
23
  stage?: "check" | "transform";
24
24
  };
@@ -31,10 +31,7 @@ type TtscPluginDescriptor = {
31
31
  * factories ignore it.
32
32
  */
33
33
  type TtscPluginFactoryContext<TConfig> = {
34
- /**
35
- * Absolute path to the compiled plugin binary (not yet built at factory
36
- * time).
37
- */
34
+ /** Absolute path to the selected ttsc native helper, not a plugin binary. */
38
35
  binary: string;
39
36
  /** Working directory of the ttsc invocation. */
40
37
  cwd: string;