@ttsc/banner 0.12.4 → 0.13.0

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/README.md CHANGED
@@ -20,45 +20,56 @@ npm install -D ttsc @typescript/native-preview
20
20
  npm install -D @ttsc/banner
21
21
  ```
22
22
 
23
- Pass the banner text inline on the `compilerOptions.plugins[]` entry:
23
+ Register the plugin in your `tsconfig.json`:
24
24
 
25
25
  ```jsonc
26
26
  // tsconfig.json
27
27
  {
28
28
  "compilerOptions": {
29
29
  "plugins": [
30
- { "transform": "@ttsc/banner", "text": "License MIT (c) 2026 Acme" }
30
+ { "transform": "@ttsc/banner" }
31
31
  ]
32
32
  }
33
33
  }
34
34
  ```
35
35
 
36
- Or keep the text in a separate file `banner.config.ts` next to your tsconfig:
36
+ Drop a `banner.config.ts` next to your `tsconfig.json`:
37
37
 
38
38
  ```ts
39
39
  // banner.config.ts
40
- import type { TtscBannerConfig } from "@ttsc/banner";
40
+ import type { ITtscBannerConfig } from "@ttsc/banner";
41
41
 
42
42
  export default {
43
43
  text: "License MIT (c) 2026 Acme",
44
- } satisfies TtscBannerConfig;
44
+ } satisfies ITtscBannerConfig;
45
45
  ```
46
46
 
47
+ A `banner.config.*` file always exports an object with a `text` string.
48
+
47
49
  Run your normal `ttsc` command:
48
50
 
49
51
  ```bash
50
52
  npx ttsc
51
53
  ```
52
54
 
53
- If `@ttsc/banner` is installed and none of the three text sources (inline `text:`, `config:` path, or an auto-discovered `banner.config.*`) yields a banner, the compile fails.
55
+ If `@ttsc/banner` is installed and no `banner.config.*` file is found, the compile fails.
54
56
 
55
57
  ## Configuration
56
58
 
57
- `@ttsc/banner` resolves its text in this order:
59
+ `@ttsc/banner` discovers its config by walking upward from the tsconfig directory, looking for `banner.config.{ts,cts,mts,js,cjs,mjs,json}`.
60
+
61
+ To point at a specific file instead of using auto-discovery, set `configFile` on the tsconfig entry:
58
62
 
59
- 1. the entry's inline `text` string;
60
- 2. the entry's `config: "./path/to/banner.config.ts"` path;
61
- 3. an upward walk for any `banner.config.{js,cjs,mjs,ts,mts,cts}` starting from the tsconfig directory.
63
+ ```jsonc
64
+ // tsconfig.json
65
+ {
66
+ "compilerOptions": {
67
+ "plugins": [
68
+ { "transform": "@ttsc/banner", "configFile": "./config/banner.config.ts" }
69
+ ]
70
+ }
71
+ }
72
+ ```
62
73
 
63
74
  The plugin formats every line of the resolved text inside a JSDoc block and appends `@packageDocumentation`.
64
75
 
package/driver/banner.go CHANGED
@@ -1,6 +1,7 @@
1
1
  package banner
2
2
 
3
3
  import (
4
+ "bytes"
4
5
  "encoding/json"
5
6
  "fmt"
6
7
  "os"
@@ -25,6 +26,35 @@ var (
25
26
  writeConfigLoaderFile = os.WriteFile
26
27
  )
27
28
 
29
+ // frameworkKeys lists the tsconfig plugin-entry keys that the ttsc host
30
+ // framework owns. They are accepted without error; all other keys are rejected.
31
+ var frameworkKeys = map[string]struct{}{
32
+ "enabled": {},
33
+ "name": {},
34
+ "stage": {},
35
+ "transform": {},
36
+ }
37
+
38
+ // validateBannerConfig rejects any tsconfig plugin entry key that is not a
39
+ // known framework key and is not the single banner-specific "configFile" key.
40
+ func validateBannerConfig(config map[string]any) error {
41
+ for key := range config {
42
+ if _, ok := frameworkKeys[key]; ok {
43
+ continue
44
+ }
45
+ if key == "configFile" {
46
+ continue
47
+ }
48
+ return fmt.Errorf(
49
+ "@ttsc/banner: tsconfig plugin entry contains unsupported key %q. "+
50
+ "Banner options must be placed in a banner.config.{ts,cts,mts,js,cjs,mjs,json} file. "+
51
+ "The only accepted key in the tsconfig entry is \"configFile\" (optional path to the config file).",
52
+ key,
53
+ )
54
+ }
55
+ return nil
56
+ }
57
+
28
58
  // SourcePreamble resolves the banner text from the plugin config and returns it
29
59
  // formatted as a JSDoc block comment suitable for prepending to each emitted file.
30
60
  func (plugin) SourcePreamble(ctx driver.PluginContext) (string, error) {
@@ -60,18 +90,22 @@ func parseBanner(config map[string]any, cwd, tsconfigPath string) (string, error
60
90
  }
61
91
 
62
92
  // resolveBannerText extracts the banner text from the plugin config.
63
- // It tries, in order: inline "text" key, explicit "config" path, auto-discovered
64
- // banner.config.* file. Returns an error when none of these sources provides text.
93
+ // The config entry is validated first: only the "configFile" key (plus
94
+ // framework keys) is accepted. When "configFile" is present its value is
95
+ // resolved to an absolute path and loaded. When absent the upward-walk
96
+ // discovery is used. Returns an error when the config is invalid or when
97
+ // no banner text can be found.
65
98
  func resolveBannerText(config map[string]any, cwd, tsconfigPath string) (string, error) {
66
- if text, ok, err := bannerTextFromConfigValue(config["text"], `"text"`); ok || err != nil {
67
- return text, err
99
+ if err := validateBannerConfig(config); err != nil {
100
+ return "", err
68
101
  }
69
- if rawConfigPath, ok := config["config"]; ok {
70
- configPath, ok := rawConfigPath.(string)
71
- if !ok || strings.TrimSpace(configPath) == "" {
72
- return "", fmt.Errorf("@ttsc/banner: \"config\" must be a non-empty string path")
102
+
103
+ if rawConfigFile, ok := config["configFile"]; ok {
104
+ configFile, ok := rawConfigFile.(string)
105
+ if !ok || strings.TrimSpace(configFile) == "" {
106
+ return "", fmt.Errorf("@ttsc/banner: \"configFile\" must be a non-empty string path")
73
107
  }
74
- location := resolveBannerConfigPath(configPath, cwd, tsconfigPath)
108
+ location := resolveBannerConfigPath(configFile, cwd, tsconfigPath)
75
109
  raw, err := loadBannerConfigFile(location)
76
110
  if err != nil {
77
111
  return "", err
@@ -81,16 +115,17 @@ func resolveBannerText(config map[string]any, cwd, tsconfigPath string) (string,
81
115
  return "", err
82
116
  }
83
117
  if !ok {
84
- return "", fmt.Errorf("@ttsc/banner: %s must export a non-empty string or an object with a non-empty \"text\" string", location)
118
+ return "", fmt.Errorf("@ttsc/banner: %s must export an object with a non-empty \"text\" string", location)
85
119
  }
86
120
  return text, nil
87
121
  }
122
+
88
123
  location, err := findBannerConfigFile(cwd, tsconfigPath)
89
124
  if err != nil {
90
125
  return "", err
91
126
  }
92
127
  if location == "" {
93
- return "", fmt.Errorf("@ttsc/banner: \"text\" must be a non-empty string or a banner.config.{js,cjs,mjs,ts,mts,cts} file must exist")
128
+ return "", fmt.Errorf("@ttsc/banner: no banner.config.{ts,cts,mts,js,cjs,mjs,json} file found; create one or set \"configFile\" in the tsconfig plugin entry")
94
129
  }
95
130
  raw, err := loadBannerConfigFile(location)
96
131
  if err != nil {
@@ -101,36 +136,30 @@ func resolveBannerText(config map[string]any, cwd, tsconfigPath string) (string,
101
136
  return "", err
102
137
  }
103
138
  if !ok {
104
- return "", fmt.Errorf("@ttsc/banner: %s must export a non-empty string or an object with a non-empty \"text\" string", location)
139
+ return "", fmt.Errorf("@ttsc/banner: %s must export an object with a non-empty \"text\" string", location)
105
140
  }
106
141
  return text, nil
107
142
  }
108
143
 
109
144
  // bannerTextFromConfigValue extracts a banner text string from a config value.
110
- // raw may be a string, a map with a "text" key, or nil (not present).
111
- // Returns (text, true, nil) on success, ("", false, nil) when absent, or
112
- // ("", true, err) / ("", false, err) on a type mismatch. label is used in
113
- // error messages and should describe the config source (e.g. "\"text\"").
145
+ // A banner config value must be an object with a non-empty "text" string; raw
146
+ // may also be nil (not present). Returns (text, true, nil) on success,
147
+ // ("", false, nil) when absent, or ("", true, err) on a type mismatch. label is
148
+ // used in error messages and is the config file's base name (e.g.
149
+ // "banner.config.json").
114
150
  func bannerTextFromConfigValue(raw any, label string) (string, bool, error) {
115
151
  if raw == nil {
116
152
  return "", false, nil
117
153
  }
118
- text, ok := raw.(string)
119
- if ok {
120
- if strings.TrimSpace(text) == "" {
121
- return "", true, fmt.Errorf("@ttsc/banner: %s must be a non-empty string", label)
122
- }
123
- return text, true, nil
124
- }
125
154
  object, ok := raw.(map[string]any)
126
155
  if !ok {
127
- return "", true, fmt.Errorf("@ttsc/banner: %s must be a string or an object with a non-empty \"text\" string", label)
156
+ return "", true, fmt.Errorf("@ttsc/banner: %s must be an object with a non-empty \"text\" string", label)
128
157
  }
129
158
  rawText, ok := object["text"]
130
159
  if !ok {
131
160
  return "", false, nil
132
161
  }
133
- text, ok = rawText.(string)
162
+ text, ok := rawText.(string)
134
163
  if !ok || strings.TrimSpace(text) == "" {
135
164
  return "", true, fmt.Errorf("@ttsc/banner: %s.text must be a non-empty string", label)
136
165
  }
@@ -138,14 +167,15 @@ func bannerTextFromConfigValue(raw any, label string) (string, bool, error) {
138
167
  }
139
168
 
140
169
  // findBannerConfigFile walks up from the tsconfig (or cwd) directory looking for
141
- // a banner.config.{js,cjs,mjs,ts,cts,mts} file. Returns the path when exactly
170
+ // a banner.config.{ts,cts,mts,js,cjs,mjs,json} file. Returns the path when exactly
142
171
  // one match is found per directory, "" when none exists at any level, or an
143
172
  // error when multiple candidates exist in the same directory.
144
173
  func findBannerConfigFile(cwd, tsconfigPath string) (string, error) {
145
- dir := discoveryConfigBaseDir(cwd, tsconfigPath)
174
+ dir := tsconfigBaseDir(cwd, tsconfigPath)
146
175
  for {
147
176
  matches := make([]string, 0, 1)
148
177
  for _, name := range []string{
178
+ "banner.config.json",
149
179
  "banner.config.js",
150
180
  "banner.config.cjs",
151
181
  "banner.config.mjs",
@@ -159,7 +189,14 @@ func findBannerConfigFile(cwd, tsconfigPath string) (string, error) {
159
189
  }
160
190
  }
161
191
  if len(matches) > 1 {
162
- return "", fmt.Errorf("@ttsc/banner: multiple banner.config.* files found in %s", dir)
192
+ names := make([]string, len(matches))
193
+ for i, match := range matches {
194
+ names[i] = filepath.Base(match)
195
+ }
196
+ return "", fmt.Errorf(
197
+ "@ttsc/banner: multiple banner config files found in %s (%s); set \"configFile\" explicitly in the tsconfig plugin entry",
198
+ dir, strings.Join(names, ", "),
199
+ )
163
200
  }
164
201
  if len(matches) == 1 {
165
202
  return matches[0], nil
@@ -183,7 +220,8 @@ func resolveBannerConfigPath(configPath, cwd, tsconfigPath string) string {
183
220
  }
184
221
 
185
222
  // tsconfigBaseDir returns the directory that contains the tsconfig file, or cwd
186
- // when tsconfigPath is empty. Used as the base for resolving explicit config paths.
223
+ // when tsconfigPath is empty. It is the base both for resolving an explicit
224
+ // `configFile` path and for starting the upward banner-config-file search.
187
225
  func tsconfigBaseDir(cwd, tsconfigPath string) string {
188
226
  if tsconfigPath == "" {
189
227
  return cwd
@@ -195,30 +233,20 @@ func tsconfigBaseDir(cwd, tsconfigPath string) string {
195
233
  return filepath.Dir(resolvedTsconfig)
196
234
  }
197
235
 
198
- // discoveryConfigBaseDir returns the starting directory for the upward banner
199
- // config file search. Semantically identical to tsconfigBaseDir; kept separate
200
- // to make the call sites self-documenting.
201
- func discoveryConfigBaseDir(cwd, tsconfigPath string) string {
202
- if tsconfigPath != "" {
203
- resolvedTsconfig := tsconfigPath
204
- if !filepath.IsAbs(resolvedTsconfig) {
205
- resolvedTsconfig = filepath.Join(cwd, resolvedTsconfig)
206
- }
207
- return filepath.Dir(resolvedTsconfig)
208
- }
209
- return cwd
210
- }
211
-
212
236
  // loadBannerConfigFile loads and evaluates a banner config file, returning its
213
- // exported value as a Go any (string or map[string]any). The file must be named
214
- // banner.config.{js,cjs,mjs,ts,cts,mts}; JS/CJS/MJS variants run under Node,
215
- // TypeScript variants compile and run via ttsx in a temp directory.
237
+ // exported value as a Go any. A valid banner config exports an object with a
238
+ // "text" string; the value is validated by bannerTextFromConfigValue. The file
239
+ // must be named banner.config.{ts,cts,mts,js,cjs,mjs,json}; JS/CJS/MJS variants
240
+ // run under Node, TypeScript variants compile and run via ttsx in a temp
241
+ // directory, and JSON files are parsed natively.
216
242
  func loadBannerConfigFile(location string) (any, error) {
217
243
  if !isBannerConfigFileName(filepath.Base(location)) {
218
- return nil, fmt.Errorf("@ttsc/banner: config file must be named banner.config.{js,cjs,mjs,ts,mts,cts}: %s", location)
244
+ return nil, fmt.Errorf("@ttsc/banner: config file must be named banner.config.{ts,cts,mts,js,cjs,mjs,json}: %s", location)
219
245
  }
220
246
  ext := strings.ToLower(filepath.Ext(location))
221
247
  switch ext {
248
+ case ".json":
249
+ return loadBannerJSONConfigFile(location)
222
250
  case ".js", ".cjs", ".mjs":
223
251
  return loadBannerScriptConfigFile(location)
224
252
  }
@@ -228,7 +256,8 @@ func loadBannerConfigFile(location string) (any, error) {
228
256
  // isBannerConfigFileName reports whether name is an allowed banner config file name.
229
257
  func isBannerConfigFileName(name string) bool {
230
258
  switch name {
231
- case "banner.config.js",
259
+ case "banner.config.json",
260
+ "banner.config.js",
232
261
  "banner.config.cjs",
233
262
  "banner.config.mjs",
234
263
  "banner.config.ts",
@@ -240,6 +269,24 @@ func isBannerConfigFileName(name string) bool {
240
269
  }
241
270
  }
242
271
 
272
+ // loadBannerJSONConfigFile reads and JSON-parses a banner config file. A leading
273
+ // UTF-8 BOM is stripped before parsing so files saved by Windows editors are
274
+ // accepted. The parsed value must be an object with a non-empty "text" string.
275
+ func loadBannerJSONConfigFile(location string) (any, error) {
276
+ body, err := os.ReadFile(location)
277
+ if err != nil {
278
+ return nil, fmt.Errorf("@ttsc/banner: read config file %s: %w", location, err)
279
+ }
280
+ // Strip a leading UTF-8 BOM so files saved by Windows editors round
281
+ // trip through json.Unmarshal without an opaque "invalid character" failure.
282
+ body = bytes.TrimPrefix(body, []byte{0xEF, 0xBB, 0xBF})
283
+ var out any
284
+ if err := json.Unmarshal(body, &out); err != nil {
285
+ return nil, fmt.Errorf("@ttsc/banner: parse config file %s: %w", location, err)
286
+ }
287
+ return out, nil
288
+ }
289
+
243
290
  // loadBannerScriptConfigFile evaluates a JS/CJS/MJS banner config file by
244
291
  // running a small Node.js loader script that dynamic-imports the file and
245
292
  // serializes its exported value to stdout as JSON.
@@ -258,13 +305,10 @@ const { pathToFileURL } = require("node:url");
258
305
  });
259
306
 
260
307
  function toSerializableBanner(value) {
261
- if (typeof value === "string") {
262
- return value;
263
- }
264
308
  if (value !== null && typeof value === "object" && typeof value.text === "string") {
265
309
  return { text: value.text };
266
310
  }
267
- throw new Error("config file must export a string or an object with a text string");
311
+ throw new Error("config file must export an object with a non-empty \"text\" string");
268
312
  }
269
313
  `
270
314
  node := os.Getenv("TTSC_NODE_BINARY")
@@ -293,7 +337,9 @@ function toSerializableBanner(value) {
293
337
 
294
338
  // loadBannerTypeScriptConfigFile compiles and runs a TypeScript banner config
295
339
  // file using ttsx in a temp directory. A symlink to the nearest node_modules
296
- // is created so the config file can import its own dependencies.
340
+ // is created so the config file can import its own dependencies. The ttsx
341
+ // build runs with `--no-plugins` so evaluating the config never triggers the
342
+ // host project's transform/check plugins against the loader tsconfig.
297
343
  func loadBannerTypeScriptConfigFile(location string) (any, error) {
298
344
  tempDir, err := os.MkdirTemp("", "ttsc-banner-config-")
299
345
  if err != nil {
@@ -323,6 +369,7 @@ func loadBannerTypeScriptConfigFile(location string) (any, error) {
323
369
  "--project", tsconfig,
324
370
  "--cwd", tempDir,
325
371
  "--cache-dir", filepath.Join(tempDir, "cache"),
372
+ "--no-plugins",
326
373
  }
327
374
  if tsgo := os.Getenv("TTSC_TSGO_BINARY"); tsgo != "" {
328
375
  args = append(args, "--binary", tsgo)
@@ -407,13 +454,10 @@ function hasOwn(value: Record<string, unknown>, key: string): boolean {
407
454
  }
408
455
 
409
456
  function toSerializableBanner(value: unknown): unknown {
410
- if (typeof value === "string") {
411
- return value;
412
- }
413
457
  if (isObject(value) && typeof value.text === "string") {
414
458
  return { text: value.text };
415
459
  }
416
- throw new Error("config file must export a string or an object with a text string");
460
+ throw new Error("config file must export an object with a non-empty \"text\" string");
417
461
  }
418
462
  `, importLiteral)
419
463
  }
package/lib/index.js CHANGED
@@ -20,18 +20,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.default = createTtscBanner;
21
21
  const node_path_1 = __importDefault(require("node:path"));
22
22
  __exportStar(require("./structures/index"), exports);
23
+ /**
24
+ * Keys that the ttsc plugin host injects into every plugin entry and are not
25
+ * owned by `@ttsc/banner`. These pass through the factory without validation so
26
+ * the host can freely add new framework keys in the future.
27
+ */
28
+ const FRAMEWORK_KEYS = new Set([
29
+ "enabled",
30
+ "name",
31
+ "stage",
32
+ "transform",
33
+ ]);
23
34
  /**
24
35
  * Plugin factory for `@ttsc/banner` — called by the ttsc host to obtain the
25
36
  * plugin descriptor.
26
37
  *
27
- * The factory is intentionally minimal: the banner plugin has no factory-level
28
- * configuration that would alter which Go source to compile. All banner options
29
- * (text, config path, enabled flag) are read at transform time by the Go
30
- * driver.
38
+ * The only banner-specific key accepted in the tsconfig plugin entry is
39
+ * `configFile`. Any other key that is not a known framework key is rejected
40
+ * with a specific error so users discover the correct configuration surface
41
+ * (the dedicated config file) rather than silently receiving no banner.
31
42
  *
32
43
  * @internal
33
44
  */
34
- function createTtscBanner(_context) {
45
+ function createTtscBanner(context) {
46
+ const entry = context.plugin;
47
+ for (const key of Object.keys(entry)) {
48
+ if (!FRAMEWORK_KEYS.has(key) && key !== "configFile") {
49
+ throw new Error(`@ttsc/banner: tsconfig plugin entry contains unsupported key ${JSON.stringify(key)}. ` +
50
+ `Banner options must be placed in a banner.config.{ts,cts,mts,js,cjs,mjs,json} file. ` +
51
+ `The only accepted key in the tsconfig entry is "configFile" (optional path to the config file).`);
52
+ }
53
+ }
35
54
  return {
36
55
  name: "@ttsc/banner",
37
56
  // Point at the `driver/` directory one level above `lib/` in the
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;;;;;;;;;;GAUG;AACH,0BACE,QAA2D;IAE3D,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;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,5 +1,5 @@
1
1
  /** Standalone `banner.config.*` file shape consumed by `@ttsc/banner`. */
2
- export type TtscBannerConfig = string | {
2
+ export interface ITtscBannerConfig {
3
3
  /** Text inserted into the generated `@packageDocumentation` block. */
4
4
  text: string;
5
- };
5
+ }
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=TtscBannerConfig.js.map
3
+ //# sourceMappingURL=ITtscBannerConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ITtscBannerConfig.js","sourceRoot":"","sources":["../../src/structures/ITtscBannerConfig.ts"],"names":[],"mappings":""}
@@ -4,10 +4,11 @@ export interface ITtscBannerPluginConfig {
4
4
  enabled?: boolean;
5
5
  /** Plugin module specifier. */
6
6
  transform?: string;
7
- /** Inline banner text. */
8
- text?: string;
9
- /** Path to `banner.config.*`, resolved from the selected tsconfig directory. */
10
- config?: string;
11
- /** Extra plugin-owned fields are passed through unchanged. */
12
- [key: string]: unknown;
7
+ /**
8
+ * Path to a `banner.config.*` file, resolved from the directory that contains
9
+ * the tsconfig. When omitted, `@ttsc/banner` walks up from that directory and
10
+ * uses the first `banner.config.{ts,cts,mts,js,cjs,mjs,json}` file it finds.
11
+ * Any other key in this entry is rejected with an error.
12
+ */
13
+ configFile?: string;
13
14
  }
@@ -1,2 +1,2 @@
1
1
  export * from "./ITtscBannerPluginConfig";
2
- export * from "./TtscBannerConfig";
2
+ export * from "./ITtscBannerConfig";
@@ -15,5 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./ITtscBannerPluginConfig"), exports);
18
- __exportStar(require("./TtscBannerConfig"), exports);
18
+ __exportStar(require("./ITtscBannerConfig"), exports);
19
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/structures/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4DAA0C;AAC1C,qDAAmC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/structures/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4DAA0C;AAC1C,sDAAoC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/banner",
3
- "version": "0.12.4",
3
+ "version": "0.13.0",
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.20260519.1",
36
+ "@typescript/native-preview": "7.0.0-dev.20260522.1",
37
37
  "rimraf": "^6.1.2",
38
- "ttsc": "0.12.4"
38
+ "ttsc": "0.13.0"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",
package/src/index.ts CHANGED
@@ -46,20 +46,43 @@ type TtscPluginFactoryContext<TConfig> = {
46
46
  tsconfig: string;
47
47
  };
48
48
 
49
+ /**
50
+ * Keys that the ttsc plugin host injects into every plugin entry and are not
51
+ * owned by `@ttsc/banner`. These pass through the factory without validation so
52
+ * the host can freely add new framework keys in the future.
53
+ */
54
+ const FRAMEWORK_KEYS = new Set<string>([
55
+ "enabled",
56
+ "name",
57
+ "stage",
58
+ "transform",
59
+ ]);
60
+
49
61
  /**
50
62
  * Plugin factory for `@ttsc/banner` — called by the ttsc host to obtain the
51
63
  * plugin descriptor.
52
64
  *
53
- * The factory is intentionally minimal: the banner plugin has no factory-level
54
- * configuration that would alter which Go source to compile. All banner options
55
- * (text, config path, enabled flag) are read at transform time by the Go
56
- * driver.
65
+ * The only banner-specific key accepted in the tsconfig plugin entry is
66
+ * `configFile`. Any other key that is not a known framework key is rejected
67
+ * with a specific error so users discover the correct configuration surface
68
+ * (the dedicated config file) rather than silently receiving no banner.
57
69
  *
58
70
  * @internal
59
71
  */
60
72
  export default function createTtscBanner(
61
- _context: TtscPluginFactoryContext<ITtscBannerPluginConfig>,
73
+ context: TtscPluginFactoryContext<ITtscBannerPluginConfig>,
62
74
  ): TtscPluginDescriptor {
75
+ const entry = context.plugin as Record<string, unknown>;
76
+ for (const key of Object.keys(entry)) {
77
+ if (!FRAMEWORK_KEYS.has(key) && key !== "configFile") {
78
+ throw new Error(
79
+ `@ttsc/banner: tsconfig plugin entry contains unsupported key ${JSON.stringify(key)}. ` +
80
+ `Banner options must be placed in a banner.config.{ts,cts,mts,js,cjs,mjs,json} file. ` +
81
+ `The only accepted key in the tsconfig entry is "configFile" (optional path to the config file).`,
82
+ );
83
+ }
84
+ }
85
+
63
86
  return {
64
87
  name: "@ttsc/banner",
65
88
  // Point at the `driver/` directory one level above `lib/` in the
@@ -0,0 +1,5 @@
1
+ /** Standalone `banner.config.*` file shape consumed by `@ttsc/banner`. */
2
+ export interface ITtscBannerConfig {
3
+ /** Text inserted into the generated `@packageDocumentation` block. */
4
+ text: string;
5
+ }
@@ -6,12 +6,11 @@ export interface ITtscBannerPluginConfig {
6
6
  /** Plugin module specifier. */
7
7
  transform?: string;
8
8
 
9
- /** Inline banner text. */
10
- text?: string;
11
-
12
- /** Path to `banner.config.*`, resolved from the selected tsconfig directory. */
13
- config?: string;
14
-
15
- /** Extra plugin-owned fields are passed through unchanged. */
16
- [key: string]: unknown;
9
+ /**
10
+ * Path to a `banner.config.*` file, resolved from the directory that contains
11
+ * the tsconfig. When omitted, `@ttsc/banner` walks up from that directory and
12
+ * uses the first `banner.config.{ts,cts,mts,js,cjs,mjs,json}` file it finds.
13
+ * Any other key in this entry is rejected with an error.
14
+ */
15
+ configFile?: string;
17
16
  }
@@ -1,2 +1,2 @@
1
1
  export * from "./ITtscBannerPluginConfig";
2
- export * from "./TtscBannerConfig";
2
+ export * from "./ITtscBannerConfig";
@@ -1 +0,0 @@
1
- {"version":3,"file":"TtscBannerConfig.js","sourceRoot":"","sources":["../../src/structures/TtscBannerConfig.ts"],"names":[],"mappings":""}
@@ -1,7 +0,0 @@
1
- /** Standalone `banner.config.*` file shape consumed by `@ttsc/banner`. */
2
- export type TtscBannerConfig =
3
- | string
4
- | {
5
- /** Text inserted into the generated `@packageDocumentation` block. */
6
- text: string;
7
- };