@ttsc/banner 0.7.1 → 0.7.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/banner",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "First-party ttsc plugin that prepends a banner comment to emitted files.",
5
5
  "main": "src/index.cjs",
6
6
  "exports": {
package/plugin/banner.go CHANGED
@@ -1,134 +1,148 @@
1
+ // Output transformer for `@ttsc/banner`.
2
+ //
3
+ // The transformer prepends a configured banner to JavaScript and declaration
4
+ // outputs. Source maps and tsbuildinfo files are excluded because prepending
5
+ // human-readable text would corrupt their structured content.
1
6
  package main
2
7
 
3
8
  import (
4
- "encoding/json"
5
- "flag"
6
- "fmt"
7
- "os"
8
- "path/filepath"
9
- "strings"
9
+ "encoding/json"
10
+ "flag"
11
+ "fmt"
12
+ "os"
13
+ "path/filepath"
14
+ "strings"
10
15
  )
11
16
 
12
17
  type pluginEntry struct {
13
- Config map[string]any `json:"config"`
14
- Name string `json:"name"`
15
- Stage string `json:"stage"`
18
+ // Config carries the original compilerOptions.plugins[] config object. The
19
+ // sidecar scans the ordered descriptor list and consumes only its own entry.
20
+ Config map[string]any `json:"config"`
21
+ Name string `json:"name"`
22
+ Stage string `json:"stage"`
16
23
  }
17
24
 
25
+ // RunOutput implements the output-stage sidecar command. The host passes an
26
+ // emitted file path, the serialized plugin manifest, and an optional alternate
27
+ // output path.
18
28
  func RunOutput(args []string) int {
19
- fs := flag.NewFlagSet("output", flag.ContinueOnError)
20
- fs.SetOutput(os.Stderr)
21
- file := fs.String("file", "", "emitted file to transform")
22
- out := fs.String("out", "", "write transformed text to this file instead of updating --file")
23
- _ = fs.String("cwd", "", "project directory")
24
- _ = fs.String("outDir", "", "emit directory override")
25
- pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
26
- _ = fs.String("tsconfig", "tsconfig.json", "project tsconfig")
27
- if err := fs.Parse(args); err != nil {
28
- return 2
29
- }
30
- if *file == "" {
31
- fmt.Fprintln(os.Stderr, "@ttsc/banner: output requires --file")
32
- return 2
33
- }
34
- config, err := findConfig(*pluginsJSON)
35
- if err != nil {
36
- fmt.Fprintln(os.Stderr, err)
37
- return 2
38
- }
39
- text, err := os.ReadFile(*file)
40
- if err != nil {
41
- fmt.Fprintf(os.Stderr, "@ttsc/banner: read %s: %v\n", *file, err)
42
- return 2
43
- }
44
- patched, err := Apply(*file, string(text), config)
45
- if err != nil {
46
- fmt.Fprintln(os.Stderr, err)
47
- return 2
48
- }
49
- target := *file
50
- if *out != "" {
51
- target = *out
52
- }
53
- if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
54
- fmt.Fprintf(os.Stderr, "@ttsc/banner: mkdir: %v\n", err)
55
- return 2
56
- }
57
- if err := os.WriteFile(target, []byte(patched), 0o644); err != nil {
58
- fmt.Fprintf(os.Stderr, "@ttsc/banner: write %s: %v\n", target, err)
59
- return 2
60
- }
61
- return 0
29
+ fs := flag.NewFlagSet("output", flag.ContinueOnError)
30
+ fs.SetOutput(os.Stderr)
31
+ file := fs.String("file", "", "emitted file to transform")
32
+ out := fs.String("out", "", "write transformed text to this file instead of updating --file")
33
+ _ = fs.String("cwd", "", "project directory")
34
+ _ = fs.String("outDir", "", "emit directory override")
35
+ pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
36
+ _ = fs.String("tsconfig", "tsconfig.json", "project tsconfig")
37
+ if err := fs.Parse(args); err != nil {
38
+ return 2
39
+ }
40
+ if *file == "" {
41
+ fmt.Fprintln(os.Stderr, "@ttsc/banner: output requires --file")
42
+ return 2
43
+ }
44
+ config, err := findConfig(*pluginsJSON)
45
+ if err != nil {
46
+ fmt.Fprintln(os.Stderr, err)
47
+ return 2
48
+ }
49
+ text, err := os.ReadFile(*file)
50
+ if err != nil {
51
+ fmt.Fprintf(os.Stderr, "@ttsc/banner: read %s: %v\n", *file, err)
52
+ return 2
53
+ }
54
+ patched, err := Apply(*file, string(text), config)
55
+ if err != nil {
56
+ fmt.Fprintln(os.Stderr, err)
57
+ return 2
58
+ }
59
+ target := *file
60
+ if *out != "" {
61
+ target = *out
62
+ }
63
+ if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
64
+ fmt.Fprintf(os.Stderr, "@ttsc/banner: mkdir: %v\n", err)
65
+ return 2
66
+ }
67
+ if err := os.WriteFile(target, []byte(patched), 0o644); err != nil {
68
+ fmt.Fprintf(os.Stderr, "@ttsc/banner: write %s: %v\n", target, err)
69
+ return 2
70
+ }
71
+ return 0
62
72
  }
63
73
 
64
74
  func Apply(fileName string, text string, config map[string]any) (string, error) {
65
- if !isBannerableOutput(fileName) {
66
- return text, nil
67
- }
68
- banner, err := parseBanner(config)
69
- if err != nil {
70
- return "", err
71
- }
72
- if strings.HasPrefix(text, banner) {
73
- return text, nil
74
- }
75
- return banner + text, nil
75
+ if !isBannerableOutput(fileName) {
76
+ return text, nil
77
+ }
78
+ banner, err := parseBanner(config)
79
+ if err != nil {
80
+ return "", err
81
+ }
82
+ // The operation is idempotent so watch builds do not duplicate banners when
83
+ // the same output file is seen more than once.
84
+ if strings.HasPrefix(text, banner) {
85
+ return text, nil
86
+ }
87
+ return banner + text, nil
76
88
  }
77
89
 
78
90
  func parseBanner(config map[string]any) (string, error) {
79
- raw, ok := config["banner"]
80
- if !ok {
81
- return "", fmt.Errorf("@ttsc/banner: \"banner\" must be a non-empty string")
82
- }
83
- text, ok := raw.(string)
84
- if !ok || strings.TrimSpace(text) == "" {
85
- return "", fmt.Errorf("@ttsc/banner: \"banner\" must be a non-empty string")
86
- }
87
- if !strings.HasSuffix(text, "\n") {
88
- text += "\n"
89
- }
90
- return text, nil
91
+ raw, ok := config["banner"]
92
+ if !ok {
93
+ return "", fmt.Errorf("@ttsc/banner: \"banner\" must be a non-empty string")
94
+ }
95
+ text, ok := raw.(string)
96
+ if !ok || strings.TrimSpace(text) == "" {
97
+ return "", fmt.Errorf("@ttsc/banner: \"banner\" must be a non-empty string")
98
+ }
99
+ if !strings.HasSuffix(text, "\n") {
100
+ // The banner is line-oriented; forcing a trailing newline keeps the first
101
+ // emitted statement on its own line.
102
+ text += "\n"
103
+ }
104
+ return text, nil
91
105
  }
92
106
 
93
107
  func findConfig(pluginsJSON string) (map[string]any, error) {
94
- if strings.TrimSpace(pluginsJSON) == "" {
95
- return nil, fmt.Errorf("@ttsc/banner: missing --plugins-json")
96
- }
97
- var entries []pluginEntry
98
- if err := json.Unmarshal([]byte(pluginsJSON), &entries); err != nil {
99
- return nil, fmt.Errorf("@ttsc/banner: invalid --plugins-json: %w", err)
100
- }
101
- for _, entry := range entries {
102
- if entry.Name == "@ttsc/banner" {
103
- if entry.Config == nil {
104
- return map[string]any{}, nil
105
- }
106
- return entry.Config, nil
107
- }
108
- }
109
- return nil, fmt.Errorf("@ttsc/banner: plugin entry not found")
108
+ if strings.TrimSpace(pluginsJSON) == "" {
109
+ return nil, fmt.Errorf("@ttsc/banner: missing --plugins-json")
110
+ }
111
+ var entries []pluginEntry
112
+ if err := json.Unmarshal([]byte(pluginsJSON), &entries); err != nil {
113
+ return nil, fmt.Errorf("@ttsc/banner: invalid --plugins-json: %w", err)
114
+ }
115
+ for _, entry := range entries {
116
+ if entry.Name == "@ttsc/banner" {
117
+ if entry.Config == nil {
118
+ return map[string]any{}, nil
119
+ }
120
+ return entry.Config, nil
121
+ }
122
+ }
123
+ return nil, fmt.Errorf("@ttsc/banner: plugin entry not found")
110
124
  }
111
125
 
112
126
  func isBannerableOutput(fileName string) bool {
113
- lower := strings.ToLower(fileName)
114
- if strings.HasSuffix(lower, ".map") || strings.HasSuffix(lower, ".tsbuildinfo") {
115
- return false
116
- }
117
- return isJavaScriptOutput(fileName) || isDeclarationOutput(fileName)
127
+ lower := strings.ToLower(fileName)
128
+ if strings.HasSuffix(lower, ".map") || strings.HasSuffix(lower, ".tsbuildinfo") {
129
+ return false
130
+ }
131
+ return isJavaScriptOutput(fileName) || isDeclarationOutput(fileName)
118
132
  }
119
133
 
120
134
  func isJavaScriptOutput(fileName string) bool {
121
- switch strings.ToLower(filepath.Ext(fileName)) {
122
- case ".js", ".mjs", ".cjs":
123
- return true
124
- default:
125
- return false
126
- }
135
+ switch strings.ToLower(filepath.Ext(fileName)) {
136
+ case ".js", ".mjs", ".cjs":
137
+ return true
138
+ default:
139
+ return false
140
+ }
127
141
  }
128
142
 
129
143
  func isDeclarationOutput(fileName string) bool {
130
- lower := strings.ToLower(fileName)
131
- return strings.HasSuffix(lower, ".d.ts") ||
132
- strings.HasSuffix(lower, ".d.mts") ||
133
- strings.HasSuffix(lower, ".d.cts")
144
+ lower := strings.ToLower(fileName)
145
+ return strings.HasSuffix(lower, ".d.ts") ||
146
+ strings.HasSuffix(lower, ".d.mts") ||
147
+ strings.HasSuffix(lower, ".d.cts")
134
148
  }
package/plugin/main.go CHANGED
@@ -1,31 +1,38 @@
1
+ // Native sidecar entrypoint for `@ttsc/banner`.
2
+ //
3
+ // The command follows the ttsc plugin sidecar protocol: `check` validates the
4
+ // plugin can run, `output` receives one emitted file and may rewrite it, and
5
+ // `version` reports the sidecar version. All project semantics stay in the
6
+ // host; this binary only prepends configured banner text.
1
7
  package main
2
8
 
3
9
  import (
4
- "fmt"
5
- "os"
10
+ "fmt"
11
+ "os"
6
12
  )
7
13
 
8
14
  const version = "0.0.1"
9
15
 
10
16
  func main() {
11
- os.Exit(run(os.Args[1:]))
17
+ os.Exit(run(os.Args[1:]))
12
18
  }
13
19
 
14
20
  func run(args []string) int {
15
- if len(args) == 0 {
16
- fmt.Fprintln(os.Stderr, "@ttsc/banner: command required (expected output|version)")
17
- return 2
18
- }
19
- switch args[0] {
20
- case "-v", "--version", "version":
21
- fmt.Fprintf(os.Stdout, "@ttsc/banner %s\n", version)
22
- return 0
23
- case "check":
24
- return 0
25
- case "output":
26
- return RunOutput(args[1:])
27
- default:
28
- fmt.Fprintf(os.Stderr, "@ttsc/banner: unknown command %q\n", args[0])
29
- return 2
30
- }
21
+ if len(args) == 0 {
22
+ fmt.Fprintln(os.Stderr, "@ttsc/banner: command required (expected output|version)")
23
+ return 2
24
+ }
25
+ switch args[0] {
26
+ case "-v", "--version", "version":
27
+ fmt.Fprintf(os.Stdout, "@ttsc/banner %s\n", version)
28
+ return 0
29
+ case "check":
30
+ // Banner rewriting is output-only; there is no program-wide analysis.
31
+ return 0
32
+ case "output":
33
+ return RunOutput(args[1:])
34
+ default:
35
+ fmt.Fprintf(os.Stderr, "@ttsc/banner: unknown command %q\n", args[0])
36
+ return 2
37
+ }
31
38
  }