@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 +1 -1
- package/plugin/banner.go +120 -106
- package/plugin/main.go +26 -19
package/package.json
CHANGED
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
"encoding/json"
|
|
10
|
+
"flag"
|
|
11
|
+
"fmt"
|
|
12
|
+
"os"
|
|
13
|
+
"path/filepath"
|
|
14
|
+
"strings"
|
|
10
15
|
)
|
|
11
16
|
|
|
12
17
|
type pluginEntry struct {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
5
|
-
|
|
10
|
+
"fmt"
|
|
11
|
+
"os"
|
|
6
12
|
)
|
|
7
13
|
|
|
8
14
|
const version = "0.0.1"
|
|
9
15
|
|
|
10
16
|
func main() {
|
|
11
|
-
|
|
17
|
+
os.Exit(run(os.Args[1:]))
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
func run(args []string) int {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
}
|