@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,345 @@
1
+ import { Schema, SchemaTransformation } from "effect";
2
+
3
+ //#region src/CompilerOptions.ts
4
+ /**
5
+ * Case-insensitive literal-union decode; canonical lowercase encode. Module-
6
+ * internal per the task brief — every exported enum schema below is built
7
+ * from it, but the helper itself is not part of the public surface.
8
+ */
9
+ const caseInsensitiveLiterals = (literals) => Schema.String.pipe(Schema.decodeTo(Schema.Literals(literals), SchemaTransformation.transform({
10
+ decode: (s) => s.toLowerCase(),
11
+ encode: (s) => s
12
+ })));
13
+ /**
14
+ * `compilerOptions.target` — the ECMAScript target. `es5` is deprecated in TS
15
+ * 6.0; `es3` has no literal (dead per R1.3 — a `target: "es3"` value fails
16
+ * decode against this schema rather than silently passing through, since
17
+ * `target` itself is a live, typed field).
18
+ *
19
+ * @public
20
+ */
21
+ const Target = caseInsensitiveLiterals([
22
+ "es5",
23
+ "es6",
24
+ "es2015",
25
+ "es2016",
26
+ "es2017",
27
+ "es2018",
28
+ "es2019",
29
+ "es2020",
30
+ "es2021",
31
+ "es2022",
32
+ "es2023",
33
+ "es2024",
34
+ "es2025",
35
+ "esnext"
36
+ ]);
37
+ /**
38
+ * `compilerOptions.module` — the module output format. `none`, `amd`, `umd`
39
+ * and `system` are deprecated in TS 6.0.
40
+ *
41
+ * @public
42
+ */
43
+ const Module = caseInsensitiveLiterals([
44
+ "none",
45
+ "commonjs",
46
+ "amd",
47
+ "umd",
48
+ "system",
49
+ "es6",
50
+ "es2015",
51
+ "es2020",
52
+ "es2022",
53
+ "esnext",
54
+ "node16",
55
+ "node18",
56
+ "node20",
57
+ "nodenext",
58
+ "preserve"
59
+ ]);
60
+ /**
61
+ * `compilerOptions.moduleResolution`. `node10`, `node` and `classic` are
62
+ * deprecated in TS 6.0.
63
+ *
64
+ * @public
65
+ */
66
+ const ModuleResolution = caseInsensitiveLiterals([
67
+ "node10",
68
+ "node",
69
+ "classic",
70
+ "node16",
71
+ "nodenext",
72
+ "bundler"
73
+ ]);
74
+ /**
75
+ * `compilerOptions.jsx`. There is no `none` literal — tsc's option map has
76
+ * only these five.
77
+ *
78
+ * @public
79
+ */
80
+ const Jsx = caseInsensitiveLiterals([
81
+ "preserve",
82
+ "react-native",
83
+ "react-jsx",
84
+ "react-jsxdev",
85
+ "react"
86
+ ]);
87
+ /** `compilerOptions.newLine`. @public */
88
+ const NewLine = caseInsensitiveLiterals(["crlf", "lf"]);
89
+ /** `compilerOptions.moduleDetection`. @public */
90
+ const ModuleDetection = caseInsensitiveLiterals([
91
+ "auto",
92
+ "legacy",
93
+ "force"
94
+ ]);
95
+ /**
96
+ * `compilerOptions.lib` member values — the complete TS 6.0.3 set, lowercase
97
+ * canonical, per R1.2.
98
+ *
99
+ * @public
100
+ */
101
+ const Lib = caseInsensitiveLiterals([
102
+ "es5",
103
+ "es6",
104
+ "es7",
105
+ "es2015",
106
+ "es2016",
107
+ "es2017",
108
+ "es2018",
109
+ "es2019",
110
+ "es2020",
111
+ "es2021",
112
+ "es2022",
113
+ "es2023",
114
+ "es2024",
115
+ "es2025",
116
+ "esnext",
117
+ "dom",
118
+ "dom.iterable",
119
+ "dom.asynciterable",
120
+ "webworker",
121
+ "webworker.importscripts",
122
+ "webworker.iterable",
123
+ "webworker.asynciterable",
124
+ "scripthost",
125
+ "es2015.core",
126
+ "es2015.collection",
127
+ "es2015.generator",
128
+ "es2015.iterable",
129
+ "es2015.promise",
130
+ "es2015.proxy",
131
+ "es2015.reflect",
132
+ "es2015.symbol",
133
+ "es2015.symbol.wellknown",
134
+ "es2016.array.include",
135
+ "es2016.intl",
136
+ "es2017.arraybuffer",
137
+ "es2017.date",
138
+ "es2017.object",
139
+ "es2017.sharedmemory",
140
+ "es2017.string",
141
+ "es2017.intl",
142
+ "es2017.typedarrays",
143
+ "es2018.asyncgenerator",
144
+ "es2018.asynciterable",
145
+ "es2018.intl",
146
+ "es2018.promise",
147
+ "es2018.regexp",
148
+ "es2019.array",
149
+ "es2019.object",
150
+ "es2019.string",
151
+ "es2019.symbol",
152
+ "es2019.intl",
153
+ "es2020.bigint",
154
+ "es2020.date",
155
+ "es2020.promise",
156
+ "es2020.sharedmemory",
157
+ "es2020.string",
158
+ "es2020.symbol.wellknown",
159
+ "es2020.intl",
160
+ "es2020.number",
161
+ "es2021.promise",
162
+ "es2021.string",
163
+ "es2021.weakref",
164
+ "es2021.intl",
165
+ "es2022.array",
166
+ "es2022.error",
167
+ "es2022.intl",
168
+ "es2022.object",
169
+ "es2022.string",
170
+ "es2022.regexp",
171
+ "es2023.array",
172
+ "es2023.collection",
173
+ "es2023.intl",
174
+ "es2024.arraybuffer",
175
+ "es2024.collection",
176
+ "es2024.object",
177
+ "es2024.promise",
178
+ "es2024.regexp",
179
+ "es2024.sharedmemory",
180
+ "es2024.string",
181
+ "es2025.collection",
182
+ "es2025.float16",
183
+ "es2025.intl",
184
+ "es2025.iterator",
185
+ "es2025.promise",
186
+ "es2025.regexp",
187
+ "esnext.asynciterable",
188
+ "esnext.symbol",
189
+ "esnext.bigint",
190
+ "esnext.weakref",
191
+ "esnext.object",
192
+ "esnext.regexp",
193
+ "esnext.string",
194
+ "esnext.float16",
195
+ "esnext.iterator",
196
+ "esnext.promise",
197
+ "esnext.array",
198
+ "esnext.collection",
199
+ "esnext.date",
200
+ "esnext.decorators",
201
+ "esnext.disposable",
202
+ "esnext.error",
203
+ "esnext.intl",
204
+ "esnext.sharedmemory",
205
+ "esnext.temporal",
206
+ "esnext.typedarrays",
207
+ "decorators",
208
+ "decorators.legacy"
209
+ ]);
210
+ const IgnoreDeprecations = Schema.Literals(["5.0", "6.0"]);
211
+ /**
212
+ * One `compilerOptions.plugins[]` entry: `name` is required and typed; every
213
+ * other key is preserved verbatim (ts-plugin authors attach arbitrary extra
214
+ * configuration).
215
+ */
216
+ const PluginEntry = Schema.StructWithRest(Schema.Struct({ name: Schema.String }), [Schema.Record(Schema.String, Schema.Unknown)]);
217
+ /**
218
+ * `compilerOptions`, decoded as every R1.3-live boolean, R1.4 string/path/
219
+ * array/record/number, and R1.2 enum field — each `optionalKey` — intersected
220
+ * with a passthrough record so unknown and dead (R1.3 dead-list) keys survive
221
+ * decode and re-encode untouched, per the forward-tolerance constraint.
222
+ *
223
+ * @public
224
+ */
225
+ const CompilerOptions = Schema.StructWithRest(Schema.Struct({
226
+ target: Schema.optionalKey(Target),
227
+ module: Schema.optionalKey(Module),
228
+ moduleResolution: Schema.optionalKey(ModuleResolution),
229
+ jsx: Schema.optionalKey(Jsx),
230
+ newLine: Schema.optionalKey(NewLine),
231
+ moduleDetection: Schema.optionalKey(ModuleDetection),
232
+ lib: Schema.optionalKey(Schema.Array(Lib)),
233
+ ignoreDeprecations: Schema.optionalKey(IgnoreDeprecations),
234
+ strict: Schema.optionalKey(Schema.Boolean),
235
+ noImplicitAny: Schema.optionalKey(Schema.Boolean),
236
+ strictNullChecks: Schema.optionalKey(Schema.Boolean),
237
+ strictFunctionTypes: Schema.optionalKey(Schema.Boolean),
238
+ strictBindCallApply: Schema.optionalKey(Schema.Boolean),
239
+ strictPropertyInitialization: Schema.optionalKey(Schema.Boolean),
240
+ strictBuiltinIteratorReturn: Schema.optionalKey(Schema.Boolean),
241
+ noImplicitThis: Schema.optionalKey(Schema.Boolean),
242
+ useUnknownInCatchVariables: Schema.optionalKey(Schema.Boolean),
243
+ /** @deprecated Deprecated in TypeScript 6.0 when set to `false`. */
244
+ alwaysStrict: Schema.optionalKey(Schema.Boolean),
245
+ noUnusedLocals: Schema.optionalKey(Schema.Boolean),
246
+ noUnusedParameters: Schema.optionalKey(Schema.Boolean),
247
+ exactOptionalPropertyTypes: Schema.optionalKey(Schema.Boolean),
248
+ noImplicitReturns: Schema.optionalKey(Schema.Boolean),
249
+ noFallthroughCasesInSwitch: Schema.optionalKey(Schema.Boolean),
250
+ noUncheckedIndexedAccess: Schema.optionalKey(Schema.Boolean),
251
+ noImplicitOverride: Schema.optionalKey(Schema.Boolean),
252
+ noPropertyAccessFromIndexSignature: Schema.optionalKey(Schema.Boolean),
253
+ allowUnusedLabels: Schema.optionalKey(Schema.Boolean),
254
+ allowUnreachableCode: Schema.optionalKey(Schema.Boolean),
255
+ noUncheckedSideEffectImports: Schema.optionalKey(Schema.Boolean),
256
+ allowJs: Schema.optionalKey(Schema.Boolean),
257
+ checkJs: Schema.optionalKey(Schema.Boolean),
258
+ resolveJsonModule: Schema.optionalKey(Schema.Boolean),
259
+ allowArbitraryExtensions: Schema.optionalKey(Schema.Boolean),
260
+ allowImportingTsExtensions: Schema.optionalKey(Schema.Boolean),
261
+ rewriteRelativeImportExtensions: Schema.optionalKey(Schema.Boolean),
262
+ resolvePackageJsonExports: Schema.optionalKey(Schema.Boolean),
263
+ resolvePackageJsonImports: Schema.optionalKey(Schema.Boolean),
264
+ /** @deprecated Deprecated in TypeScript 6.0 when set to `false`. */
265
+ allowSyntheticDefaultImports: Schema.optionalKey(Schema.Boolean),
266
+ /** @deprecated Deprecated in TypeScript 6.0 when set to `false`. */
267
+ esModuleInterop: Schema.optionalKey(Schema.Boolean),
268
+ preserveSymlinks: Schema.optionalKey(Schema.Boolean),
269
+ allowUmdGlobalAccess: Schema.optionalKey(Schema.Boolean),
270
+ verbatimModuleSyntax: Schema.optionalKey(Schema.Boolean),
271
+ isolatedModules: Schema.optionalKey(Schema.Boolean),
272
+ isolatedDeclarations: Schema.optionalKey(Schema.Boolean),
273
+ erasableSyntaxOnly: Schema.optionalKey(Schema.Boolean),
274
+ forceConsistentCasingInFileNames: Schema.optionalKey(Schema.Boolean),
275
+ declaration: Schema.optionalKey(Schema.Boolean),
276
+ declarationMap: Schema.optionalKey(Schema.Boolean),
277
+ emitDeclarationOnly: Schema.optionalKey(Schema.Boolean),
278
+ sourceMap: Schema.optionalKey(Schema.Boolean),
279
+ inlineSourceMap: Schema.optionalKey(Schema.Boolean),
280
+ inlineSources: Schema.optionalKey(Schema.Boolean),
281
+ removeComments: Schema.optionalKey(Schema.Boolean),
282
+ importHelpers: Schema.optionalKey(Schema.Boolean),
283
+ /** @deprecated Deprecated in TypeScript 6.0. */
284
+ downlevelIteration: Schema.optionalKey(Schema.Boolean),
285
+ emitBOM: Schema.optionalKey(Schema.Boolean),
286
+ noEmit: Schema.optionalKey(Schema.Boolean),
287
+ noEmitHelpers: Schema.optionalKey(Schema.Boolean),
288
+ noEmitOnError: Schema.optionalKey(Schema.Boolean),
289
+ preserveConstEnums: Schema.optionalKey(Schema.Boolean),
290
+ stripInternal: Schema.optionalKey(Schema.Boolean),
291
+ experimentalDecorators: Schema.optionalKey(Schema.Boolean),
292
+ emitDecoratorMetadata: Schema.optionalKey(Schema.Boolean),
293
+ useDefineForClassFields: Schema.optionalKey(Schema.Boolean),
294
+ noCheck: Schema.optionalKey(Schema.Boolean),
295
+ composite: Schema.optionalKey(Schema.Boolean),
296
+ incremental: Schema.optionalKey(Schema.Boolean),
297
+ disableSourceOfProjectReferenceRedirect: Schema.optionalKey(Schema.Boolean),
298
+ disableSolutionSearching: Schema.optionalKey(Schema.Boolean),
299
+ disableReferencedProjectLoad: Schema.optionalKey(Schema.Boolean),
300
+ assumeChangesOnlyAffectDirectDependencies: Schema.optionalKey(Schema.Boolean),
301
+ noErrorTruncation: Schema.optionalKey(Schema.Boolean),
302
+ noLib: Schema.optionalKey(Schema.Boolean),
303
+ noResolve: Schema.optionalKey(Schema.Boolean),
304
+ skipDefaultLibCheck: Schema.optionalKey(Schema.Boolean),
305
+ skipLibCheck: Schema.optionalKey(Schema.Boolean),
306
+ diagnostics: Schema.optionalKey(Schema.Boolean),
307
+ extendedDiagnostics: Schema.optionalKey(Schema.Boolean),
308
+ listFiles: Schema.optionalKey(Schema.Boolean),
309
+ listFilesOnly: Schema.optionalKey(Schema.Boolean),
310
+ listEmittedFiles: Schema.optionalKey(Schema.Boolean),
311
+ explainFiles: Schema.optionalKey(Schema.Boolean),
312
+ traceResolution: Schema.optionalKey(Schema.Boolean),
313
+ preserveWatchOutput: Schema.optionalKey(Schema.Boolean),
314
+ pretty: Schema.optionalKey(Schema.Boolean),
315
+ disableSizeLimit: Schema.optionalKey(Schema.Boolean),
316
+ libReplacement: Schema.optionalKey(Schema.Boolean),
317
+ stableTypeOrdering: Schema.optionalKey(Schema.Boolean),
318
+ /** @deprecated Deprecated in TypeScript 6.0. */
319
+ outFile: Schema.optionalKey(Schema.String),
320
+ outDir: Schema.optionalKey(Schema.String),
321
+ rootDir: Schema.optionalKey(Schema.String),
322
+ declarationDir: Schema.optionalKey(Schema.String),
323
+ sourceRoot: Schema.optionalKey(Schema.String),
324
+ mapRoot: Schema.optionalKey(Schema.String),
325
+ tsBuildInfoFile: Schema.optionalKey(Schema.String),
326
+ /** @deprecated Deprecated in TypeScript 6.0. */
327
+ baseUrl: Schema.optionalKey(Schema.String),
328
+ generateCpuProfile: Schema.optionalKey(Schema.String),
329
+ generateTrace: Schema.optionalKey(Schema.String),
330
+ typeRoots: Schema.optionalKey(Schema.Array(Schema.String)),
331
+ rootDirs: Schema.optionalKey(Schema.Array(Schema.String)),
332
+ jsxFactory: Schema.optionalKey(Schema.String),
333
+ jsxFragmentFactory: Schema.optionalKey(Schema.String),
334
+ jsxImportSource: Schema.optionalKey(Schema.String),
335
+ reactNamespace: Schema.optionalKey(Schema.String),
336
+ types: Schema.optionalKey(Schema.Array(Schema.String)),
337
+ customConditions: Schema.optionalKey(Schema.Array(Schema.String)),
338
+ moduleSuffixes: Schema.optionalKey(Schema.Array(Schema.String)),
339
+ paths: Schema.optionalKey(Schema.Record(Schema.String, Schema.Array(Schema.String))),
340
+ plugins: Schema.optionalKey(Schema.Array(PluginEntry)),
341
+ maxNodeModuleJsDepth: Schema.optionalKey(Schema.Number)
342
+ }), [Schema.Record(Schema.String, Schema.Unknown)]);
343
+
344
+ //#endregion
345
+ export { CompilerOptions, Jsx, Lib, Module, ModuleDetection, ModuleResolution, NewLine, Target };
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C. Spencer Beggs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,138 @@
1
+ //#region src/PortableTsconfig.ts
2
+ const TSCONFIG_SCHEMA_URL = "https://json.schemastore.org/tsconfig";
3
+ /**
4
+ * Boolean `compilerOptions` preserved in a {@link (PortableTsconfig:interface)}: the
5
+ * strict family, module/interop semantics, checking-depth and lib controls,
6
+ * and language-feature switches — every one type-checking behavior, none of
7
+ * them naming a path or producing a build artifact.
8
+ */
9
+ const PRESERVED_BOOLEAN_OPTIONS = [
10
+ "strict",
11
+ "noImplicitAny",
12
+ "strictNullChecks",
13
+ "strictFunctionTypes",
14
+ "strictBindCallApply",
15
+ "strictPropertyInitialization",
16
+ "strictBuiltinIteratorReturn",
17
+ "noImplicitThis",
18
+ "useUnknownInCatchVariables",
19
+ "alwaysStrict",
20
+ "noUnusedLocals",
21
+ "noUnusedParameters",
22
+ "exactOptionalPropertyTypes",
23
+ "noImplicitReturns",
24
+ "noFallthroughCasesInSwitch",
25
+ "noUncheckedIndexedAccess",
26
+ "noImplicitOverride",
27
+ "noPropertyAccessFromIndexSignature",
28
+ "allowUnusedLabels",
29
+ "allowUnreachableCode",
30
+ "noUncheckedSideEffectImports",
31
+ "allowJs",
32
+ "checkJs",
33
+ "resolveJsonModule",
34
+ "allowArbitraryExtensions",
35
+ "allowImportingTsExtensions",
36
+ "rewriteRelativeImportExtensions",
37
+ "resolvePackageJsonExports",
38
+ "resolvePackageJsonImports",
39
+ "allowSyntheticDefaultImports",
40
+ "esModuleInterop",
41
+ "preserveSymlinks",
42
+ "allowUmdGlobalAccess",
43
+ "verbatimModuleSyntax",
44
+ "isolatedModules",
45
+ "isolatedDeclarations",
46
+ "erasableSyntaxOnly",
47
+ "forceConsistentCasingInFileNames",
48
+ "useDefineForClassFields",
49
+ "noLib",
50
+ "skipDefaultLibCheck",
51
+ "skipLibCheck",
52
+ "noCheck",
53
+ "experimentalDecorators",
54
+ "emitDecoratorMetadata",
55
+ "importHelpers",
56
+ "downlevelIteration",
57
+ "preserveConstEnums"
58
+ ];
59
+ /**
60
+ * Enum-valued `compilerOptions` preserved in a {@link (PortableTsconfig:interface)}: the
61
+ * target/module/moduleResolution/jsx/lib family plus `moduleDetection` and
62
+ * `ignoreDeprecations`. `newLine` is deliberately excluded — pure emit
63
+ * formatting, no bearing on type-checking behavior.
64
+ */
65
+ const PRESERVED_ENUM_OPTIONS = [
66
+ "target",
67
+ "module",
68
+ "moduleResolution",
69
+ "jsx",
70
+ "moduleDetection",
71
+ "lib",
72
+ "ignoreDeprecations"
73
+ ];
74
+ /**
75
+ * Plain-string `compilerOptions` preserved in a {@link (PortableTsconfig:interface)}: the
76
+ * jsx factory/namespace family. None of these name a filesystem path.
77
+ */
78
+ const PRESERVED_STRING_OPTIONS = [
79
+ "jsxFactory",
80
+ "jsxFragmentFactory",
81
+ "jsxImportSource",
82
+ "reactNamespace"
83
+ ];
84
+ const PRESERVED_OPTIONS = [
85
+ ...PRESERVED_BOOLEAN_OPTIONS,
86
+ ...PRESERVED_ENUM_OPTIONS,
87
+ ...PRESERVED_STRING_OPTIONS
88
+ ];
89
+ /**
90
+ * A `ResolvedTsconfig` carries a string `configPath`, an array `extendedPaths`
91
+ * AND an object `compilerOptions`; a bare `CompilerOptions.Type` never carries
92
+ * all three. Every conjunct is required — `CompilerOptions.Type`'s passthrough
93
+ * index signature tolerates any unrecognized key, so a hostile config file can
94
+ * plant a string `configPath` and even an array `extendedPaths` as passthrough
95
+ * keys on a bare options bag (reachable from forward tolerance). The
96
+ * `compilerOptions` conjunct is what makes the discrimination TOTAL: `make`
97
+ * only branches into `input.compilerOptions` when that value is a non-null
98
+ * object it can safely index, so no crafted bag can steer the filter into a
99
+ * `TypeError` at the `source[key]` loop below. (A bag that fakes all three is
100
+ * then read exactly like a `ResolvedTsconfig` — its `compilerOptions` object
101
+ * is filtered, which is the safe outcome.)
102
+ */
103
+ const isResolvedTsconfig = (input) => typeof input.configPath === "string" && Array.isArray(input.extendedPaths) && typeof input.compilerOptions === "object" && input.compilerOptions !== null;
104
+ /**
105
+ * Project a {@link (ResolvedTsconfig:interface)} or a bare
106
+ * `CompilerOptions.Type` down to a {@link (PortableTsconfig:interface)}: copy
107
+ * only the allow-listed type-semantics options, force `composite: false` and
108
+ * `noEmit: true` regardless of what the source declared, and stamp `$schema`.
109
+ * Every other key — including every unknown passthrough key the source
110
+ * preserved for forward tolerance — is dropped; this is an allow-list, not a
111
+ * deny-list, so an option this package does not yet classify never leaks onto
112
+ * the portable shape by accident.
113
+ */
114
+ const make = (input) => {
115
+ const source = isResolvedTsconfig(input) ? input.compilerOptions : input;
116
+ const compilerOptions = {};
117
+ for (const key of PRESERVED_OPTIONS) {
118
+ const value = source[key];
119
+ if (value !== void 0) compilerOptions[key] = value;
120
+ }
121
+ compilerOptions.composite = false;
122
+ compilerOptions.noEmit = true;
123
+ return {
124
+ $schema: TSCONFIG_SCHEMA_URL,
125
+ compilerOptions
126
+ };
127
+ };
128
+ /**
129
+ * The portable-tsconfig filter: {@link (PortableTsconfig:variable).make}
130
+ * narrows a resolved or bare compiler-options object to the allow-listed,
131
+ * machine-independent subset described on {@link (PortableTsconfig:interface)}.
132
+ *
133
+ * @public
134
+ */
135
+ const PortableTsconfig = { make };
136
+
137
+ //#endregion
138
+ export { PortableTsconfig };
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @effected/tsconfig-json
2
+
3
+ [![npm](https://img.shields.io/npm/v/@effected%2Ftsconfig-json?label=npm&color=cb3837)](https://www.npmjs.com/package/@effected/tsconfig-json)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-4caf50.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js %3E%3D24.11.0](https://img.shields.io/badge/Node.js-%3E%3D24.11.0-5fa04e.svg)](https://nodejs.org/)
6
+ [![TypeScript 7.0](https://img.shields.io/badge/TypeScript-7.0-3178c6.svg)](https://www.typescriptlang.org/)
7
+
8
+ Composable tsconfig.json handling for Effect: document and compiler-option schemas, `extends`-chain resolution with tsc's own merge semantics, nearest-config discovery and a portable-config filter for virtual TypeScript environments. Every parse is JSONC — comments and trailing commas are legal everywhere, exactly as tsc treats them — and options the schemas do not know pass through decode and encode untouched instead of being dropped.
9
+
10
+ > **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
11
+ > development against a single pinned Effect v4 beta. Packages graduate to
12
+ > `1.0.0` once Effect `4.0.0` ships. To hold your own `effect` versions at
13
+ > exactly the ones the kit is built and tested against, install
14
+ > [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
15
+ >
16
+ > **Stability: unstable.** This package's API surface is not yet considered
17
+ > complete and may change across `0.x` releases. Pin an exact version — even a
18
+ > package marked *stable* before `1.0.0` can introduce a breaking change by
19
+ > accident, and an exact pin turns that into a type-check error rather than a
20
+ > runtime surprise. Full policy: [release strategy](https://github.com/spencerbeggs/effected#release-strategy).
21
+
22
+ ## Why @effected/tsconfig-json
23
+
24
+ Reading a tsconfig.json correctly means reproducing what tsc does, and what tsc does is more than `JSON.parse` plus `Object.assign`. An `extends` target resolves like a module: a bare specifier walks ancestor `node_modules` directories, a package's `exports` map can redirect or block it, and a `tsconfig` field in its manifest can point somewhere else entirely. Merging the chain is per-field, path options absolutize against the config that declared them rather than the one you loaded, and `${configDir}` substitutes once at the end against the top config's directory. These rules were extracted from the TypeScript compiler's source and encoded here as data-driven tests, so the resolution you get is the resolution tsc computes.
25
+
26
+ The package does all of this without importing `typescript`, not even as a type. It works at the string level — `"target": "es2023"` stays a string through schema, merge and discovery — and the version-coupled numeric enum mappings live in `TsEnumCodec` as plain data tables, so converting to the numeric shape a real compiler expects is an explicit final step rather than a dependency you carry everywhere. Malformed input always fails through a typed error channel, and the recursive `extends` walk carries cycle and depth guards, because a config file is untrusted input.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ npm install @effected/tsconfig-json @effected/jsonc @effected/walker effect
32
+ ```
33
+
34
+ ```bash
35
+ pnpm add @effected/tsconfig-json @effected/jsonc @effected/walker effect
36
+ ```
37
+
38
+ Requires Node.js >=24.11.0. `effect` v4, `@effected/jsonc` and `@effected/walker` are peer dependencies; there are no runtime dependencies of its own.
39
+
40
+ All IO goes through `FileSystem` and `Path` from `effect` core, not a platform package, so a consumer provides them once at the edge (`@effect/platform-node` on Node, `@effect/platform-bun` on Bun) and a test provides `Path.layer` and `FileSystem.layerNoop` straight from core with nothing else installed.
41
+
42
+ ## Quick start
43
+
44
+ Resolve a config and its full `extends` chain:
45
+
46
+ ```ts
47
+ import { TsconfigLoader } from "@effected/tsconfig-json";
48
+ import { NodeFileSystem, NodePath } from "@effect/platform-node";
49
+ import { Effect, Layer } from "effect";
50
+
51
+ const PlatformLive = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
52
+
53
+ const resolved = await Effect.runPromise(TsconfigLoader.resolve("./tsconfig.json").pipe(Effect.provide(PlatformLive)));
54
+
55
+ console.log(resolved.extendedPaths);
56
+ // every config on the extends chain as normalized absolute paths, base-most first and your own config last
57
+ console.log(resolved.compilerOptions);
58
+ // the merged options after folding the whole chain — later configs win per field, paths replaced wholesale
59
+ ```
60
+
61
+ Find the nearest config first when you only have a starting directory:
62
+
63
+ ```ts
64
+ import { TsconfigDiscovery } from "@effected/tsconfig-json";
65
+ import { Effect, Option } from "effect";
66
+
67
+ const nearest = TsconfigDiscovery.findNearest(process.cwd());
68
+ // Effect<Option<string>, never, FileSystem | Path> — absence is Option.none(), never an error
69
+ ```
70
+
71
+ Hand the result to a real compiler by encoding the enum families to their numeric form, or narrow it to the portable subset a virtual TypeScript environment can safely reuse:
72
+
73
+ ```ts
74
+ import { PortableTsconfig, TsEnumCodec } from "@effected/tsconfig-json";
75
+
76
+ console.log(TsEnumCodec.encodeCompilerOptions({ target: "es2023", strict: true, lib: ["esnext"] }));
77
+ // { target: 10, strict: true, lib: [ 'lib.esnext.d.ts' ] }
78
+
79
+ console.log(PortableTsconfig.make(resolved).compilerOptions.noEmit);
80
+ // true — always forced, whatever the source config declared
81
+ ```
82
+
83
+ ## Features
84
+
85
+ - `TsconfigJson` / `TsconfigJsonFromString` — the document schema and its JSONC string codec. Comments and trailing commas are legal in every parse; there is no JSON-strict path.
86
+ - `CompilerOptions` — string-level schemas for `compilerOptions`: enum values decode case-insensitively and encode to canonical lowercase, and unknown or removed options survive a round trip as passthrough.
87
+ - `TsconfigLoader.load` / `TsconfigLoader.resolve` — read and decode one config, or resolve its full `extends` chain depth-first with per-branch cycle stacks (diamond chains are legal), a depth guard and tsc's target resolution for relative, rooted and bare-specifier targets including `exports` maps.
88
+ - `ResolvedTsconfig` — the pure merge engine behind `resolve`: per-field merge semantics, path-option absolutization against the declaring config's directory, final `${configDir}` substitution and `pathsBase` provenance, with no filesystem access at all.
89
+ - `TsconfigDiscovery.findNearest` — the nearest `tsconfig.json` (or any filename via `options.filename`) at or above a starting directory, over `@effected/walker`; one unreadable ancestor cannot hide a config above it.
90
+ - `TsEnumCodec` — the string↔numeric enum tables as plain data with zero `typescript` imports. `encodeCompilerOptions` produces the numeric shape `ts.CompilerOptions` expects, with `lib` entries in the file-name form the compiler resolves verbatim; `decodeCompilerOptions` reverses it.
91
+ - `PortableTsconfig.make` — an allow-list projection down to machine-independent type-semantics options, with `composite: false` and `noEmit: true` forced: the slice a virtual TypeScript environment (Twoslash, API Extractor, an in-memory language service) can safely inherit.
92
+ - Typed failures everywhere: a malformed file is a `TsconfigParseError` carrying its path, a broken chain is a `TsconfigExtendsError` with a `not-found` / `cycle` / `depth` / `empty` reason and the full resolution chain, and IO errors flow through as `PlatformError`. Nothing fails as a defect.
93
+
94
+ ## License
95
+
96
+ [MIT](LICENSE)