@ttsc/banner 0.27.0 → 0.28.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/driver/banner.go +125 -40
- package/package.json +2 -2
package/driver/banner.go
CHANGED
|
@@ -62,7 +62,18 @@ func validateBannerConfig(config map[string]any) error {
|
|
|
62
62
|
// SourcePreamble resolves the banner text from the plugin config and returns it
|
|
63
63
|
// formatted as a JSDoc block comment suitable for prepending to each emitted file.
|
|
64
64
|
func (plugin) SourcePreamble(ctx driver.PluginContext) (string, error) {
|
|
65
|
-
|
|
65
|
+
preamble, err := parseBannerWithReporters(ctx.Entry.Config, ctx.Cwd, ctx.Tsconfig, ctx.ReportHostInput, ctx.ReportHostInputHash, ctx.ReportHostInputRealpath)
|
|
66
|
+
if err != nil {
|
|
67
|
+
return "", err
|
|
68
|
+
}
|
|
69
|
+
// Every file receives the same text, and that text comes from
|
|
70
|
+
// banner.config.* alone — including, for a script or TypeScript config, every
|
|
71
|
+
// module the loader pulled in, each of which was reported above as a host
|
|
72
|
+
// input. Host inputs stay universal under the completeness contract, so a
|
|
73
|
+
// config edit still invalidates every file while an unrelated type edit stops
|
|
74
|
+
// doing so (samchon/ttsc#1263).
|
|
75
|
+
ctx.ReportDependenciesComplete()
|
|
76
|
+
return preamble, nil
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
// parseBanner resolves and formats banner text into a JSDoc block comment.
|
|
@@ -151,7 +162,12 @@ func resolveBannerTextWithReporters(config map[string]any, cwd, tsconfigPath str
|
|
|
151
162
|
return text, nil
|
|
152
163
|
}
|
|
153
164
|
|
|
154
|
-
location, err := findBannerConfigFile(cwd, tsconfigPath)
|
|
165
|
+
location, probed, err := findBannerConfigFile(cwd, tsconfigPath)
|
|
166
|
+
// Report the rejected candidates before the error checks: a search that ended
|
|
167
|
+
// ambiguous or empty examined them just the same, and a consumer that learns
|
|
168
|
+
// of them can invalidate a generation the next search would answer
|
|
169
|
+
// differently.
|
|
170
|
+
driver.ReportRejectedConfigCandidates(probed, hashReporter, realpathReporter)
|
|
155
171
|
if err != nil {
|
|
156
172
|
return "", err
|
|
157
173
|
}
|
|
@@ -198,47 +214,45 @@ func bannerTextFromConfigValue(raw any, label string) (string, bool, error) {
|
|
|
198
214
|
return text, true, nil
|
|
199
215
|
}
|
|
200
216
|
|
|
217
|
+
// bannerConfigFilenames is the discovery name list, in precedence order.
|
|
218
|
+
var bannerConfigFilenames = []string{
|
|
219
|
+
"banner.config.json",
|
|
220
|
+
"banner.config.js",
|
|
221
|
+
"banner.config.cjs",
|
|
222
|
+
"banner.config.mjs",
|
|
223
|
+
"banner.config.ts",
|
|
224
|
+
"banner.config.cts",
|
|
225
|
+
"banner.config.mts",
|
|
226
|
+
}
|
|
227
|
+
|
|
201
228
|
// findBannerConfigFile walks up from the tsconfig (or cwd) directory looking for
|
|
202
229
|
// a banner.config.{ts,cts,mts,js,cjs,mjs,json} file. Returns the path when exactly
|
|
203
230
|
// one match is found per directory, "" when none exists at any level, or an
|
|
204
231
|
// error when multiple candidates exist in the same directory.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if stat, err := os.Stat(candidate); err == nil && !stat.IsDir() {
|
|
220
|
-
matches = append(matches, candidate)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if len(matches) > 1 {
|
|
224
|
-
names := make([]string, len(matches))
|
|
225
|
-
for i, match := range matches {
|
|
226
|
-
names[i] = filepath.Base(match)
|
|
227
|
-
}
|
|
228
|
-
return "", fmt.Errorf(
|
|
229
|
-
"@ttsc/banner: multiple banner config files found in %s (%s); set \"configFile\" explicitly in the tsconfig plugin entry",
|
|
230
|
-
dir, strings.Join(names, ", "),
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
if len(matches) == 1 {
|
|
234
|
-
return matches[0], nil
|
|
235
|
-
}
|
|
236
|
-
parent := filepath.Dir(dir)
|
|
237
|
-
if parent == dir {
|
|
238
|
-
return "", nil
|
|
232
|
+
//
|
|
233
|
+
// The second return value is every candidate the walk examined and rejected,
|
|
234
|
+
// each carrying whether it was absent or a directory wearing the name. Those
|
|
235
|
+
// paths decide the result as much as the file it returned: one
|
|
236
|
+
// created nearer the entry wins the next search outright, and one created
|
|
237
|
+
// beside the match makes that directory ambiguous. The caller reports them so a
|
|
238
|
+
// persistent consumer stops serving output built from a config a cold run would
|
|
239
|
+
// no longer choose (samchon/ttsc#1271).
|
|
240
|
+
func findBannerConfigFile(cwd, tsconfigPath string) (string, []driver.ConfigCandidate, error) {
|
|
241
|
+
discovery := driver.DiscoverConfigFile(tsconfigBaseDir(cwd, tsconfigPath), bannerConfigFilenames)
|
|
242
|
+
if len(discovery.Matches) > 1 {
|
|
243
|
+
names := make([]string, len(discovery.Matches))
|
|
244
|
+
for i, match := range discovery.Matches {
|
|
245
|
+
names[i] = filepath.Base(match)
|
|
239
246
|
}
|
|
240
|
-
|
|
247
|
+
return "", discovery.Probed, fmt.Errorf(
|
|
248
|
+
"@ttsc/banner: multiple banner config files found in %s (%s); set \"configFile\" explicitly in the tsconfig plugin entry",
|
|
249
|
+
discovery.Directory, strings.Join(names, ", "),
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
if len(discovery.Matches) == 1 {
|
|
253
|
+
return discovery.Matches[0], discovery.Probed, nil
|
|
241
254
|
}
|
|
255
|
+
return "", discovery.Probed, nil
|
|
242
256
|
}
|
|
243
257
|
|
|
244
258
|
// resolveBannerConfigPath resolves a config path from the plugin entry.
|
|
@@ -434,7 +448,8 @@ func loadBannerScriptConfigFile(location string) (any, error) {
|
|
|
434
448
|
|
|
435
449
|
func loadBannerScriptConfigFileWithInputs(location string) (bannerLoadedConfig, error) {
|
|
436
450
|
const script = `
|
|
437
|
-
const
|
|
451
|
+
const nodeModule = require("node:module");
|
|
452
|
+
const { createRequire, isBuiltin, registerHooks } = nodeModule;
|
|
438
453
|
const crypto = require("node:crypto");
|
|
439
454
|
const fs = require("node:fs");
|
|
440
455
|
const path = require("node:path");
|
|
@@ -630,6 +645,33 @@ registerHooks({
|
|
|
630
645
|
},
|
|
631
646
|
});
|
|
632
647
|
|
|
648
|
+
// The hook above never sees a require() made from inside a CommonJS module the
|
|
649
|
+
// ESM loader evaluated, which on Node 22 is every require the config makes:
|
|
650
|
+
// module.registerHooks observes the import() of that module and nothing within
|
|
651
|
+
// it. A config's own dependencies would then be reported without the candidates
|
|
652
|
+
// that decide them, so a spelling appearing later could change what the config
|
|
653
|
+
// resolves to with nothing in the envelope to notice it (samchon/ttsc#1280).
|
|
654
|
+
// Wrapping the CommonJS resolver records the same two observations the hook
|
|
655
|
+
// does, on the graph the hook cannot reach.
|
|
656
|
+
const nextResolveFilename = nodeModule._resolveFilename;
|
|
657
|
+
nodeModule._resolveFilename = function resolveFilename(request, parent, isMain, options) {
|
|
658
|
+
// _resolveFilename is an internal entry point anything may call, so a
|
|
659
|
+
// non-string request arrives here as readily as a specifier does. Reading it
|
|
660
|
+
// would replace Node's own argument error with a TypeError from this loader.
|
|
661
|
+
if (typeof request !== "string") {
|
|
662
|
+
return nextResolveFilename.call(this, request, parent, isMain, options);
|
|
663
|
+
}
|
|
664
|
+
const parentFile = parent && typeof parent.filename === "string" ? parent.filename : undefined;
|
|
665
|
+
const parentURL = parentFile === undefined ? undefined : pathToFileURL(parentFile).href;
|
|
666
|
+
recordResolutionCandidates(request, parentURL, undefined);
|
|
667
|
+
const resolved = nextResolveFilename.call(this, request, parent, isMain, options);
|
|
668
|
+
if (path.isAbsolute(resolved)) {
|
|
669
|
+
recordResolutionCandidates(request, parentURL, pathToFileURL(resolved).href);
|
|
670
|
+
recordFile(resolved);
|
|
671
|
+
}
|
|
672
|
+
return resolved;
|
|
673
|
+
};
|
|
674
|
+
|
|
633
675
|
(async () => {
|
|
634
676
|
const mod = await import(pathToFileURL(process.argv[1]).href);
|
|
635
677
|
let current = Object.prototype.hasOwnProperty.call(mod, "default") ? mod.default : mod;
|
|
@@ -809,11 +851,11 @@ func loadBannerTypeScriptConfigFileWithInputs(location, resolutionRoot string) (
|
|
|
809
851
|
// JSON-encoded import specifier) and writes the serialized banner value to stdout.
|
|
810
852
|
func bannerTypeScriptConfigLoaderSource(importLiteral string) string {
|
|
811
853
|
return fmt.Sprintf(`// @ts-nocheck
|
|
812
|
-
import { createRequire, isBuiltin, registerHooks } from "node:module";
|
|
854
|
+
import Module, { createRequire, isBuiltin, registerHooks } from "node:module";
|
|
813
855
|
import crypto from "node:crypto";
|
|
814
856
|
import fs from "node:fs";
|
|
815
857
|
import path from "node:path";
|
|
816
|
-
import { fileURLToPath } from "node:url";
|
|
858
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
817
859
|
|
|
818
860
|
const inputs = new Set<string>();
|
|
819
861
|
const hashes = new Map<string, string | null>();
|
|
@@ -1020,6 +1062,49 @@ registerHooks({
|
|
|
1020
1062
|
},
|
|
1021
1063
|
});
|
|
1022
1064
|
|
|
1065
|
+
// The hook above never sees a require() made from inside a CommonJS module the
|
|
1066
|
+
// ESM loader evaluated, which on Node 22 is every require the config makes:
|
|
1067
|
+
// module.registerHooks observes the import() of that module and nothing within
|
|
1068
|
+
// it. A config's own dependencies would then be reported without the candidates
|
|
1069
|
+
// that decide them, so a spelling appearing later could change what the config
|
|
1070
|
+
// resolves to with nothing in the envelope to notice it (samchon/ttsc#1280).
|
|
1071
|
+
// Wrapping the CommonJS resolver records the same two observations the hook
|
|
1072
|
+
// does, on the graph the hook cannot reach.
|
|
1073
|
+
const moduleInternals = Module as unknown as {
|
|
1074
|
+
_resolveFilename(
|
|
1075
|
+
request: string,
|
|
1076
|
+
parent: { filename?: string | null } | null | undefined,
|
|
1077
|
+
isMain: boolean,
|
|
1078
|
+
options?: unknown,
|
|
1079
|
+
): string;
|
|
1080
|
+
};
|
|
1081
|
+
const nextResolveFilename = moduleInternals._resolveFilename;
|
|
1082
|
+
moduleInternals._resolveFilename = function resolveFilename(
|
|
1083
|
+
this: unknown,
|
|
1084
|
+
request: string,
|
|
1085
|
+
parent: { filename?: string | null } | null | undefined,
|
|
1086
|
+
isMain: boolean,
|
|
1087
|
+
options?: unknown,
|
|
1088
|
+
): string {
|
|
1089
|
+
// _resolveFilename is an internal entry point anything may call, so a
|
|
1090
|
+
// non-string request arrives here as readily as a specifier does. Reading it
|
|
1091
|
+
// would replace Node's own argument error with a TypeError from this loader.
|
|
1092
|
+
if (typeof request !== "string") {
|
|
1093
|
+
return nextResolveFilename.call(this, request, parent, isMain, options);
|
|
1094
|
+
}
|
|
1095
|
+
const parentURL =
|
|
1096
|
+
typeof parent?.filename === "string"
|
|
1097
|
+
? pathToFileURL(parent.filename).href
|
|
1098
|
+
: undefined;
|
|
1099
|
+
recordResolutionCandidates(request, parentURL, undefined);
|
|
1100
|
+
const resolved = nextResolveFilename.call(this, request, parent, isMain, options);
|
|
1101
|
+
if (path.isAbsolute(resolved)) {
|
|
1102
|
+
recordResolutionCandidates(request, parentURL, pathToFileURL(resolved).href);
|
|
1103
|
+
recordFile(resolved);
|
|
1104
|
+
}
|
|
1105
|
+
return resolved;
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1023
1108
|
declare const process: {
|
|
1024
1109
|
exitCode?: number;
|
|
1025
1110
|
stdout: { write(value: string, callback?: () => void): void };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/banner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@types/node": "^25.3.0",
|
|
36
36
|
"rimraf": "^6.1.2",
|
|
37
37
|
"typescript": "^7.0.2",
|
|
38
|
-
"ttsc": "0.
|
|
38
|
+
"ttsc": "0.28.0"
|
|
39
39
|
},
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|