@ttsc/banner 0.30.4 → 0.31.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 +184 -609
- package/lib/createTtscBanner.d.ts +100 -0
- package/lib/createTtscBanner.js +167 -0
- package/lib/createTtscBanner.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -128
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscBannerConfig.d.ts +40 -2
- package/lib/structures/ITtscBannerPluginConfig.d.ts +57 -7
- package/package.json +5 -3
- package/plugin/main.go +19 -19
- package/src/createTtscBanner.ts +256 -0
- package/src/index.ts +1 -209
- package/src/structures/ITtscBannerConfig.ts +40 -2
- package/src/structures/ITtscBannerPluginConfig.ts +57 -7
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import type { ITtscBannerPluginConfig } from "./structures";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The shape returned by a ttsc plugin factory function.
|
|
9
|
+
*
|
|
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
|
+
*/
|
|
14
|
+
type TtscPluginDescriptor = {
|
|
15
|
+
/** The bundled driver host records actual writes and their physical sources. */
|
|
16
|
+
capabilities: { emitProvenance: true };
|
|
17
|
+
|
|
18
|
+
/** Universal config-discovery inputs consumed by the native transform. */
|
|
19
|
+
hostInputs?: string[];
|
|
20
|
+
|
|
21
|
+
/** Evaluation-time fingerprints paired with {@link hostInputs}. */
|
|
22
|
+
hostInputHashes?: Record<string, string | null>;
|
|
23
|
+
|
|
24
|
+
/** Evaluation-time physical targets paired with {@link hostInputs}. */
|
|
25
|
+
hostInputRealpaths?: Record<string, string | null>;
|
|
26
|
+
|
|
27
|
+
/** Human-readable plugin name used in logs and error messages. */
|
|
28
|
+
name: string;
|
|
29
|
+
|
|
30
|
+
/** Absolute path to the Go source directory for this plugin. */
|
|
31
|
+
source: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Pipeline stage. `"transform"` plugins may rewrite source files; `"check"`
|
|
35
|
+
* plugins only produce diagnostics. The framework default is `"transform"`.
|
|
36
|
+
*/
|
|
37
|
+
stage?: "check" | "transform";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Context object passed by the ttsc host to every plugin factory function.
|
|
42
|
+
*
|
|
43
|
+
* Module anchors locate shipped Go source. Project anchors locate user
|
|
44
|
+
* configuration, including when the host uses a generated wrapper tsconfig.
|
|
45
|
+
*/
|
|
46
|
+
type TtscPluginFactoryContext<TConfig> = {
|
|
47
|
+
/** Absolute path to the selected ttsc native helper, not a plugin binary. */
|
|
48
|
+
binary: string;
|
|
49
|
+
|
|
50
|
+
/** Working directory of the ttsc invocation. */
|
|
51
|
+
cwd: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Absolute path to the directory holding this descriptor module, the
|
|
55
|
+
* load-mode-independent replacement for `__dirname`.
|
|
56
|
+
*/
|
|
57
|
+
dirname: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Absolute path to this descriptor module, the load-mode-independent
|
|
61
|
+
* replacement for `__filename`.
|
|
62
|
+
*/
|
|
63
|
+
filename: string;
|
|
64
|
+
|
|
65
|
+
/** Host-declared anchor for implicit plugin config discovery. */
|
|
66
|
+
pluginConfigDir?: string;
|
|
67
|
+
|
|
68
|
+
/** The raw plugin entry from `compilerOptions.plugins[]`. */
|
|
69
|
+
plugin: TConfig;
|
|
70
|
+
|
|
71
|
+
/** Absolute path to the project root (directory containing tsconfig). */
|
|
72
|
+
projectRoot: string;
|
|
73
|
+
|
|
74
|
+
/** Absolute path to the resolved tsconfig. */
|
|
75
|
+
tsconfig: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Keys that the ttsc plugin host injects into every plugin entry and are not
|
|
80
|
+
* owned by `@ttsc/banner`. The host validates registration; this factory
|
|
81
|
+
* rejects unknown banner options without rejecting those registration keys.
|
|
82
|
+
*/
|
|
83
|
+
const FRAMEWORK_KEYS = new Set<string>([
|
|
84
|
+
"enabled",
|
|
85
|
+
"name",
|
|
86
|
+
"stage",
|
|
87
|
+
"transform",
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Plugin factory called by the ttsc host to obtain the `@ttsc/banner`
|
|
92
|
+
* descriptor.
|
|
93
|
+
*
|
|
94
|
+
* The only banner-specific key accepted in the tsconfig plugin entry is
|
|
95
|
+
* `configFile`. Any other key that is not a known framework key is rejected
|
|
96
|
+
* with a specific error so users discover the correct configuration surface
|
|
97
|
+
* (the dedicated config file) rather than silently receiving no banner.
|
|
98
|
+
*
|
|
99
|
+
* @evidence contracts/common.md#principled-implementation
|
|
100
|
+
* The default-export factory maps host module anchors to the shipped driver
|
|
101
|
+
* and project anchors to config candidates. Discovery records all seven
|
|
102
|
+
* candidate names in every directory from the anchor through the first one
|
|
103
|
+
* holding a non-directory match, including absent paths and directory markers,
|
|
104
|
+
* so creating a nearer config or a second file beside the match changes the
|
|
105
|
+
* descriptor inputs. Native evaluation validates ambiguous or invalid config;
|
|
106
|
+
* this factory supplies observations rather than treating existence as a
|
|
107
|
+
* valid banner. SHA-256 covers candidate bytes and a distinct directory marker.
|
|
108
|
+
*
|
|
109
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
110
|
+
* Registration validation, discovery observations and descriptor construction
|
|
111
|
+
* have separate responsibilities. Private helpers isolate path discovery and
|
|
112
|
+
* filesystem observations; they do not implement another config evaluator.
|
|
113
|
+
* The driver path follows the installed package layout, while config anchors
|
|
114
|
+
* follow the project because those two directories have different owners.
|
|
115
|
+
*
|
|
116
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
117
|
+
* Framework keys, candidate filenames, package identity and stage encode the
|
|
118
|
+
* supported registration contract. The descriptor neither evaluates user
|
|
119
|
+
* JavaScript nor changes a foreign loader; it records a failed probe (an absent
|
|
120
|
+
* or unreadable candidate) as a null hash or physical target, and native
|
|
121
|
+
* evaluation reports the actual error.
|
|
122
|
+
*
|
|
123
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
124
|
+
* JSDoc separates registration from config evaluation and explains why
|
|
125
|
+
* unknown inline keys are rejected. Context and descriptor fields document
|
|
126
|
+
* host anchoring and observed inputs. Paragraphs and reasons follow the
|
|
127
|
+
* documentation skill; the exported factory is documented as a host entry.
|
|
128
|
+
*/
|
|
129
|
+
export default function createTtscBanner(
|
|
130
|
+
context: TtscPluginFactoryContext<ITtscBannerPluginConfig>,
|
|
131
|
+
): TtscPluginDescriptor {
|
|
132
|
+
const entry = context.plugin as Record<string, unknown>;
|
|
133
|
+
for (const key of Object.keys(entry)) {
|
|
134
|
+
if (!FRAMEWORK_KEYS.has(key) && key !== "configFile") {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`@ttsc/banner: tsconfig plugin entry contains unsupported key ${JSON.stringify(key)}. ` +
|
|
137
|
+
`Banner options must be placed in a banner.config.{ts,cts,mts,js,cjs,mjs,json} file. ` +
|
|
138
|
+
`The only accepted key in the tsconfig entry is "configFile" (optional path to the config file).`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const configInputs = bannerConfigInputs(context);
|
|
144
|
+
return {
|
|
145
|
+
capabilities: { emitProvenance: true },
|
|
146
|
+
hostInputHashes: configInputs.hashes,
|
|
147
|
+
hostInputRealpaths: configInputs.realpaths,
|
|
148
|
+
hostInputs: configInputs.inputs,
|
|
149
|
+
name: "@ttsc/banner",
|
|
150
|
+
// Point at the `driver/` directory one level above `lib/` in the
|
|
151
|
+
// installed package tree (where the Go sources live). `context.dirname`
|
|
152
|
+
// is this descriptor's own directory in every load mode, unlike the
|
|
153
|
+
// CommonJS `__dirname`, which is undefined under a `.ts`-source or ESM load.
|
|
154
|
+
source: path.resolve(context.dirname, "..", "driver"),
|
|
155
|
+
stage: "transform",
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const BANNER_CONFIG_FILENAMES = [
|
|
160
|
+
"banner.config.json",
|
|
161
|
+
"banner.config.js",
|
|
162
|
+
"banner.config.cjs",
|
|
163
|
+
"banner.config.mjs",
|
|
164
|
+
"banner.config.ts",
|
|
165
|
+
"banner.config.cts",
|
|
166
|
+
"banner.config.mts",
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Mirror native config resolution for a nonblank string `configFile` or for
|
|
171
|
+
* upward discovery, retaining the absent candidates that could supersede or
|
|
172
|
+
* make ambiguous the match. Native evaluation rejects any other `configFile`.
|
|
173
|
+
*/
|
|
174
|
+
function bannerConfigInputs(
|
|
175
|
+
context: TtscPluginFactoryContext<ITtscBannerPluginConfig>,
|
|
176
|
+
): {
|
|
177
|
+
hashes: Record<string, string | null>;
|
|
178
|
+
inputs: string[];
|
|
179
|
+
realpaths: Record<string, string | null>;
|
|
180
|
+
} {
|
|
181
|
+
const configFile = (context.plugin as { configFile?: unknown }).configFile;
|
|
182
|
+
const base = path.resolve(
|
|
183
|
+
context.pluginConfigDir ?? path.dirname(context.tsconfig),
|
|
184
|
+
);
|
|
185
|
+
if (typeof configFile === "string" && configFile.trim() !== "") {
|
|
186
|
+
const file = path.isAbsolute(configFile)
|
|
187
|
+
? path.resolve(configFile)
|
|
188
|
+
: path.resolve(base, configFile);
|
|
189
|
+
return {
|
|
190
|
+
hashes: { [file]: hostInputHash(file) },
|
|
191
|
+
inputs: [file],
|
|
192
|
+
realpaths: { [file]: hostInputRealpath(file) },
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return configDiscoveryInputs(base, BANNER_CONFIG_FILENAMES);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function configDiscoveryInputs(
|
|
199
|
+
base: string,
|
|
200
|
+
names: readonly string[],
|
|
201
|
+
): {
|
|
202
|
+
hashes: Record<string, string | null>;
|
|
203
|
+
inputs: string[];
|
|
204
|
+
realpaths: Record<string, string | null>;
|
|
205
|
+
} {
|
|
206
|
+
const inputs: string[] = [];
|
|
207
|
+
const hashes: Record<string, string | null> = {};
|
|
208
|
+
const realpaths: Record<string, string | null> = {};
|
|
209
|
+
for (let directory = base; ; directory = path.dirname(directory)) {
|
|
210
|
+
const candidates = names.map((name) => path.join(directory, name));
|
|
211
|
+
inputs.push(...candidates);
|
|
212
|
+
for (const candidate of candidates) {
|
|
213
|
+
hashes[candidate] = hostInputHash(candidate);
|
|
214
|
+
realpaths[candidate] = hostInputRealpath(candidate);
|
|
215
|
+
}
|
|
216
|
+
if (candidates.some(configCandidateExists)) break;
|
|
217
|
+
const parent = path.dirname(directory);
|
|
218
|
+
if (parent === directory) break;
|
|
219
|
+
}
|
|
220
|
+
return { hashes, inputs, realpaths };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function hostInputRealpath(file: string): string | null {
|
|
224
|
+
try {
|
|
225
|
+
return fs.realpathSync.native(file);
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Hash the exact candidate state observed before discovery selects a file. */
|
|
232
|
+
function hostInputHash(file: string): string | null {
|
|
233
|
+
try {
|
|
234
|
+
if (fs.statSync(file).isDirectory()) {
|
|
235
|
+
return crypto
|
|
236
|
+
.createHash("sha256")
|
|
237
|
+
.update("ttsc:host-input:directory\0")
|
|
238
|
+
.digest("hex");
|
|
239
|
+
}
|
|
240
|
+
return crypto
|
|
241
|
+
.createHash("sha256")
|
|
242
|
+
.update(fs.readFileSync(file))
|
|
243
|
+
.digest("hex");
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Match the native discovery rule: a directory is never a config file. */
|
|
250
|
+
function configCandidateExists(file: string): boolean {
|
|
251
|
+
try {
|
|
252
|
+
return !fs.statSync(file).isDirectory();
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,211 +1,3 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import type { ITtscBannerPluginConfig } from "./structures";
|
|
6
|
-
|
|
7
1
|
export * from "./structures/index";
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
* The shape returned by a ttsc plugin factory function.
|
|
11
|
-
*
|
|
12
|
-
* Mirrors the ttsc plugin descriptor contract. The `source` field points to the
|
|
13
|
-
* Go source directory that the host will compile and cache as either an
|
|
14
|
-
* executable sidecar or linked native source.
|
|
15
|
-
*/
|
|
16
|
-
type TtscPluginDescriptor = {
|
|
17
|
-
/** Universal config-discovery inputs consumed by the native transform. */
|
|
18
|
-
hostInputs?: string[];
|
|
19
|
-
/** Evaluation-time fingerprints paired with {@link hostInputs}. */
|
|
20
|
-
hostInputHashes?: Record<string, string | null>;
|
|
21
|
-
/** Evaluation-time physical targets paired with {@link hostInputs}. */
|
|
22
|
-
hostInputRealpaths?: Record<string, string | null>;
|
|
23
|
-
/** Human-readable plugin name used in logs and error messages. */
|
|
24
|
-
name: string;
|
|
25
|
-
/** Absolute path to the Go source directory for this plugin. */
|
|
26
|
-
source: string;
|
|
27
|
-
/**
|
|
28
|
-
* Pipeline stage. `"transform"` plugins may rewrite source files; `"check"`
|
|
29
|
-
* plugins only produce diagnostics. The framework default is `"transform"`.
|
|
30
|
-
*/
|
|
31
|
-
stage?: "check" | "transform";
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Context object passed by the ttsc host to every plugin factory function.
|
|
36
|
-
*
|
|
37
|
-
* The factory may inspect the context to customise the descriptor — for example
|
|
38
|
-
* selecting a different Go source directory based on `plugin` config — but most
|
|
39
|
-
* factories ignore it.
|
|
40
|
-
*/
|
|
41
|
-
type TtscPluginFactoryContext<TConfig> = {
|
|
42
|
-
/** Absolute path to the selected ttsc native helper, not a plugin binary. */
|
|
43
|
-
binary: string;
|
|
44
|
-
/** Working directory of the ttsc invocation. */
|
|
45
|
-
cwd: string;
|
|
46
|
-
/**
|
|
47
|
-
* Absolute path to the directory holding this descriptor module — the
|
|
48
|
-
* load-mode-independent replacement for `__dirname`.
|
|
49
|
-
*/
|
|
50
|
-
dirname: string;
|
|
51
|
-
/**
|
|
52
|
-
* Absolute path to this descriptor module — the load-mode-independent
|
|
53
|
-
* replacement for `__filename`.
|
|
54
|
-
*/
|
|
55
|
-
filename: string;
|
|
56
|
-
/** Host-declared anchor for implicit plugin config discovery. */
|
|
57
|
-
pluginConfigDir?: string;
|
|
58
|
-
/** The raw plugin entry from `compilerOptions.plugins[]`. */
|
|
59
|
-
plugin: TConfig;
|
|
60
|
-
/** Absolute path to the project root (directory containing tsconfig). */
|
|
61
|
-
projectRoot: string;
|
|
62
|
-
/** Absolute path to the resolved tsconfig. */
|
|
63
|
-
tsconfig: string;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Keys that the ttsc plugin host injects into every plugin entry and are not
|
|
68
|
-
* owned by `@ttsc/banner`. These pass through the factory without validation so
|
|
69
|
-
* the host can freely add new framework keys in the future.
|
|
70
|
-
*/
|
|
71
|
-
const FRAMEWORK_KEYS = new Set<string>([
|
|
72
|
-
"enabled",
|
|
73
|
-
"name",
|
|
74
|
-
"stage",
|
|
75
|
-
"transform",
|
|
76
|
-
]);
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Plugin factory for `@ttsc/banner` — called by the ttsc host to obtain the
|
|
80
|
-
* plugin descriptor.
|
|
81
|
-
*
|
|
82
|
-
* The only banner-specific key accepted in the tsconfig plugin entry is
|
|
83
|
-
* `configFile`. Any other key that is not a known framework key is rejected
|
|
84
|
-
* with a specific error so users discover the correct configuration surface
|
|
85
|
-
* (the dedicated config file) rather than silently receiving no banner.
|
|
86
|
-
*
|
|
87
|
-
* @internal
|
|
88
|
-
*/
|
|
89
|
-
export default function createTtscBanner(
|
|
90
|
-
context: TtscPluginFactoryContext<ITtscBannerPluginConfig>,
|
|
91
|
-
): TtscPluginDescriptor {
|
|
92
|
-
const entry = context.plugin as Record<string, unknown>;
|
|
93
|
-
for (const key of Object.keys(entry)) {
|
|
94
|
-
if (!FRAMEWORK_KEYS.has(key) && key !== "configFile") {
|
|
95
|
-
throw new Error(
|
|
96
|
-
`@ttsc/banner: tsconfig plugin entry contains unsupported key ${JSON.stringify(key)}. ` +
|
|
97
|
-
`Banner options must be placed in a banner.config.{ts,cts,mts,js,cjs,mjs,json} file. ` +
|
|
98
|
-
`The only accepted key in the tsconfig entry is "configFile" (optional path to the config file).`,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const configInputs = bannerConfigInputs(context);
|
|
104
|
-
return {
|
|
105
|
-
hostInputHashes: configInputs.hashes,
|
|
106
|
-
hostInputRealpaths: configInputs.realpaths,
|
|
107
|
-
hostInputs: configInputs.inputs,
|
|
108
|
-
name: "@ttsc/banner",
|
|
109
|
-
// Point at the `driver/` directory one level above `lib/` in the
|
|
110
|
-
// installed package tree (where the Go sources live). `context.dirname`
|
|
111
|
-
// is this descriptor's own directory in every load mode, unlike the
|
|
112
|
-
// CommonJS `__dirname`, which is undefined under a `.ts`-source or ESM load.
|
|
113
|
-
source: path.resolve(context.dirname, "..", "driver"),
|
|
114
|
-
stage: "transform",
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const BANNER_CONFIG_FILENAMES = [
|
|
119
|
-
"banner.config.json",
|
|
120
|
-
"banner.config.js",
|
|
121
|
-
"banner.config.cjs",
|
|
122
|
-
"banner.config.mjs",
|
|
123
|
-
"banner.config.ts",
|
|
124
|
-
"banner.config.cts",
|
|
125
|
-
"banner.config.mts",
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
/** Mirror native config resolution while retaining missing priority probes. */
|
|
129
|
-
function bannerConfigInputs(
|
|
130
|
-
context: TtscPluginFactoryContext<ITtscBannerPluginConfig>,
|
|
131
|
-
): {
|
|
132
|
-
hashes: Record<string, string | null>;
|
|
133
|
-
inputs: string[];
|
|
134
|
-
realpaths: Record<string, string | null>;
|
|
135
|
-
} {
|
|
136
|
-
const configFile = (context.plugin as { configFile?: unknown }).configFile;
|
|
137
|
-
const base = path.resolve(
|
|
138
|
-
context.pluginConfigDir ?? path.dirname(context.tsconfig),
|
|
139
|
-
);
|
|
140
|
-
if (typeof configFile === "string" && configFile.trim() !== "") {
|
|
141
|
-
const file = path.isAbsolute(configFile)
|
|
142
|
-
? path.resolve(configFile)
|
|
143
|
-
: path.resolve(base, configFile);
|
|
144
|
-
return {
|
|
145
|
-
hashes: { [file]: hostInputHash(file) },
|
|
146
|
-
inputs: [file],
|
|
147
|
-
realpaths: { [file]: hostInputRealpath(file) },
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
return configDiscoveryInputs(base, BANNER_CONFIG_FILENAMES);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function configDiscoveryInputs(
|
|
154
|
-
base: string,
|
|
155
|
-
names: readonly string[],
|
|
156
|
-
): {
|
|
157
|
-
hashes: Record<string, string | null>;
|
|
158
|
-
inputs: string[];
|
|
159
|
-
realpaths: Record<string, string | null>;
|
|
160
|
-
} {
|
|
161
|
-
const inputs: string[] = [];
|
|
162
|
-
const hashes: Record<string, string | null> = {};
|
|
163
|
-
const realpaths: Record<string, string | null> = {};
|
|
164
|
-
for (let directory = base; ; directory = path.dirname(directory)) {
|
|
165
|
-
const candidates = names.map((name) => path.join(directory, name));
|
|
166
|
-
inputs.push(...candidates);
|
|
167
|
-
for (const candidate of candidates) {
|
|
168
|
-
hashes[candidate] = hostInputHash(candidate);
|
|
169
|
-
realpaths[candidate] = hostInputRealpath(candidate);
|
|
170
|
-
}
|
|
171
|
-
if (candidates.some(configCandidateExists)) break;
|
|
172
|
-
const parent = path.dirname(directory);
|
|
173
|
-
if (parent === directory) break;
|
|
174
|
-
}
|
|
175
|
-
return { hashes, inputs, realpaths };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function hostInputRealpath(file: string): string | null {
|
|
179
|
-
try {
|
|
180
|
-
return fs.realpathSync.native(file);
|
|
181
|
-
} catch {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** Hash the exact candidate state observed before discovery selects a file. */
|
|
187
|
-
function hostInputHash(file: string): string | null {
|
|
188
|
-
try {
|
|
189
|
-
if (fs.statSync(file).isDirectory()) {
|
|
190
|
-
return crypto
|
|
191
|
-
.createHash("sha256")
|
|
192
|
-
.update("ttsc:host-input:directory\0")
|
|
193
|
-
.digest("hex");
|
|
194
|
-
}
|
|
195
|
-
return crypto
|
|
196
|
-
.createHash("sha256")
|
|
197
|
-
.update(fs.readFileSync(file))
|
|
198
|
-
.digest("hex");
|
|
199
|
-
} catch {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** Match the native discovery rule: a directory is never a config file. */
|
|
205
|
-
function configCandidateExists(file: string): boolean {
|
|
206
|
-
try {
|
|
207
|
-
return !fs.statSync(file).isDirectory();
|
|
208
|
-
} catch {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
3
|
+
export { default } from "./createTtscBanner";
|
|
@@ -1,5 +1,43 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Object exported by a standalone `banner.config.*` file.
|
|
3
|
+
*
|
|
4
|
+
* The native loader requires a nonblank text string. Keeping banner contents in
|
|
5
|
+
* this object gives JSON, JavaScript and TypeScript configuration the same
|
|
6
|
+
* value contract; the tsconfig plugin entry selects the file instead.
|
|
7
|
+
*
|
|
8
|
+
* @evidence contracts/common.md#principled-implementation
|
|
9
|
+
* A required string member expresses the loader's object payload. TypeScript
|
|
10
|
+
* cannot express nonblank contents through this string type, so the native
|
|
11
|
+
* loader validates that condition before formatting. The formatter preserves
|
|
12
|
+
* content lines while normalizing CRLF and escaping closing delimiters; it
|
|
13
|
+
* changes comment representation without treating text as executable source.
|
|
14
|
+
*
|
|
15
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
16
|
+
* Banner content has one field in a dedicated config value. Registration and
|
|
17
|
+
* config discovery stay in ITtscBannerPluginConfig because they select the
|
|
18
|
+
* content's source rather than change its representation.
|
|
19
|
+
*
|
|
20
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
21
|
+
* The text is consumer content, not a fixture-selected banner or a hardcoded
|
|
22
|
+
* expected output. Required separators and the annotation are formatting
|
|
23
|
+
* constants in the driver rather than hidden options in this value.
|
|
24
|
+
*
|
|
25
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
26
|
+
* Interface and field JSDoc explain standalone config ownership, required
|
|
27
|
+
* nonblank text, CRLF and trailing-line handling, terminator escaping and
|
|
28
|
+
* removeComments behavior. Purpose, validation and formatting rationale are
|
|
29
|
+
* separated into native paragraphs under the documentation skill; the type
|
|
30
|
+
* acknowledgment covers its field without duplicating a checklist on that
|
|
31
|
+
* property.
|
|
32
|
+
*/
|
|
2
33
|
export interface ITtscBannerConfig {
|
|
3
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Text placed before the generated `@packageDocumentation` annotation.
|
|
36
|
+
*
|
|
37
|
+
* Empty or whitespace-only values are rejected. The formatter preserves the
|
|
38
|
+
* supplied lines, removes trailing blank lines, normalizes CRLF and escapes
|
|
39
|
+
* comment terminators so the value cannot close the generated JSDoc block.
|
|
40
|
+
* The resulting comment follows the compiler's `removeComments` policy.
|
|
41
|
+
*/
|
|
4
42
|
text: string;
|
|
5
43
|
}
|
|
@@ -1,16 +1,66 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Host registration and config-file selection for `@ttsc/banner`.
|
|
3
|
+
*
|
|
4
|
+
* This entry belongs in `compilerOptions.plugins[]`. Banner text belongs in the
|
|
5
|
+
* separate `ITtscBannerConfig` value so discovery and config evaluation have
|
|
6
|
+
* one supported home instead of an additional inline option surface.
|
|
7
|
+
*
|
|
8
|
+
* @evidence contracts/common.md#principled-implementation
|
|
9
|
+
* Optional enabled distinguishes omission from an explicit false host switch;
|
|
10
|
+
* transform carries the module specifier and configFile carries a separate
|
|
11
|
+
* filesystem path. Those values belong to different resolution contracts, so
|
|
12
|
+
* a binary path or inline text is not represented as banner configuration.
|
|
13
|
+
* String typing does not establish path existence or nonblankness; the loader
|
|
14
|
+
* validates an explicitly supplied configFile before evaluation.
|
|
15
|
+
*
|
|
16
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
17
|
+
* The type separates two host registration fields from one banner-owned file
|
|
18
|
+
* selector. Text belongs to the loaded ITtscBannerConfig object, leaving one
|
|
19
|
+
* evaluation path instead of parallel inline and file configuration policies.
|
|
20
|
+
*
|
|
21
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
22
|
+
* Host switches and configFile represent the supported registration
|
|
23
|
+
* contract. Banner text has one typed config-file home, preventing a second
|
|
24
|
+
* inline option shape from bypassing that loader's validation.
|
|
25
|
+
*
|
|
26
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
27
|
+
* Interface and member JSDoc distinguish host registration from banner
|
|
28
|
+
* text, disabled and omitted enabled states, module specifiers from config
|
|
29
|
+
* or binary paths, and configFile anchoring, discovery, validation and
|
|
30
|
+
* failure behavior. Separate paragraphs explain why generated wrapper
|
|
31
|
+
* directories cannot become the discovery base, applying the documentation
|
|
32
|
+
* skill's clear prose and rationale guidance to the complete type contract.
|
|
33
|
+
*/
|
|
2
34
|
export interface ITtscBannerPluginConfig {
|
|
3
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Set to `false` to retain the entry without running the plugin.
|
|
37
|
+
*
|
|
38
|
+
* Omission leaves the entry enabled. The ttsc host owns this switch; it is
|
|
39
|
+
* not a banner option passed through the standalone config file.
|
|
40
|
+
*/
|
|
4
41
|
enabled?: boolean;
|
|
5
42
|
|
|
6
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Module specifier the host resolves to the plugin factory.
|
|
45
|
+
*
|
|
46
|
+
* Use `@ttsc/banner` to register this package through the transform key. This
|
|
47
|
+
* names the factory module, not the banner config file or Go binary.
|
|
48
|
+
*/
|
|
7
49
|
transform?: string;
|
|
8
50
|
|
|
9
51
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
52
|
+
* Explicit banner config path, absolute or relative to the host's plugin
|
|
53
|
+
* config directory. Without that explicit anchor, resolution uses the
|
|
54
|
+
* tsconfig directory, or the invocation directory when no tsconfig is set.
|
|
55
|
+
*
|
|
56
|
+
* Omission searches upward from the same anchor for `banner.config.*`.
|
|
57
|
+
* Missing or ambiguous discovery is an error. An explicit value must be a
|
|
58
|
+
* nonblank string naming a supported config file. Banner-specific inline
|
|
59
|
+
* options such as text are rejected; host-owned registration keys remain
|
|
60
|
+
* permitted.
|
|
61
|
+
*
|
|
62
|
+
* The host anchor matters when the tsconfig is a generated wrapper: its
|
|
63
|
+
* temporary directory must not become the user's config-resolution base.
|
|
14
64
|
*/
|
|
15
65
|
configFile?: string;
|
|
16
66
|
}
|