@effected/tsconfig-json 0.1.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.
@@ -0,0 +1,245 @@
1
+ //#region src/ResolvedTsconfig.ts
2
+ const CONFIG_DIR_TEMPLATE = `\${configDir}`;
3
+ const CONFIG_DIR_TEMPLATE_LOWER = CONFIG_DIR_TEMPLATE.toLowerCase();
4
+ /** A value is `${configDir}`-prefixed if its leading token matches case-insensitively (E5). */
5
+ const startsWithConfigDir = (value) => value.slice(0, CONFIG_DIR_TEMPLATE.length).toLowerCase() === CONFIG_DIR_TEMPLATE_LOWER;
6
+ const normalizeSlashes = (p) => p.replace(/\\/g, "/");
7
+ /** POSIX `dirname` over the normalized path; a rootless path yields `"."`. */
8
+ const dirname = (p) => {
9
+ const norm = normalizeSlashes(p);
10
+ const idx = norm.lastIndexOf("/");
11
+ if (idx < 0) return ".";
12
+ if (idx === 0) return "/";
13
+ return norm.slice(0, idx);
14
+ };
15
+ /** POSIX relative path from directory `from` to directory `to`. */
16
+ const relative = (from, to) => {
17
+ const f = normalizeSlashes(from).split("/").filter((s) => s.length > 0);
18
+ const t = normalizeSlashes(to).split("/").filter((s) => s.length > 0);
19
+ let i = 0;
20
+ while (i < f.length && i < t.length && f[i] === t[i]) i++;
21
+ return [...f.slice(i).map(() => ".."), ...t.slice(i)].join("/");
22
+ };
23
+ /** True for POSIX-rooted, Windows-drive-rooted, and UNC paths. */
24
+ const isAbsolutePath = (p) => p.startsWith("/") || p.startsWith("\\\\") || /^[a-zA-Z]:[\\/]/.test(p);
25
+ /**
26
+ * Rebuild a record, mapping each value, using `defineProperty` so an own
27
+ * `__proto__` key is written as data rather than triggering the prototype
28
+ * setter. Preserves every own key (forward tolerance) without pollution.
29
+ */
30
+ const rebuildRecord = (source, mapValue) => {
31
+ const out = {};
32
+ for (const key of Object.keys(source)) Object.defineProperty(out, key, {
33
+ value: mapValue(key, source[key]),
34
+ enumerable: true,
35
+ writable: true,
36
+ configurable: true
37
+ });
38
+ return out;
39
+ };
40
+ const PATH_STRING_KEYS = [
41
+ "outFile",
42
+ "outDir",
43
+ "rootDir",
44
+ "declarationDir",
45
+ "sourceRoot",
46
+ "mapRoot",
47
+ "tsBuildInfoFile",
48
+ "baseUrl",
49
+ "generateCpuProfile",
50
+ "generateTrace"
51
+ ];
52
+ const PATH_LIST_KEYS = ["typeRoots", "rootDirs"];
53
+ /**
54
+ * Apply `transform` to every path-typed compilerOptions surface: the R1.4 path
55
+ * strings and path lists, and — when `includePathsValues` — the `paths` record's
56
+ * values (E5's final phase substitutes those; E5's parse phase leaves them
57
+ * verbatim). Non-string values pass through untouched (forward tolerance).
58
+ */
59
+ const transformCompilerOptionPaths = (co, transform, includePathsValues) => {
60
+ const out = { ...co };
61
+ for (const key of PATH_STRING_KEYS) {
62
+ const value = out[key];
63
+ if (typeof value === "string") out[key] = transform(value);
64
+ }
65
+ for (const key of PATH_LIST_KEYS) {
66
+ const value = out[key];
67
+ if (Array.isArray(value)) out[key] = value.map((entry) => typeof entry === "string" ? transform(entry) : entry);
68
+ }
69
+ if (includePathsValues) {
70
+ const paths = out.paths;
71
+ if (paths !== null && typeof paths === "object") out.paths = rebuildRecord(paths, (_key, arr) => Array.isArray(arr) ? arr.map((entry) => typeof entry === "string" ? transform(entry) : entry) : arr);
72
+ }
73
+ return out;
74
+ };
75
+ /**
76
+ * Absolutize a config's path-typed options (E5) against its own `configDir`,
77
+ * using the injected `join` (`Path.Path.resolve` at the call site — so an
78
+ * already-absolute value is preserved). `${configDir}`-prefixed values are
79
+ * exempt (resolved later, in {@link (ResolvedTsconfig:variable).substituteConfigDir}), and
80
+ * `paths` VALUES stay verbatim. Only `compilerOptions` path surfaces are
81
+ * touched; `files`/`include`/`exclude` are re-rooted at merge time instead (E4).
82
+ *
83
+ * @public
84
+ */
85
+ const absolutize = (doc, configDir, join) => {
86
+ const co = doc.compilerOptions;
87
+ if (co === void 0) return doc;
88
+ const absolutizeValue = (value) => startsWithConfigDir(value) ? value : join(configDir, value);
89
+ return {
90
+ ...doc,
91
+ compilerOptions: transformCompilerOptionPaths(co, absolutizeValue, false)
92
+ };
93
+ };
94
+ const DERIVED_CONSUMED_KEYS = /* @__PURE__ */ new Set([
95
+ "compilerOptions",
96
+ "extends",
97
+ "files",
98
+ "include",
99
+ "exclude",
100
+ "references",
101
+ "watchOptions",
102
+ "typeAcquisition",
103
+ "compileOnSave"
104
+ ]);
105
+ const RESOLVED_STRUCTURAL_KEYS = /* @__PURE__ */ new Set([
106
+ "configPath",
107
+ "extendedPaths",
108
+ "compilerOptions",
109
+ "files",
110
+ "include",
111
+ "exclude",
112
+ "references",
113
+ "watchOptions",
114
+ "typeAcquisition",
115
+ "compileOnSave",
116
+ "pathsBase",
117
+ "extends"
118
+ ]);
119
+ const extractPassthrough = (source, consumed) => {
120
+ const out = {};
121
+ for (const key of Object.keys(source)) {
122
+ if (consumed.has(key)) continue;
123
+ Object.defineProperty(out, key, {
124
+ value: source[key],
125
+ enumerable: true,
126
+ writable: true,
127
+ configurable: true
128
+ });
129
+ }
130
+ return out;
131
+ };
132
+ /** Re-root one inherited `files`/`include`/`exclude` entry, exempting absolute and `${configDir}` entries (E4). */
133
+ const rerootEntry = (prefix, entry) => {
134
+ if (startsWithConfigDir(entry) || isAbsolutePath(entry)) return entry;
135
+ return prefix === "" ? entry : `${prefix}/${entry}`;
136
+ };
137
+ /**
138
+ * `files`/`include`/`exclude`: the derived (own) config declaring the property
139
+ * wins outright — an own empty array beats an inherited value. Otherwise inherit
140
+ * the base's entries, re-rooted with the relative prefix.
141
+ */
142
+ const mergeFileList = (baseList, derivedList, rerootPrefix) => {
143
+ if (derivedList !== void 0) return derivedList;
144
+ if (baseList === void 0) return void 0;
145
+ return baseList.map((entry) => rerootEntry(rerootPrefix, entry));
146
+ };
147
+ /** `watchOptions`: per-key shallow merge (derived wins per key). */
148
+ const mergeWatchOptions = (base, derived) => {
149
+ if (base === void 0) return derived;
150
+ if (derived === void 0) return base;
151
+ return {
152
+ ...base,
153
+ ...derived
154
+ };
155
+ };
156
+ /**
157
+ * Fold one more-derived config onto the accumulated base (E4), derived winning.
158
+ * The loader (Task 8) applies this across the resolution chain, own config last.
159
+ * `derivedPath` is the derived config's absolute normalized path, from which the
160
+ * re-rooting frame and `pathsBase` are computed.
161
+ *
162
+ * @public
163
+ */
164
+ const merge = (base, derived, derivedPath) => {
165
+ const finalDir = dirname(derivedPath);
166
+ const baseDir = dirname(base.configPath);
167
+ const compilerOptions = {
168
+ ...base.compilerOptions,
169
+ ...derived.compilerOptions
170
+ };
171
+ const pathsBase = derived.compilerOptions?.paths !== void 0 ? finalDir : base.pathsBase;
172
+ const rerootPrefix = relative(finalDir, baseDir);
173
+ const files = mergeFileList(base.files, derived.files, rerootPrefix);
174
+ const include = mergeFileList(base.include, derived.include, rerootPrefix);
175
+ const exclude = mergeFileList(base.exclude, derived.exclude, rerootPrefix);
176
+ const references = derived.references;
177
+ const typeAcquisition = derived.typeAcquisition;
178
+ const compileOnSave = derived.compileOnSave !== void 0 ? derived.compileOnSave : base.compileOnSave === true ? true : void 0;
179
+ const watchOptions = mergeWatchOptions(base.watchOptions, derived.watchOptions);
180
+ return {
181
+ ...extractPassthrough(base, RESOLVED_STRUCTURAL_KEYS),
182
+ ...extractPassthrough(derived, DERIVED_CONSUMED_KEYS),
183
+ configPath: derivedPath,
184
+ extendedPaths: [...base.extendedPaths, derivedPath],
185
+ compilerOptions,
186
+ ...files !== void 0 ? { files } : {},
187
+ ...include !== void 0 ? { include } : {},
188
+ ...exclude !== void 0 ? { exclude } : {},
189
+ ...references !== void 0 ? { references } : {},
190
+ ...watchOptions !== void 0 ? { watchOptions } : {},
191
+ ...typeAcquisition !== void 0 ? { typeAcquisition } : {},
192
+ ...compileOnSave !== void 0 ? { compileOnSave } : {},
193
+ ...pathsBase !== void 0 ? { pathsBase } : {}
194
+ };
195
+ };
196
+ const substituteWatchExcludes = (wo, substitute) => {
197
+ if (wo === void 0) return void 0;
198
+ const out = { ...wo };
199
+ for (const key of ["excludeDirectories", "excludeFiles"]) {
200
+ const value = out[key];
201
+ if (Array.isArray(value)) out[key] = value.map((entry) => typeof entry === "string" ? substitute(entry) : entry);
202
+ }
203
+ return out;
204
+ };
205
+ /**
206
+ * The E5 final phase: replace a leading `${configDir}` token (case-insensitive,
207
+ * leading position only) with `finalDir` — the top-level extending config's
208
+ * directory — across every eligible surface: compilerOptions path options,
209
+ * `paths` values, `files`/`include`/`exclude`, and `watchOptions`'
210
+ * `excludeDirectories`/`excludeFiles`. Every other field is left untouched.
211
+ *
212
+ * @public
213
+ */
214
+ const substituteConfigDir = (resolved, finalDir) => {
215
+ const substitute = (value) => startsWithConfigDir(value) ? finalDir + value.slice(CONFIG_DIR_TEMPLATE.length) : value;
216
+ const compilerOptions = transformCompilerOptionPaths(resolved.compilerOptions, substitute, true);
217
+ const files = resolved.files === void 0 ? void 0 : resolved.files.map(substitute);
218
+ const include = resolved.include === void 0 ? void 0 : resolved.include.map(substitute);
219
+ const exclude = resolved.exclude === void 0 ? void 0 : resolved.exclude.map(substitute);
220
+ const watchOptions = substituteWatchExcludes(resolved.watchOptions, substitute);
221
+ return {
222
+ ...resolved,
223
+ compilerOptions,
224
+ ...files !== void 0 ? { files } : {},
225
+ ...include !== void 0 ? { include } : {},
226
+ ...exclude !== void 0 ? { exclude } : {},
227
+ ...watchOptions !== void 0 ? { watchOptions } : {}
228
+ };
229
+ };
230
+ /**
231
+ * The pure extends-merge engine: parse-time path absolutization
232
+ * ({@link (ResolvedTsconfig:variable).absolutize}), the per-field merge fold
233
+ * ({@link (ResolvedTsconfig:variable).merge}), and the `${configDir}` final
234
+ * phase ({@link (ResolvedTsconfig:variable).substituteConfigDir}).
235
+ *
236
+ * @public
237
+ */
238
+ const ResolvedTsconfig = {
239
+ absolutize,
240
+ merge,
241
+ substituteConfigDir
242
+ };
243
+
244
+ //#endregion
245
+ export { ResolvedTsconfig };
package/TsEnumCodec.js ADDED
@@ -0,0 +1,203 @@
1
+ import { Option } from "effect";
2
+
3
+ //#region src/TsEnumCodec.ts
4
+ /**
5
+ * Builds a family's forward/reverse maps from its R1.6 row order. The
6
+ * reverse map overwrites on every duplicate value, so the last-listed name
7
+ * for a value is canonical — exactly R1.6's alias/canonical rule.
8
+ */
9
+ const buildTable = (rows) => {
10
+ const forward = /* @__PURE__ */ new Map();
11
+ const reverse = /* @__PURE__ */ new Map();
12
+ for (const [name, value] of rows) {
13
+ forward.set(name, value);
14
+ reverse.set(value, name);
15
+ }
16
+ return {
17
+ forward,
18
+ reverse
19
+ };
20
+ };
21
+ const TABLES = {
22
+ target: buildTable([
23
+ ["es5", 1],
24
+ ["es6", 2],
25
+ ["es2015", 2],
26
+ ["es2016", 3],
27
+ ["es2017", 4],
28
+ ["es2018", 5],
29
+ ["es2019", 6],
30
+ ["es2020", 7],
31
+ ["es2021", 8],
32
+ ["es2022", 9],
33
+ ["es2023", 10],
34
+ ["es2024", 11],
35
+ ["es2025", 12],
36
+ ["esnext", 99]
37
+ ]),
38
+ module: buildTable([
39
+ ["none", 0],
40
+ ["commonjs", 1],
41
+ ["amd", 2],
42
+ ["umd", 3],
43
+ ["system", 4],
44
+ ["es6", 5],
45
+ ["es2015", 5],
46
+ ["es2020", 6],
47
+ ["es2022", 7],
48
+ ["esnext", 99],
49
+ ["node16", 100],
50
+ ["node18", 101],
51
+ ["node20", 102],
52
+ ["nodenext", 199],
53
+ ["preserve", 200]
54
+ ]),
55
+ moduleResolution: buildTable([
56
+ ["classic", 1],
57
+ ["node", 2],
58
+ ["node10", 2],
59
+ ["node16", 3],
60
+ ["nodenext", 99],
61
+ ["bundler", 100]
62
+ ]),
63
+ jsx: buildTable([
64
+ ["preserve", 1],
65
+ ["react", 2],
66
+ ["react-native", 3],
67
+ ["react-jsx", 4],
68
+ ["react-jsxdev", 5]
69
+ ]),
70
+ newLine: buildTable([["crlf", 0], ["lf", 1]]),
71
+ moduleDetection: buildTable([
72
+ ["legacy", 1],
73
+ ["auto", 2],
74
+ ["force", 3]
75
+ ]),
76
+ watchFile: buildTable([
77
+ ["fixedpollinginterval", 0],
78
+ ["prioritypollinginterval", 1],
79
+ ["dynamicprioritypolling", 2],
80
+ ["fixedchunksizepolling", 3],
81
+ ["usefsevents", 4],
82
+ ["usefseventsonparentdirectory", 5]
83
+ ]),
84
+ watchDirectory: buildTable([
85
+ ["usefsevents", 0],
86
+ ["fixedpollinginterval", 1],
87
+ ["dynamicprioritypolling", 2],
88
+ ["fixedchunksizepolling", 3]
89
+ ]),
90
+ fallbackPolling: buildTable([
91
+ ["fixedinterval", 0],
92
+ ["priorityinterval", 1],
93
+ ["dynamicpriority", 2],
94
+ ["fixedchunksize", 3]
95
+ ])
96
+ };
97
+ /**
98
+ * Encodes a family's canonical (or alias) string spelling to its numeric
99
+ * form. `Option.none()` for a string with no table entry — never guessed.
100
+ *
101
+ * @public
102
+ */
103
+ const encode = (family, value) => Option.fromNullishOr(TABLES[family].forward.get(value));
104
+ /**
105
+ * Decodes a family's numeric value to its canonical string spelling.
106
+ * `Option.none()` for a numeric value with no table entry (a future TS enum
107
+ * member) — never guessed.
108
+ *
109
+ * @public
110
+ */
111
+ const decode = (family, value) => Option.fromNullishOr(TABLES[family].reverse.get(value));
112
+ /**
113
+ * Normalizes any spelling of a `lib` reference — the plain short name
114
+ * (`esnext`), the on-disk file name (`lib.esnext.d.ts`), or an absolute path
115
+ * to one (`/…/typescript/lib/lib.dom.iterable.d.ts`) — to the canonical
116
+ * lowercase short name (`esnext`, `dom.iterable`). Strips a leading
117
+ * directory, the `lib.` prefix and the `.d.ts` suffix; idempotent on an
118
+ * already-short name.
119
+ *
120
+ * @public
121
+ */
122
+ const normalizeLibReference = (lib) => {
123
+ const base = (lib.split("/").pop() ?? lib).toLowerCase();
124
+ const withoutPrefix = base.startsWith("lib.") ? base.slice(4) : base;
125
+ return withoutPrefix.endsWith(".d.ts") ? withoutPrefix.slice(0, -5) : withoutPrefix;
126
+ };
127
+ /** `compilerOptions` keys whose values are one of the R1.6 enum families. */
128
+ const COMPILER_OPTION_ENUM_KEYS = [
129
+ ["target", "target"],
130
+ ["module", "module"],
131
+ ["moduleResolution", "moduleResolution"],
132
+ ["jsx", "jsx"],
133
+ ["newLine", "newLine"],
134
+ ["moduleDetection", "moduleDetection"]
135
+ ];
136
+ /**
137
+ * Encodes a decoded `compilerOptions` object into the numeric-enum-shaped
138
+ * form `ts.CompilerOptions` (and `@typescript/vfs`'s `TsEnvironment`) expect:
139
+ * every R1.6 enum family becomes its numeric value, and `lib` entries become
140
+ * the file-name form (`lib.esnext.d.ts`) — see the module banner for the
141
+ * evidence. Every other key (booleans, strings, arrays, unknown passthrough
142
+ * keys) is copied through untouched.
143
+ *
144
+ * @public
145
+ */
146
+ const encodeCompilerOptions = (options) => {
147
+ const source = options;
148
+ const result = { ...source };
149
+ for (const [key, family] of COMPILER_OPTION_ENUM_KEYS) {
150
+ const value = source[key];
151
+ if (typeof value === "string") {
152
+ const encoded = encode(family, value);
153
+ if (Option.isSome(encoded)) result[key] = encoded.value;
154
+ }
155
+ }
156
+ const lib = source.lib;
157
+ if (Array.isArray(lib)) result.lib = lib.map((entry) => typeof entry === "string" ? `lib.${normalizeLibReference(entry)}.d.ts` : entry);
158
+ return result;
159
+ };
160
+ /**
161
+ * Decodes a numeric-enum-shaped `compilerOptions` object (as produced by
162
+ * {@link (TsEnumCodec:variable).encodeCompilerOptions} or read off a live
163
+ * `ts.CompilerOptions`) back into the string-enum shape this package's
164
+ * schemas use: every R1.6 enum family becomes its canonical string, and
165
+ * `lib` entries become the short form. A numeric value with no table entry —
166
+ * a future TS enum member — is left as-is (passthrough, never an error) —
167
+ * which is why the return type stays the wider `Record<string, unknown>`
168
+ * rather than {@link (CompilerOptions:variable).Type}: an unmappable
169
+ * passthrough value would violate that narrower type's contract. Every other
170
+ * key is copied through untouched.
171
+ *
172
+ * @public
173
+ */
174
+ const decodeCompilerOptions = (numeric) => {
175
+ const result = { ...numeric };
176
+ for (const [key, family] of COMPILER_OPTION_ENUM_KEYS) {
177
+ const value = numeric[key];
178
+ if (typeof value === "number") {
179
+ const decoded = decode(family, value);
180
+ if (Option.isSome(decoded)) result[key] = decoded.value;
181
+ }
182
+ }
183
+ const lib = numeric.lib;
184
+ if (Array.isArray(lib)) result.lib = lib.map((entry) => typeof entry === "string" ? normalizeLibReference(entry) : entry);
185
+ return result;
186
+ };
187
+ /**
188
+ * The string↔numeric enum codec for `compilerOptions` / `watchOptions`
189
+ * families, per R1.6. Plain data: every lookup is a synchronous map read
190
+ * returning `Option.Option`, never a thrown error.
191
+ *
192
+ * @public
193
+ */
194
+ const TsEnumCodec = {
195
+ encode,
196
+ decode,
197
+ normalizeLibReference,
198
+ encodeCompilerOptions,
199
+ decodeCompilerOptions
200
+ };
201
+
202
+ //#endregion
203
+ export { TsEnumCodec };
@@ -0,0 +1,29 @@
1
+ import { Effect, Path } from "effect";
2
+ import { Walker } from "@effected/walker";
3
+
4
+ //#region src/TsconfigDiscovery.ts
5
+ /**
6
+ * Find the nearest `tsconfig.json` (or `options.filename`) at or above
7
+ * `start`, ascending toward the filesystem root. Absence — nowhere on the
8
+ * chain, or every candidate unreadable — is `Option.none()`, never an error;
9
+ * discovery is best-effort per `Walker.findUpward`'s absorption posture, and a
10
+ * permission-denied probe on one directory does not hide a config file above
11
+ * it.
12
+ *
13
+ * @public
14
+ */
15
+ const findNearest = (start, options) => Effect.gen(function* () {
16
+ const path = yield* Path.Path;
17
+ const filename = options?.filename ?? "tsconfig.json";
18
+ const dirs = yield* Walker.ascend(start, options?.stopAt === void 0 ? {} : { stopAt: options.stopAt });
19
+ return yield* Walker.findUpward(dirs, (dir) => [path.join(dir, filename)]);
20
+ });
21
+ /**
22
+ * Nearest-config upward discovery for `tsconfig.json`.
23
+ *
24
+ * @public
25
+ */
26
+ const TsconfigDiscovery = { findNearest };
27
+
28
+ //#endregion
29
+ export { TsconfigDiscovery };
@@ -0,0 +1,122 @@
1
+ import { CompilerOptions } from "./CompilerOptions.js";
2
+ import { Schema, SchemaTransformation } from "effect";
3
+ import { Jsonc } from "@effected/jsonc";
4
+
5
+ //#region src/TsconfigJson.ts
6
+ /**
7
+ * Case-insensitive literal-union decode; canonical lowercase encode.
8
+ * Replicated from `CompilerOptions.ts`'s module-private helper (not exported
9
+ * there — reused as house style, not imported, to avoid coupling this
10
+ * module's public schemas to CompilerOptions.ts's internals).
11
+ */
12
+ const caseInsensitiveLiterals = (literals) => Schema.String.pipe(Schema.decodeTo(Schema.Literals(literals), SchemaTransformation.transform({
13
+ decode: (s) => s.toLowerCase(),
14
+ encode: (s) => s
15
+ })));
16
+ /** `watchOptions.watchFile`. @public */
17
+ const WatchFile = caseInsensitiveLiterals([
18
+ "fixedpollinginterval",
19
+ "prioritypollinginterval",
20
+ "dynamicprioritypolling",
21
+ "fixedchunksizepolling",
22
+ "usefsevents",
23
+ "usefseventsonparentdirectory"
24
+ ]);
25
+ /** `watchOptions.watchDirectory`. @public */
26
+ const WatchDirectory = caseInsensitiveLiterals([
27
+ "usefsevents",
28
+ "fixedpollinginterval",
29
+ "dynamicprioritypolling",
30
+ "fixedchunksizepolling"
31
+ ]);
32
+ /** `watchOptions.fallbackPolling`. @public */
33
+ const FallbackPolling = caseInsensitiveLiterals([
34
+ "fixedinterval",
35
+ "priorityinterval",
36
+ "dynamicpriority",
37
+ "fixedchunksize"
38
+ ]);
39
+ /**
40
+ * One `references[]` entry: `path` is required and non-empty; every other key
41
+ * is preserved verbatim (per R1.5).
42
+ *
43
+ * @public
44
+ */
45
+ const Reference = Schema.StructWithRest(Schema.Struct({ path: Schema.String.check(Schema.isMinLength(1)) }), [Schema.Record(Schema.String, Schema.Unknown)]);
46
+ /**
47
+ * `watchOptions` — the three enum fields (R1.2), the two live booleans/arrays
48
+ * (R1.5), and a passthrough record (schemastore's `force` is phantom).
49
+ *
50
+ * @public
51
+ */
52
+ const WatchOptions = Schema.StructWithRest(Schema.Struct({
53
+ watchFile: Schema.optionalKey(WatchFile),
54
+ watchDirectory: Schema.optionalKey(WatchDirectory),
55
+ fallbackPolling: Schema.optionalKey(FallbackPolling),
56
+ synchronousWatchDirectory: Schema.optionalKey(Schema.Boolean),
57
+ excludeDirectories: Schema.optionalKey(Schema.Array(Schema.String)),
58
+ excludeFiles: Schema.optionalKey(Schema.Array(Schema.String))
59
+ }), [Schema.Record(Schema.String, Schema.Unknown)]);
60
+ /**
61
+ * `typeAcquisition` — per R1.5.
62
+ *
63
+ * @public
64
+ */
65
+ const TypeAcquisition = Schema.StructWithRest(Schema.Struct({
66
+ enable: Schema.optionalKey(Schema.Boolean),
67
+ include: Schema.optionalKey(Schema.Array(Schema.String)),
68
+ exclude: Schema.optionalKey(Schema.Array(Schema.String)),
69
+ disableFilenameBasedTypeAcquisition: Schema.optionalKey(Schema.Boolean)
70
+ }), [Schema.Record(Schema.String, Schema.Unknown)]);
71
+ /**
72
+ * The tsconfig.json document: every typed top-level field (R1.1) optional,
73
+ * plus a passthrough record so unrecognized keys (`buildOptions`, `ts-node`,
74
+ * …) survive decode and re-encode untouched — tsc itself silently ignores
75
+ * unknown top-level keys, and this schema follows suit.
76
+ *
77
+ * @public
78
+ */
79
+ const TsconfigJson = Schema.StructWithRest(Schema.Struct({
80
+ compilerOptions: Schema.optionalKey(CompilerOptions),
81
+ extends: Schema.optionalKey(Schema.Union([Schema.String, Schema.Array(Schema.String)])),
82
+ files: Schema.optionalKey(Schema.Array(Schema.String)),
83
+ include: Schema.optionalKey(Schema.Array(Schema.String)),
84
+ exclude: Schema.optionalKey(Schema.Array(Schema.String)),
85
+ references: Schema.optionalKey(Schema.Array(Reference)),
86
+ watchOptions: Schema.optionalKey(WatchOptions),
87
+ typeAcquisition: Schema.optionalKey(TypeAcquisition),
88
+ compileOnSave: Schema.optionalKey(Schema.Boolean),
89
+ $schema: Schema.optionalKey(Schema.String)
90
+ }), [Schema.Record(Schema.String, Schema.Unknown)]);
91
+ /**
92
+ * Decodes a JSONC-encoded tsconfig.json document straight into
93
+ * {@link (TsconfigJson:variable)}. Bound once at module top level —
94
+ * `Jsonc.schema` is schema-producing, and this is the shared instance (the
95
+ * house `FromString` idiom, R3.3; `TsconfigJson` is a `Schema.StructWithRest`
96
+ * value rather than a `Schema.Class`, so the codec is a sibling export, not a
97
+ * static).
98
+ *
99
+ * @public
100
+ */
101
+ const TsconfigJsonFromString = Jsonc.schema(TsconfigJson);
102
+ /**
103
+ * Raised when a tsconfig.json document fails to parse or decode. `path` is
104
+ * the file path when the failure is file-bound, and the empty string
105
+ * otherwise (e.g. decoding an in-memory string). The loader (Task 8) wraps
106
+ * file-bound decode failures in this error; this module only declares it.
107
+ *
108
+ * @public
109
+ */
110
+ var TsconfigParseError = class extends Schema.TaggedErrorClass()("TsconfigParseError", {
111
+ /** The file path that failed to parse, or `""` when not file-bound. */
112
+ path: Schema.String,
113
+ /** The underlying decode failure. */
114
+ cause: Schema.Defect()
115
+ }) {
116
+ get message() {
117
+ return this.path.length > 0 ? `failed to parse tsconfig.json at "${this.path}"` : "failed to parse tsconfig.json";
118
+ }
119
+ };
120
+
121
+ //#endregion
122
+ export { FallbackPolling, Reference, TsconfigJson, TsconfigJsonFromString, TsconfigParseError, TypeAcquisition, WatchDirectory, WatchFile, WatchOptions };