@tsdoctor/vfs 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.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/TsEnvironment.js +95 -0
- package/TsconfigParser.js +98 -0
- package/TypeResolutionOptions.js +98 -0
- package/TypeScriptConfig.js +226 -0
- package/Vfs.js +47 -0
- package/VirtualPackage.js +126 -0
- package/index.d.ts +505 -0
- package/index.js +8 -0
- package/package.json +56 -0
- package/tsdoc-metadata.json +11 -0
package/Vfs.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/Vfs.ts
|
|
2
|
+
/**
|
|
3
|
+
* Merge VFS maps left to right into a new map; later entries win on path
|
|
4
|
+
* collisions.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { mergeVfs } from "@tsdoctor/vfs";
|
|
9
|
+
*
|
|
10
|
+
* const combined = mergeVfs(vfsA, vfsB);
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
const mergeVfs = (...maps) => {
|
|
16
|
+
const out = /* @__PURE__ */ new Map();
|
|
17
|
+
for (const map of maps) for (const [path, content] of map) out.set(path, content);
|
|
18
|
+
return out;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Prefix every path in `entries` with `node_modules/<name>/`, normalizing
|
|
22
|
+
* away leading slashes.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
const prefixVfs = (name, entries) => {
|
|
27
|
+
const out = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const [path, content] of entries) out.set(`node_modules/${name}/${path.replace(/^\/+/, "")}`, content);
|
|
29
|
+
return out;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Whether a path names a TypeScript declaration file.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* The single spelling of this predicate. `TsEnvironment` uses it to pick a
|
|
36
|
+
* VFS map's root files, and `@tsdoctor/registry`'s module resolution uses it
|
|
37
|
+
* to decide whether a resolved path is a declaration. Two spellings of "is
|
|
38
|
+
* this a `.d.ts`" would be free to drift, and the drift would be silent — a
|
|
39
|
+
* root file quietly missing from a TypeScript environment degrades hovers
|
|
40
|
+
* without producing an error.
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
const isTypeDefinition = (filePath) => filePath.endsWith(".d.ts") || filePath.endsWith(".d.mts") || filePath.endsWith(".d.cts");
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { isTypeDefinition, mergeVfs, prefixVfs };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Effect, FileSystem, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/VirtualPackage.ts
|
|
4
|
+
/**
|
|
5
|
+
* A synthetic npm package built from locally supplied TypeScript declaration
|
|
6
|
+
* content, for inclusion in a {@link Vfs} without fetching from the CDN.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Useful when you have locally generated `.d.ts` files — API Extractor
|
|
10
|
+
* output, hand-written ambient declarations — and want them in the same VFS
|
|
11
|
+
* `TypeRegistry` builds from remote packages. Instances are transient: they
|
|
12
|
+
* are never persisted to the disk cache.
|
|
13
|
+
*
|
|
14
|
+
* The class is deliberately subclass-friendly (the rspress consumer extends
|
|
15
|
+
* it): construct via `VirtualPackage.make(...)` or the statics, and extend
|
|
16
|
+
* with `class Mine extends VirtualPackage { ... }`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { VirtualPackage } from "@tsdoctor/vfs";
|
|
21
|
+
*
|
|
22
|
+
* const pkg = VirtualPackage.create("@my-org/api-types", "1.0.0", "export interface User { id: string }");
|
|
23
|
+
* const vfs = pkg.toVfs();
|
|
24
|
+
* // node_modules/@my-org/api-types/package.json, node_modules/@my-org/api-types/index.d.ts
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
var VirtualPackage = class VirtualPackage extends Schema.Class("VirtualPackage")({
|
|
30
|
+
/** The package name (e.g. `"@my-org/api-types"`). */
|
|
31
|
+
name: Schema.String,
|
|
32
|
+
/** The package version. */
|
|
33
|
+
version: Schema.String,
|
|
34
|
+
/** Entry file names (e.g. `"index.d.ts"`) mapped to declaration source. */
|
|
35
|
+
entries: Schema.ReadonlyMap(Schema.String, Schema.String)
|
|
36
|
+
}) {
|
|
37
|
+
/**
|
|
38
|
+
* Single-entry factory: a virtual package whose sole entry point is
|
|
39
|
+
* `index.d.ts`.
|
|
40
|
+
*/
|
|
41
|
+
static create(name, version, declarations) {
|
|
42
|
+
return VirtualPackage.make({
|
|
43
|
+
name,
|
|
44
|
+
version,
|
|
45
|
+
entries: /* @__PURE__ */ new Map([["index.d.ts", declarations]])
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Multi-entry factory: one `.d.ts` per entry point, exposed through a
|
|
50
|
+
* synthetic `exports` map.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* An empty entries map is developer wiring, not input — it would produce a
|
|
54
|
+
* package whose `types` points at a file that does not exist — so it
|
|
55
|
+
* throws at construction (defect posture), as does an entry set whose
|
|
56
|
+
* names collide after extension normalization (see
|
|
57
|
+
* {@link VirtualPackage.toVfs}).
|
|
58
|
+
*/
|
|
59
|
+
static createMultiEntry(name, version, entries) {
|
|
60
|
+
if (entries.size === 0) throw new Error(`VirtualPackage.createMultiEntry: "${name}" needs at least one entry file`);
|
|
61
|
+
return VirtualPackage.make({
|
|
62
|
+
name,
|
|
63
|
+
version,
|
|
64
|
+
entries
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Load a single `.d.ts` file from disk as a virtual package with one
|
|
69
|
+
* `index.d.ts` entry.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Reads through the platform-agnostic `FileSystem` service; the
|
|
73
|
+
* `PlatformError` surfaces typed.
|
|
74
|
+
*/
|
|
75
|
+
static fromFile(name, version, filePath) {
|
|
76
|
+
return Effect.gen(function* () {
|
|
77
|
+
const content = yield* (yield* FileSystem.FileSystem).readFileString(filePath);
|
|
78
|
+
return VirtualPackage.create(name, version, content);
|
|
79
|
+
}).pipe(Effect.withSpan("VirtualPackage.fromFile"));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The package's {@link Vfs}: a synthetic `package.json` plus every entry
|
|
83
|
+
* file, each path prefixed `node_modules/<name>/`.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* The `package.json` uses `types` for a single entry and an `exports` map
|
|
87
|
+
* for multiple entries, so TypeScript module resolution works against the
|
|
88
|
+
* generated VFS.
|
|
89
|
+
*/
|
|
90
|
+
toVfs() {
|
|
91
|
+
const vfs = /* @__PURE__ */ new Map();
|
|
92
|
+
const prefix = `node_modules/${this.name}`;
|
|
93
|
+
vfs.set(`${prefix}/package.json`, this.toPackageJson());
|
|
94
|
+
for (const [fileName, content] of this.entries) {
|
|
95
|
+
if (fileName === "package.json") throw new Error(`VirtualPackage: "${this.name}" cannot define package.json as an entry`);
|
|
96
|
+
vfs.set(`${prefix}/${fileName}`, content);
|
|
97
|
+
}
|
|
98
|
+
return vfs;
|
|
99
|
+
}
|
|
100
|
+
toPackageJson() {
|
|
101
|
+
if (this.entries.size === 0) throw new Error(`VirtualPackage: "${this.name}" has no entry files — nothing to point types at`);
|
|
102
|
+
const manifest = {
|
|
103
|
+
name: this.name,
|
|
104
|
+
version: this.version
|
|
105
|
+
};
|
|
106
|
+
if (this.entries.size === 1) {
|
|
107
|
+
const [only] = this.entries.keys();
|
|
108
|
+
manifest.types = only ?? "index.d.ts";
|
|
109
|
+
} else {
|
|
110
|
+
manifest.exports = {};
|
|
111
|
+
const sources = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const fileName of this.entries.keys()) {
|
|
113
|
+
const baseName = fileName.replace(/\.d\.(m|c)?ts$/, "");
|
|
114
|
+
const key = baseName === "index" ? "." : `./${baseName}`;
|
|
115
|
+
const previous = sources.get(key);
|
|
116
|
+
if (previous !== void 0) throw new Error(`VirtualPackage: "${this.name}" entries "${previous}" and "${fileName}" both normalize to the export key "${key}"`);
|
|
117
|
+
sources.set(key, fileName);
|
|
118
|
+
manifest.exports[key] = { types: `./${fileName}` };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return JSON.stringify(manifest, null, 2);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
export { VirtualPackage };
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { CompilerOptions, ProgrammaticCompilerOptions } from "@effected/tsconfig-json";
|
|
2
|
+
import { Effect, FileSystem, PlatformError, Result, Schema } from "effect";
|
|
3
|
+
import { VirtualTypeScriptEnvironment } from "@typescript/vfs";
|
|
4
|
+
import { PathLike } from "node:fs";
|
|
5
|
+
//#region src/TypeResolutionOptions.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* The compiler options that may influence how a documentation example
|
|
8
|
+
* type-checks.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Deliberately a **whitelist**, picked from `@effected/tsconfig-json`'s
|
|
12
|
+
* `CompilerOptions` rather than restated. Everything here reaches the
|
|
13
|
+
* TypeScript environment, and passing through options the tool does not
|
|
14
|
+
* understand would let a consumer's unrelated build setting silently change
|
|
15
|
+
* how their examples type-check.
|
|
16
|
+
*
|
|
17
|
+
* Picking from the kit's own field schemas is what keeps this a policy
|
|
18
|
+
* statement rather than a second definition of tsconfig: the accepted values,
|
|
19
|
+
* their spellings and their case-insensitivity are the kit's to own, and this
|
|
20
|
+
* module owns only the choice of WHICH options are in scope.
|
|
21
|
+
*
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
declare const TypeResolutionCompilerOptions: Schema.Struct<{
|
|
25
|
+
readonly target: Schema.optionalKey<Schema.decodeTo<Schema.Literals<readonly ["es5", "es6", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020", "es2021", "es2022", "es2023", "es2024", "es2025", "esnext"]>, Schema.String, never, never>>;
|
|
26
|
+
readonly module: Schema.optionalKey<Schema.decodeTo<Schema.Literals<readonly ["none", "commonjs", "amd", "umd", "system", "es6", "es2015", "es2020", "es2022", "esnext", "node16", "node18", "node20", "nodenext", "preserve"]>, Schema.String, never, never>>;
|
|
27
|
+
readonly moduleResolution: Schema.optionalKey<Schema.decodeTo<Schema.Literals<readonly ["node10", "node", "classic", "node16", "nodenext", "bundler"]>, Schema.String, never, never>>;
|
|
28
|
+
readonly jsx: Schema.optionalKey<Schema.decodeTo<Schema.Literals<readonly ["preserve", "react-native", "react-jsx", "react-jsxdev", "react"]>, Schema.String, never, never>>;
|
|
29
|
+
readonly lib: Schema.optionalKey<Schema.$Array<Schema.decodeTo<Schema.Literals<readonly ["es5", "es6", "es7", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020", "es2021", "es2022", "es2023", "es2024", "es2025", "esnext", "dom", "dom.iterable", "dom.asynciterable", "webworker", "webworker.importscripts", "webworker.iterable", "webworker.asynciterable", "scripthost", "es2015.core", "es2015.collection", "es2015.generator", "es2015.iterable", "es2015.promise", "es2015.proxy", "es2015.reflect", "es2015.symbol", "es2015.symbol.wellknown", "es2016.array.include", "es2016.intl", "es2017.arraybuffer", "es2017.date", "es2017.object", "es2017.sharedmemory", "es2017.string", "es2017.intl", "es2017.typedarrays", "es2018.asyncgenerator", "es2018.asynciterable", "es2018.intl", "es2018.promise", "es2018.regexp", "es2019.array", "es2019.object", "es2019.string", "es2019.symbol", "es2019.intl", "es2020.bigint", "es2020.date", "es2020.promise", "es2020.sharedmemory", "es2020.string", "es2020.symbol.wellknown", "es2020.intl", "es2020.number", "es2021.promise", "es2021.string", "es2021.weakref", "es2021.intl", "es2022.array", "es2022.error", "es2022.intl", "es2022.object", "es2022.string", "es2022.regexp", "es2023.array", "es2023.collection", "es2023.intl", "es2024.arraybuffer", "es2024.collection", "es2024.object", "es2024.promise", "es2024.regexp", "es2024.sharedmemory", "es2024.string", "es2025.collection", "es2025.float16", "es2025.intl", "es2025.iterator", "es2025.promise", "es2025.regexp", "esnext.asynciterable", "esnext.symbol", "esnext.bigint", "esnext.weakref", "esnext.object", "esnext.regexp", "esnext.string", "esnext.float16", "esnext.iterator", "esnext.promise", "esnext.array", "esnext.collection", "esnext.date", "esnext.decorators", "esnext.disposable", "esnext.error", "esnext.intl", "esnext.sharedmemory", "esnext.temporal", "esnext.typedarrays", "decorators", "decorators.legacy"]>, Schema.String, never, never>>>;
|
|
30
|
+
readonly types: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
31
|
+
readonly typeRoots: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
32
|
+
readonly strict: Schema.optionalKey<Schema.Boolean>;
|
|
33
|
+
readonly skipLibCheck: Schema.optionalKey<Schema.Boolean>;
|
|
34
|
+
readonly esModuleInterop: Schema.optionalKey<Schema.Boolean>;
|
|
35
|
+
readonly allowSyntheticDefaultImports: Schema.optionalKey<Schema.Boolean>;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* The decoded whitelist: canonical tsconfig spellings, every field optional.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
type TypeResolutionCompilerOptions = typeof TypeResolutionCompilerOptions.Type;
|
|
43
|
+
/**
|
|
44
|
+
* Decode compiler options written in either spelling into the whitelist.
|
|
45
|
+
*
|
|
46
|
+
* @remarks
|
|
47
|
+
* Accepts the tsconfig spelling a user writes (`target: "es2025"`, and
|
|
48
|
+
* case-insensitively `lib: ["ESNext", "DOM"]`) and the programmatic spelling a
|
|
49
|
+
* caller holding `ts.CompilerOptions` has (`target: ts.ScriptTarget.ES2025`),
|
|
50
|
+
* because a consumer configuring a documentation build in TypeScript
|
|
51
|
+
* reasonably produces either.
|
|
52
|
+
*
|
|
53
|
+
* **Fails rather than guesses.** A value with no entry in the enum tables — a
|
|
54
|
+
* numeric target from a future TypeScript, a misspelled module kind — is
|
|
55
|
+
* rejected on the error channel instead of being passed through. Degrading to
|
|
56
|
+
* a default here would type-check every example against a configuration the
|
|
57
|
+
* user did not ask for, and produce confidently wrong output with no error:
|
|
58
|
+
* the failure mode this seam exists to prevent.
|
|
59
|
+
*
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
declare const decodeCompilerOptions: (input: unknown) => Result.Result<TypeResolutionCompilerOptions, Schema.SchemaError>;
|
|
63
|
+
/**
|
|
64
|
+
* Convert whitelisted options to the numeric-enum form the TypeScript compiler
|
|
65
|
+
* takes.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* The ONE conversion site between the tsconfig spelling and the programmatic
|
|
69
|
+
* one. Two consequences follow from it being single:
|
|
70
|
+
*
|
|
71
|
+
* - Any environment fingerprint MUST be computed on the ENCODED value.
|
|
72
|
+
* Otherwise `{lib:["ESNext"]}` and `{lib:["lib.esnext.d.ts"]}` build two
|
|
73
|
+
* identical TypeScript environments under different keys.
|
|
74
|
+
* - There is no cast here. The whitelist is a subset of the kit's own
|
|
75
|
+
* `CompilerOptions`, so it is assignable to the encoder by construction —
|
|
76
|
+
* which is precisely what a hand-rolled options type could not be.
|
|
77
|
+
*
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
declare const toProgrammaticCompilerOptions: (options: TypeResolutionCompilerOptions) => ProgrammaticCompilerOptions;
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/TsconfigParser.d.ts
|
|
83
|
+
/**
|
|
84
|
+
* Error thrown when tsconfig.json parsing fails.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* Retained as the plugin's own type rather than surfacing the kit's
|
|
88
|
+
* `TsconfigParseError`/`TsconfigExtendsError` directly: the adapter's
|
|
89
|
+
* `typescript-config.ts` branches on `instanceof TsConfigParseError` to decide
|
|
90
|
+
* whether a failure is already reported, and both kit errors mean the same
|
|
91
|
+
* thing to that caller. It now also carries a decode failure from
|
|
92
|
+
* {@link decodeCompilerOptions}, which is the same thing again: a tsconfig
|
|
93
|
+
* this tool cannot act on.
|
|
94
|
+
*
|
|
95
|
+
* @public
|
|
96
|
+
*/
|
|
97
|
+
declare class TsConfigParseError extends Error {
|
|
98
|
+
readonly configPath: string;
|
|
99
|
+
readonly cause?: unknown | undefined;
|
|
100
|
+
constructor(configPath: string, message: string, cause?: unknown | undefined);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse a `tsconfig.json` and extract the compiler options used for type
|
|
104
|
+
* resolution.
|
|
105
|
+
*
|
|
106
|
+
* @param configPath - Path to tsconfig.json (relative or absolute)
|
|
107
|
+
* @param projectRoot - Project root directory for resolving relative paths
|
|
108
|
+
* @returns The declared compiler options, in the tsconfig spelling
|
|
109
|
+
* @throws TsConfigParseError if the config cannot be read or parsed
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const options = parseTsConfig("tsconfig.json", "/path/to/project");
|
|
114
|
+
* // Returns: { target: "es2025", module: "nodenext", lib: ["esnext"], ... }
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
119
|
+
declare function parseTsConfig(configPath: string, projectRoot: string): TypeResolutionCompilerOptions;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/Vfs.d.ts
|
|
122
|
+
/**
|
|
123
|
+
* The package's currency type: a virtual file system mapping
|
|
124
|
+
* `node_modules/`-prefixed paths to file contents.
|
|
125
|
+
*/
|
|
126
|
+
/**
|
|
127
|
+
* A virtual file system: file paths (prefixed `node_modules/<package>/`)
|
|
128
|
+
* mapped to their string contents.
|
|
129
|
+
*
|
|
130
|
+
* @remarks
|
|
131
|
+
* This is the value every loading operation produces and every TypeScript
|
|
132
|
+
* integration consumes. Maps from multiple packages merge with {@link mergeVfs};
|
|
133
|
+
* `@typescript/vfs` consumes the merged map directly (see `TsEnvironment`).
|
|
134
|
+
*
|
|
135
|
+
* @public
|
|
136
|
+
*/
|
|
137
|
+
type Vfs = Map<string, string>;
|
|
138
|
+
/**
|
|
139
|
+
* Merge VFS maps left to right into a new map; later entries win on path
|
|
140
|
+
* collisions.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* import { mergeVfs } from "@tsdoctor/vfs";
|
|
145
|
+
*
|
|
146
|
+
* const combined = mergeVfs(vfsA, vfsB);
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @public
|
|
150
|
+
*/
|
|
151
|
+
declare const mergeVfs: (...maps: ReadonlyArray<ReadonlyMap<string, string>>) => Vfs;
|
|
152
|
+
/**
|
|
153
|
+
* Prefix every path in `entries` with `node_modules/<name>/`, normalizing
|
|
154
|
+
* away leading slashes.
|
|
155
|
+
*
|
|
156
|
+
* @public
|
|
157
|
+
*/
|
|
158
|
+
declare const prefixVfs: (name: string, entries: ReadonlyMap<string, string>) => Vfs;
|
|
159
|
+
/**
|
|
160
|
+
* Whether a path names a TypeScript declaration file.
|
|
161
|
+
*
|
|
162
|
+
* @remarks
|
|
163
|
+
* The single spelling of this predicate. `TsEnvironment` uses it to pick a
|
|
164
|
+
* VFS map's root files, and `@tsdoctor/registry`'s module resolution uses it
|
|
165
|
+
* to decide whether a resolved path is a declaration. Two spellings of "is
|
|
166
|
+
* this a `.d.ts`" would be free to drift, and the drift would be silent — a
|
|
167
|
+
* root file quietly missing from a TypeScript environment degrades hovers
|
|
168
|
+
* without producing an error.
|
|
169
|
+
*
|
|
170
|
+
* @public
|
|
171
|
+
*/
|
|
172
|
+
declare const isTypeDefinition: (filePath: string) => boolean;
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/TsEnvironment.d.ts
|
|
175
|
+
declare const TsEnvironmentError_base: Schema.Class<TsEnvironmentError, Schema.TaggedStruct<"TsEnvironmentError", {
|
|
176
|
+
/** The underlying failure, preserved structurally. */
|
|
177
|
+
readonly cause: Schema.Defect;
|
|
178
|
+
}>, import("effect/Cause").YieldableError>;
|
|
179
|
+
/**
|
|
180
|
+
* Raised when building a virtual TypeScript environment fails — including
|
|
181
|
+
* when the optional `typescript` / `@typescript/vfs` /
|
|
182
|
+
* `@effected/tsconfig-json` peers are not installed.
|
|
183
|
+
*
|
|
184
|
+
* @public
|
|
185
|
+
*/
|
|
186
|
+
declare class TsEnvironmentError extends TsEnvironmentError_base {
|
|
187
|
+
get message(): string;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Options for {@link TsEnvironment.make}.
|
|
191
|
+
*
|
|
192
|
+
* @public
|
|
193
|
+
*/
|
|
194
|
+
interface TsEnvironmentOptions {
|
|
195
|
+
/** The virtual file system to typecheck against. */
|
|
196
|
+
readonly vfs: Vfs;
|
|
197
|
+
/**
|
|
198
|
+
* Compiler options for the language service, in tsconfig JSON form
|
|
199
|
+
* (`{ target: "es2022" }`, not `ts.ScriptTarget.ES2022`). Enum-valued
|
|
200
|
+
* fields are converted to the compiler's numeric enums internally, so this
|
|
201
|
+
* type has no dependency on the `typescript` package.
|
|
202
|
+
*/
|
|
203
|
+
readonly compilerOptions: CompilerOptions.Type;
|
|
204
|
+
/**
|
|
205
|
+
* The directory VFS paths are rooted under and the filesystem fallback
|
|
206
|
+
* root. Defaults to `process.cwd()` (which v3 hardcoded).
|
|
207
|
+
*/
|
|
208
|
+
readonly projectRoot?: string;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The `@typescript/vfs` seam: builds a `VirtualTypeScriptEnvironment` over a
|
|
212
|
+
* {@link Vfs} plus the TypeScript default lib files.
|
|
213
|
+
*
|
|
214
|
+
* @remarks
|
|
215
|
+
* The ONLY module touching the optional `typescript` / `@typescript/vfs` /
|
|
216
|
+
* `@effected/tsconfig-json` peers, and it loads all three lazily inside
|
|
217
|
+
* {@link TsEnvironment.make} — a consumer that never calls it never loads
|
|
218
|
+
* the compiler, and a missing peer fails typed as
|
|
219
|
+
* {@link TsEnvironmentError} instead of crashing at import time. Keep every
|
|
220
|
+
* one of them behind that dynamic `import()`: a static value import here is
|
|
221
|
+
* reachable from `index.ts`, so it would turn an omitted optional peer into
|
|
222
|
+
* an `ERR_MODULE_NOT_FOUND` on the entry graph for consumers who never
|
|
223
|
+
* touch this module. Only the type-only `CompilerOptions` import is safe
|
|
224
|
+
* statically, because it erases. The underlying `createDefaultMapFromNodeModules` /
|
|
225
|
+
* `createFSBackedSystem` read the real filesystem through TypeScript's own
|
|
226
|
+
* `sys`, outside the Effect `FileSystem` service — accepted and documented;
|
|
227
|
+
* this module is why the package is integrated tier on its own surface.
|
|
228
|
+
*
|
|
229
|
+
* No cache map (v3's `createTypeScriptCache` returned a one-entry `Map`
|
|
230
|
+
* keyed by `JSON.stringify(compilerOptions)`): a consumer that wants keyed
|
|
231
|
+
* reuse holds its own map.
|
|
232
|
+
*
|
|
233
|
+
* `VirtualTypeScriptEnvironment` is deliberately not re-exported — import
|
|
234
|
+
* the type from `@typescript/vfs`, which consumers of this module already
|
|
235
|
+
* declare.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* import { TsEnvironment } from "@tsdoctor/vfs";
|
|
240
|
+
*
|
|
241
|
+
* const environment = TsEnvironment.make({
|
|
242
|
+
* vfs,
|
|
243
|
+
* compilerOptions: { strict: true, target: "es2022" },
|
|
244
|
+
* });
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* @public
|
|
248
|
+
*/
|
|
249
|
+
declare class TsEnvironment {
|
|
250
|
+
private constructor();
|
|
251
|
+
/** Build a `VirtualTypeScriptEnvironment` over a {@link Vfs}. */
|
|
252
|
+
static make(options: TsEnvironmentOptions): Effect.Effect<VirtualTypeScriptEnvironment, TsEnvironmentError>;
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/TypeScriptConfig.d.ts
|
|
256
|
+
/**
|
|
257
|
+
* How a caller points at TypeScript configuration: a `tsconfig.json`, inline
|
|
258
|
+
* compiler options, or both.
|
|
259
|
+
*
|
|
260
|
+
* @remarks
|
|
261
|
+
* When both are given the tsconfig is loaded first and the inline options merge
|
|
262
|
+
* on top, so a caller can adopt a project's configuration and override one
|
|
263
|
+
* field without restating it.
|
|
264
|
+
*
|
|
265
|
+
* @public
|
|
266
|
+
*/
|
|
267
|
+
interface TypeScriptConfig {
|
|
268
|
+
/**
|
|
269
|
+
* A `tsconfig.json` path, or a function returning compiler options.
|
|
270
|
+
*
|
|
271
|
+
* @remarks
|
|
272
|
+
* The function's options are **user input**, so they are typed loosely and
|
|
273
|
+
* decoded rather than trusted: a caller writing configuration in TypeScript
|
|
274
|
+
* may reasonably return either the tsconfig spelling (`target: "es2025"`) or
|
|
275
|
+
* the programmatic one (`target: ts.ScriptTarget.ES2025`).
|
|
276
|
+
*/
|
|
277
|
+
tsconfig?: PathLike | (() => Promise<CompilerOptionsInput>);
|
|
278
|
+
/** User-supplied compiler options, in either spelling. Decoded, not trusted. */
|
|
279
|
+
compilerOptions?: CompilerOptionsInput;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Compiler options as a user may write them, before decoding.
|
|
283
|
+
*
|
|
284
|
+
* @remarks
|
|
285
|
+
* Deliberately loose. `@tsdoctor/vfs`'s `decodeCompilerOptions` is what turns
|
|
286
|
+
* this into a `TypeResolutionCompilerOptions`, accepting either spelling
|
|
287
|
+
* and REJECTING a value it cannot map rather than passing it through. This type
|
|
288
|
+
* exists so the untrusted shape and the decoded one cannot be confused at a
|
|
289
|
+
* call site — the previous single type was both, and user input reached the
|
|
290
|
+
* compiler through a cast.
|
|
291
|
+
*
|
|
292
|
+
* @public
|
|
293
|
+
*/
|
|
294
|
+
type CompilerOptionsInput = Readonly<Record<string, unknown>>;
|
|
295
|
+
/**
|
|
296
|
+
* Default TypeScript compiler options for Twoslash and type resolution.
|
|
297
|
+
*
|
|
298
|
+
* These defaults are optimized for documentation:
|
|
299
|
+
* - Modern ES targets (ESNext)
|
|
300
|
+
* - Bundler module resolution for broad compatibility
|
|
301
|
+
* - Lenient settings (non-strict) since docs often show simplified examples
|
|
302
|
+
* - Skip lib checks for faster processing
|
|
303
|
+
*
|
|
304
|
+
* @remarks
|
|
305
|
+
* Numeric values correspond to TypeScript enums:
|
|
306
|
+
* - target: 99 = ESNext
|
|
307
|
+
* - module: 99 = ESNext
|
|
308
|
+
* - moduleResolution: 100 = Bundler
|
|
309
|
+
*
|
|
310
|
+
* @public
|
|
311
|
+
*/
|
|
312
|
+
declare const DEFAULT_COMPILER_OPTIONS: TypeResolutionCompilerOptions;
|
|
313
|
+
/**
|
|
314
|
+
* Merge one set of compiler options over another, later winning per key.
|
|
315
|
+
*
|
|
316
|
+
* @remarks
|
|
317
|
+
* Arrays (`lib`, `types`) are REPLACED wholesale rather than concatenated,
|
|
318
|
+
* matching TypeScript's own `extends` semantics: declaring `lib` means "these
|
|
319
|
+
* libraries", not "these as well as the defaults".
|
|
320
|
+
*
|
|
321
|
+
* @param base - the options to start from
|
|
322
|
+
* @param override - the options to layer on top, or `undefined` for a copy of `base`
|
|
323
|
+
* @returns a new object; neither argument is mutated
|
|
324
|
+
*
|
|
325
|
+
* @public
|
|
326
|
+
*/
|
|
327
|
+
declare function mergeCompilerOptions(base: TypeResolutionCompilerOptions, override: TypeResolutionCompilerOptions | undefined): TypeResolutionCompilerOptions;
|
|
328
|
+
/**
|
|
329
|
+
* Resolve a single TypeScriptConfig to compiler options (sync version).
|
|
330
|
+
* Only handles path-based tsconfig - use resolveTypeScriptConfigSingleAsync for function-based.
|
|
331
|
+
*
|
|
332
|
+
* Follows the priority cascade:
|
|
333
|
+
* 1. Parse tsconfig.json if specified (path only, not function)
|
|
334
|
+
* 2. Merge compilerOptions on top
|
|
335
|
+
*
|
|
336
|
+
* @param config - TypeScript config with optional tsconfig path and/or compilerOptions
|
|
337
|
+
* @param projectRoot - Project root for resolving relative tsconfig paths
|
|
338
|
+
* @returns Resolved compiler options (not merged with defaults)
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* // Just tsconfig
|
|
343
|
+
* resolveTypeScriptConfigSingle({ tsconfig: "tsconfig.json" }, "/project");
|
|
344
|
+
*
|
|
345
|
+
* // Just compilerOptions
|
|
346
|
+
* resolveTypeScriptConfigSingle({ compilerOptions: { target: "esnext" } }, "/project");
|
|
347
|
+
*
|
|
348
|
+
* // Both (compilerOptions override tsconfig)
|
|
349
|
+
* resolveTypeScriptConfigSingle({
|
|
350
|
+
* tsconfig: "tsconfig.json",
|
|
351
|
+
* compilerOptions: { strict: false }
|
|
352
|
+
* }, "/project");
|
|
353
|
+
* ```
|
|
354
|
+
*
|
|
355
|
+
* @public
|
|
356
|
+
*/
|
|
357
|
+
declare function resolveTypeScriptConfigSingle(config: TypeScriptConfig | undefined, projectRoot: string): TypeResolutionCompilerOptions;
|
|
358
|
+
/**
|
|
359
|
+
* Resolve a single TypeScriptConfig to compiler options (async version).
|
|
360
|
+
* Handles both path-based and function-based tsconfig.
|
|
361
|
+
*
|
|
362
|
+
* Follows the priority cascade:
|
|
363
|
+
* 1. Load tsconfig (from path or function)
|
|
364
|
+
* 2. Merge compilerOptions on top
|
|
365
|
+
*
|
|
366
|
+
* @param config - TypeScript config with optional tsconfig path/function and/or compilerOptions
|
|
367
|
+
* @param projectRoot - Project root for resolving relative tsconfig paths
|
|
368
|
+
* @returns Promise resolving to compiler options (not merged with defaults)
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* // Path-based tsconfig
|
|
373
|
+
* await resolveTypeScriptConfigSingleAsync({ tsconfig: "tsconfig.json" }, "/project");
|
|
374
|
+
*
|
|
375
|
+
* // Function-based tsconfig
|
|
376
|
+
* await resolveTypeScriptConfigSingleAsync({
|
|
377
|
+
* tsconfig: async () => ({ target: 99, lib: ["ESNext"] })
|
|
378
|
+
* }, "/project");
|
|
379
|
+
*
|
|
380
|
+
* // Both (compilerOptions override tsconfig)
|
|
381
|
+
* await resolveTypeScriptConfigSingleAsync({
|
|
382
|
+
* tsconfig: async () => ({ target: 99 }),
|
|
383
|
+
* compilerOptions: { strict: false }
|
|
384
|
+
* }, "/project");
|
|
385
|
+
* ```
|
|
386
|
+
*
|
|
387
|
+
* @public
|
|
388
|
+
*/
|
|
389
|
+
declare function resolveTypeScriptConfigSingleAsync(config: TypeScriptConfig | undefined, projectRoot: string): Promise<TypeResolutionCompilerOptions>;
|
|
390
|
+
/**
|
|
391
|
+
* Resolve TypeScript compiler options from a cascade of configurations (async).
|
|
392
|
+
*
|
|
393
|
+
* Resolution order (later levels override earlier):
|
|
394
|
+
* 1. DEFAULT_COMPILER_OPTIONS (sensible defaults)
|
|
395
|
+
* 2. Global config
|
|
396
|
+
* 3. API-level config
|
|
397
|
+
*
|
|
398
|
+
* At each level, if a TypeScriptConfig has both `tsconfig` and `compilerOptions`,
|
|
399
|
+
* the tsconfig is loaded first, then compilerOptions are merged on top.
|
|
400
|
+
*
|
|
401
|
+
* @param projectRoot - Project root directory for resolving relative paths
|
|
402
|
+
* @param global - Global plugin TypeScript configuration
|
|
403
|
+
* @param api - API-level TypeScript configuration
|
|
404
|
+
* @returns Promise resolving to fully resolved compiler options
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* // Simple global config
|
|
409
|
+
* const options = await resolveTypeScriptConfig("/project", {
|
|
410
|
+
* tsconfig: "tsconfig.json"
|
|
411
|
+
* });
|
|
412
|
+
*
|
|
413
|
+
* // With async tsconfig loader
|
|
414
|
+
* const options = await resolveTypeScriptConfig("/project", {
|
|
415
|
+
* tsconfig: async () => ({ target: 99, lib: ["ESNext"] })
|
|
416
|
+
* });
|
|
417
|
+
*
|
|
418
|
+
* // With API override
|
|
419
|
+
* const options = await resolveTypeScriptConfig(
|
|
420
|
+
* "/project",
|
|
421
|
+
* { tsconfig: "tsconfig.json" },
|
|
422
|
+
* { compilerOptions: { strict: false } }
|
|
423
|
+
* );
|
|
424
|
+
* ```
|
|
425
|
+
*
|
|
426
|
+
* @public
|
|
427
|
+
*/
|
|
428
|
+
declare function resolveTypeScriptConfig(projectRoot: string, global?: TypeScriptConfig, api?: TypeScriptConfig): Promise<TypeResolutionCompilerOptions>;
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/VirtualPackage.d.ts
|
|
431
|
+
declare const VirtualPackage_base: Schema.Class<VirtualPackage, Schema.Struct<{
|
|
432
|
+
/** The package name (e.g. `"@my-org/api-types"`). */
|
|
433
|
+
readonly name: Schema.String;
|
|
434
|
+
/** The package version. */
|
|
435
|
+
readonly version: Schema.String;
|
|
436
|
+
/** Entry file names (e.g. `"index.d.ts"`) mapped to declaration source. */
|
|
437
|
+
readonly entries: Schema.$ReadonlyMap<Schema.String, Schema.String>;
|
|
438
|
+
}>, {}>;
|
|
439
|
+
/**
|
|
440
|
+
* A synthetic npm package built from locally supplied TypeScript declaration
|
|
441
|
+
* content, for inclusion in a {@link Vfs} without fetching from the CDN.
|
|
442
|
+
*
|
|
443
|
+
* @remarks
|
|
444
|
+
* Useful when you have locally generated `.d.ts` files — API Extractor
|
|
445
|
+
* output, hand-written ambient declarations — and want them in the same VFS
|
|
446
|
+
* `TypeRegistry` builds from remote packages. Instances are transient: they
|
|
447
|
+
* are never persisted to the disk cache.
|
|
448
|
+
*
|
|
449
|
+
* The class is deliberately subclass-friendly (the rspress consumer extends
|
|
450
|
+
* it): construct via `VirtualPackage.make(...)` or the statics, and extend
|
|
451
|
+
* with `class Mine extends VirtualPackage { ... }`.
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* ```ts
|
|
455
|
+
* import { VirtualPackage } from "@tsdoctor/vfs";
|
|
456
|
+
*
|
|
457
|
+
* const pkg = VirtualPackage.create("@my-org/api-types", "1.0.0", "export interface User { id: string }");
|
|
458
|
+
* const vfs = pkg.toVfs();
|
|
459
|
+
* // node_modules/@my-org/api-types/package.json, node_modules/@my-org/api-types/index.d.ts
|
|
460
|
+
* ```
|
|
461
|
+
*
|
|
462
|
+
* @public
|
|
463
|
+
*/
|
|
464
|
+
declare class VirtualPackage extends VirtualPackage_base {
|
|
465
|
+
/**
|
|
466
|
+
* Single-entry factory: a virtual package whose sole entry point is
|
|
467
|
+
* `index.d.ts`.
|
|
468
|
+
*/
|
|
469
|
+
static create(name: string, version: string, declarations: string): VirtualPackage;
|
|
470
|
+
/**
|
|
471
|
+
* Multi-entry factory: one `.d.ts` per entry point, exposed through a
|
|
472
|
+
* synthetic `exports` map.
|
|
473
|
+
*
|
|
474
|
+
* @remarks
|
|
475
|
+
* An empty entries map is developer wiring, not input — it would produce a
|
|
476
|
+
* package whose `types` points at a file that does not exist — so it
|
|
477
|
+
* throws at construction (defect posture), as does an entry set whose
|
|
478
|
+
* names collide after extension normalization (see
|
|
479
|
+
* {@link VirtualPackage.toVfs}).
|
|
480
|
+
*/
|
|
481
|
+
static createMultiEntry(name: string, version: string, entries: ReadonlyMap<string, string>): VirtualPackage;
|
|
482
|
+
/**
|
|
483
|
+
* Load a single `.d.ts` file from disk as a virtual package with one
|
|
484
|
+
* `index.d.ts` entry.
|
|
485
|
+
*
|
|
486
|
+
* @remarks
|
|
487
|
+
* Reads through the platform-agnostic `FileSystem` service; the
|
|
488
|
+
* `PlatformError` surfaces typed.
|
|
489
|
+
*/
|
|
490
|
+
static fromFile(name: string, version: string, filePath: string): Effect.Effect<VirtualPackage, PlatformError.PlatformError, FileSystem.FileSystem>;
|
|
491
|
+
/**
|
|
492
|
+
* The package's {@link Vfs}: a synthetic `package.json` plus every entry
|
|
493
|
+
* file, each path prefixed `node_modules/<name>/`.
|
|
494
|
+
*
|
|
495
|
+
* @remarks
|
|
496
|
+
* The `package.json` uses `types` for a single entry and an `exports` map
|
|
497
|
+
* for multiple entries, so TypeScript module resolution works against the
|
|
498
|
+
* generated VFS.
|
|
499
|
+
*/
|
|
500
|
+
toVfs(): Vfs;
|
|
501
|
+
private toPackageJson;
|
|
502
|
+
}
|
|
503
|
+
//#endregion
|
|
504
|
+
export { type CompilerOptionsInput, DEFAULT_COMPILER_OPTIONS, TsConfigParseError, TsEnvironment, TsEnvironmentError, type TsEnvironmentOptions, TypeResolutionCompilerOptions, type TypeScriptConfig, type Vfs, VirtualPackage, decodeCompilerOptions, isTypeDefinition, mergeCompilerOptions, mergeVfs, parseTsConfig, prefixVfs, resolveTypeScriptConfig, resolveTypeScriptConfigSingle, resolveTypeScriptConfigSingleAsync, toProgrammaticCompilerOptions };
|
|
505
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TypeResolutionCompilerOptions, decodeCompilerOptions, toProgrammaticCompilerOptions } from "./TypeResolutionOptions.js";
|
|
2
|
+
import { TsConfigParseError, parseTsConfig } from "./TsconfigParser.js";
|
|
3
|
+
import { isTypeDefinition, mergeVfs, prefixVfs } from "./Vfs.js";
|
|
4
|
+
import { TsEnvironment, TsEnvironmentError } from "./TsEnvironment.js";
|
|
5
|
+
import { DEFAULT_COMPILER_OPTIONS, mergeCompilerOptions, resolveTypeScriptConfig, resolveTypeScriptConfigSingle, resolveTypeScriptConfigSingleAsync } from "./TypeScriptConfig.js";
|
|
6
|
+
import { VirtualPackage } from "./VirtualPackage.js";
|
|
7
|
+
|
|
8
|
+
export { DEFAULT_COMPILER_OPTIONS, TsConfigParseError, TsEnvironment, TsEnvironmentError, TypeResolutionCompilerOptions, VirtualPackage, decodeCompilerOptions, isTypeDefinition, mergeCompilerOptions, mergeVfs, parseTsConfig, prefixVfs, resolveTypeScriptConfig, resolveTypeScriptConfigSingle, resolveTypeScriptConfigSingleAsync, toProgrammaticCompilerOptions };
|