rspress-plugin-api-extractor 0.10.0 → 0.12.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/BuildEnv.js +58 -0
- package/build-program.js +34 -33
- package/build-stages.js +48 -42
- package/config-helpers.js +7 -7
- package/errors.js +1 -6
- package/index.d.ts +84 -86
- package/layers/AppLayer.js +67 -0
- package/layers/api-results.js +83 -0
- package/layers/build-metrics.js +1 -1
- package/layers/config-resolution.js +407 -0
- package/layers/external-types.js +74 -0
- package/layers/{ObservabilityLive.js → observability.js} +3 -3
- package/layers/type-environment.js +109 -0
- package/layers/xdg.js +44 -0
- package/markdown/helpers.js +9 -55
- package/markdown/page-generators/class-page.js +8 -31
- package/markdown/page-generators/index-pages.js +6 -8
- package/markdown/page-generators/interface-page.js +7 -7
- package/markdown/shiki-utils.js +65 -10
- package/model-loader.js +3 -3
- package/observability/EventBus.js +29 -7
- package/observability/heartbeat.js +1 -1
- package/observability/sinks/metrics-sink.js +1 -1
- package/observability/sinks/trace-sink.js +4 -4
- package/observability/spans.js +3 -1
- package/observability/sync-emitter.js +78 -0
- package/og-resolver.js +74 -284
- package/package.json +3 -4
- package/path-derivation.js +19 -1
- package/plugin.js +63 -91
- package/prettier-formatter.js +5 -11
- package/remark-api-codeblocks.js +11 -19
- package/remark-with-api.js +11 -21
- package/schemas/config.js +0 -2
- package/services/ConfigService.js +37 -2
- package/services/HighlighterService.js +75 -0
- package/services/OgService.js +190 -0
- package/services/PluginConfig.js +26 -0
- package/services/TwoslashCacheService.js +128 -2
- package/services/TwoslashEnvironments.js +35 -0
- package/services/TypeRegistryService.js +178 -2
- package/shiki-transformer.js +53 -234
- package/sync-node-fs.js +6 -6
- package/tsconfig-parser.js +77 -95
- package/twoslash-access.js +48 -0
- package/twoslash-transformer.js +106 -83
- package/vfs-registry.js +1 -31
- package/layers/ConfigServiceLive.js +0 -600
- package/layers/PathDerivationServiceLive.js +0 -16
- package/layers/TwoslashCacheServiceLive.js +0 -53
- package/layers/TypeRegistryServiceLive.js +0 -155
- package/markdown/index.js +0 -11
- package/schemas/index.js +0 -6
- package/services/PathDerivationService.js +0 -7
|
@@ -1,600 +0,0 @@
|
|
|
1
|
-
import { PluginEvent } from "../observability/events.js";
|
|
2
|
-
import { emit, wantsLevel } from "../observability/EventBus.js";
|
|
3
|
-
import { BuildMetrics } from "./build-metrics.js";
|
|
4
|
-
import "./ObservabilityLive.js";
|
|
5
|
-
import { TypeReferenceExtractor } from "../type-reference-extractor.js";
|
|
6
|
-
import { OpenGraphResolver } from "../og-resolver.js";
|
|
7
|
-
import { withPhase } from "../observability/spans.js";
|
|
8
|
-
import { resolveTypeScriptConfig } from "../typescript-config.js";
|
|
9
|
-
import { TwoslashManager } from "../twoslash-transformer.js";
|
|
10
|
-
import { classifyApiConfig, extractAutoDetectedPackages, isVersionConfig, mergeLlmsPluginConfig, validateExternalPackages } from "../config-utils.js";
|
|
11
|
-
import { ApiExtractedPackage } from "../api-extracted-package.js";
|
|
12
|
-
import { CategoryResolver } from "../category-resolver.js";
|
|
13
|
-
import { ConfigValidationError } from "../errors.js";
|
|
14
|
-
import { HideCutLinesTransformer, MemberFormatTransformer } from "../hide-cut-transformer.js";
|
|
15
|
-
import { DEFAULT_SHIKI_THEMES } from "../markdown/shiki-utils.js";
|
|
16
|
-
import { loadApiModel, loadPackageJson, loadVersionModel } from "../model-loader.js";
|
|
17
|
-
import { DEFAULT_CATEGORIES } from "../schemas/config.js";
|
|
18
|
-
import "../schemas/index.js";
|
|
19
|
-
import { ConfigService } from "../services/ConfigService.js";
|
|
20
|
-
import { PathDerivationService } from "../services/PathDerivationService.js";
|
|
21
|
-
import { TwoslashCacheService } from "../services/TwoslashCacheService.js";
|
|
22
|
-
import { TypeRegistryService } from "../services/TypeRegistryService.js";
|
|
23
|
-
import { makeTwoslashCache, twoslashEnvHash } from "../twoslash-cache.js";
|
|
24
|
-
import path from "node:path";
|
|
25
|
-
import { hashContent } from "@tsdoctor/snapshot";
|
|
26
|
-
import { Effect, Layer, Metric } from "effect";
|
|
27
|
-
import ts from "typescript";
|
|
28
|
-
import os from "node:os";
|
|
29
|
-
import { createHighlighter } from "shiki";
|
|
30
|
-
|
|
31
|
-
//#region src/layers/ConfigServiceLive.ts
|
|
32
|
-
const DEFAULT_THRESHOLDS = {
|
|
33
|
-
slowCodeBlock: 100,
|
|
34
|
-
slowPageGeneration: 500,
|
|
35
|
-
slowApiLoad: 1e3,
|
|
36
|
-
slowFileOperation: 50,
|
|
37
|
-
slowHttpRequest: 2e3,
|
|
38
|
-
slowDbOperation: 100
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Normalize theme configuration from user input to a consistent format.
|
|
42
|
-
*/
|
|
43
|
-
function normalizeThemeConfig(theme) {
|
|
44
|
-
if (!theme) return { ...DEFAULT_SHIKI_THEMES };
|
|
45
|
-
if (typeof theme === "string") return {
|
|
46
|
-
light: theme,
|
|
47
|
-
dark: theme
|
|
48
|
-
};
|
|
49
|
-
if ("light" in theme && "dark" in theme && typeof theme.light === "string" && typeof theme.dark === "string") return {
|
|
50
|
-
light: theme.light,
|
|
51
|
-
dark: theme.dark
|
|
52
|
-
};
|
|
53
|
-
return {
|
|
54
|
-
light: theme,
|
|
55
|
-
dark: theme
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Prepend import statements for external type references to the VFS declaration files.
|
|
60
|
-
* Returns per-entry payloads for event emission (heavy content/importRefs gated on wantTrace).
|
|
61
|
-
*/
|
|
62
|
-
function prependImportsToVfs(vfs, apiPackage, packageName, wantTrace) {
|
|
63
|
-
const extractor = new TypeReferenceExtractor(apiPackage, packageName);
|
|
64
|
-
const payloads = [];
|
|
65
|
-
for (const entryPoint of apiPackage.entryPoints) {
|
|
66
|
-
const entryEp = entryPoint;
|
|
67
|
-
const imports = extractor.extractImportsForEntryPoint(entryEp);
|
|
68
|
-
const importStatements = TypeReferenceExtractor.formatImports(imports);
|
|
69
|
-
const entryName = entryEp.displayName || "";
|
|
70
|
-
const file = `node_modules/${packageName}/${entryName ? `${entryName}.d.ts` : "index.d.ts"}`;
|
|
71
|
-
const hasImports = importStatements.length > 0;
|
|
72
|
-
if (hasImports) {
|
|
73
|
-
const existing = vfs.get(file);
|
|
74
|
-
if (existing) vfs.set(file, `${importStatements.join("\n")}\n\n${existing}`);
|
|
75
|
-
}
|
|
76
|
-
const content = vfs.get(file) ?? "";
|
|
77
|
-
const declCount = entryEp.members.length;
|
|
78
|
-
const contentHash = hashContent(content);
|
|
79
|
-
const importRefs = wantTrace && hasImports ? imports.map((i) => ({
|
|
80
|
-
from: i.packageName,
|
|
81
|
-
symbols: [...i.symbols]
|
|
82
|
-
})) : [];
|
|
83
|
-
payloads.push({
|
|
84
|
-
file,
|
|
85
|
-
entryPoint: entryName,
|
|
86
|
-
declCount,
|
|
87
|
-
contentHash,
|
|
88
|
-
content: wantTrace ? content : "",
|
|
89
|
-
hasImports,
|
|
90
|
-
importRefs
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return payloads;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Validate plugin options and return an Effect that fails with ConfigValidationError.
|
|
97
|
-
*/
|
|
98
|
-
function validateOptions(options, rspressConfig) {
|
|
99
|
-
return Effect.gen(function* () {
|
|
100
|
-
const api = options.api ?? void 0;
|
|
101
|
-
const apis = options.apis != null && options.apis.length > 0 ? options.apis : void 0;
|
|
102
|
-
const { multiVersion } = rspressConfig;
|
|
103
|
-
if (api && apis) return yield* new ConfigValidationError({
|
|
104
|
-
field: "api/apis",
|
|
105
|
-
reason: "Cannot provide both 'api' and 'apis'. Use 'api' for single-package sites or 'apis' for multi-package portals."
|
|
106
|
-
});
|
|
107
|
-
if (!api && !apis) {
|
|
108
|
-
if (classifyApiConfig(options) === "missing") return yield* new ConfigValidationError({
|
|
109
|
-
field: "api/apis",
|
|
110
|
-
reason: "Must provide either 'api' or 'apis'."
|
|
111
|
-
});
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (apis) {
|
|
115
|
-
if (multiVersion) return yield* new ConfigValidationError({
|
|
116
|
-
field: "apis",
|
|
117
|
-
reason: "multiVersion is not supported with 'apis' (multi-API mode). Use 'api' (single-API mode) for versioned documentation."
|
|
118
|
-
});
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (api) {
|
|
122
|
-
if (multiVersion) {
|
|
123
|
-
if (!api.versions) return yield* new ConfigValidationError({
|
|
124
|
-
field: "api.versions",
|
|
125
|
-
reason: "'versions' is required when multiVersion is active."
|
|
126
|
-
});
|
|
127
|
-
const pluginKeys = new Set(Object.keys(api.versions));
|
|
128
|
-
const rspressKeys = new Set(multiVersion.versions);
|
|
129
|
-
if (pluginKeys.size !== rspressKeys.size || ![...pluginKeys].every((k) => rspressKeys.has(k))) return yield* new ConfigValidationError({
|
|
130
|
-
field: "api.versions",
|
|
131
|
-
reason: `api.versions keys [${[...pluginKeys].join(", ")}] must exactly match multiVersion.versions [${[...rspressKeys].join(", ")}].`
|
|
132
|
-
});
|
|
133
|
-
} else {
|
|
134
|
-
if (api.versions) yield* emit(PluginEvent.ConfigCascadeWarning({
|
|
135
|
-
ctx: { buildId: "" },
|
|
136
|
-
level: "warn",
|
|
137
|
-
field: "versions",
|
|
138
|
-
chosen: "(none — multiVersion not configured)",
|
|
139
|
-
ignored: ["api.versions"]
|
|
140
|
-
}));
|
|
141
|
-
if (!api.model) return yield* new ConfigValidationError({
|
|
142
|
-
field: "api.model",
|
|
143
|
-
reason: "'model' is required when multiVersion is not active."
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Create ConfigServiceLive from plugin options.
|
|
151
|
-
* Resolves plugin options + RSPress config into a fully prepared build context
|
|
152
|
-
* with loaded models, type system, and resources.
|
|
153
|
-
*/
|
|
154
|
-
function ConfigServiceLive(options, shikiCrossLinker, buildId = "", resolvedThresholds) {
|
|
155
|
-
return Layer.effect(ConfigService, Effect.gen(function* () {
|
|
156
|
-
const typeRegistry = yield* TypeRegistryService;
|
|
157
|
-
const pathService = yield* PathDerivationService;
|
|
158
|
-
return { resolve: (rspressConfig) => Effect.gen(function* () {
|
|
159
|
-
const loadStart = performance.now();
|
|
160
|
-
const wantTrace = yield* wantsLevel("trace");
|
|
161
|
-
yield* validateOptions(options, { ...rspressConfig.multiVersion ? { multiVersion: {
|
|
162
|
-
default: rspressConfig.multiVersion.default,
|
|
163
|
-
versions: [...rspressConfig.multiVersion.versions]
|
|
164
|
-
} } : {} });
|
|
165
|
-
const rspressMultiVersion = rspressConfig.multiVersion;
|
|
166
|
-
const rspressLocales = rspressConfig.locales?.map((l) => l.lang) ?? [];
|
|
167
|
-
const rspressLang = rspressConfig.lang;
|
|
168
|
-
const docsRoot = rspressConfig.root;
|
|
169
|
-
const rspressRoot = docsRoot || process.cwd();
|
|
170
|
-
const categoryResolver = new CategoryResolver();
|
|
171
|
-
const pluginDefaults = categoryResolver.mergeCategories(DEFAULT_CATEGORIES, options.defaultCategories);
|
|
172
|
-
const apiConfigs = [];
|
|
173
|
-
const combinedVfs = /* @__PURE__ */ new Map();
|
|
174
|
-
const allExternalPackages = [];
|
|
175
|
-
let firstApiTsconfig;
|
|
176
|
-
let firstApiCompilerOptions;
|
|
177
|
-
/**
|
|
178
|
-
* Raw TypeScript config per API scope. Each documented package is
|
|
179
|
-
* type-checked under its OWN configuration; the build no longer picks
|
|
180
|
-
* one and applies it to everything.
|
|
181
|
-
*/
|
|
182
|
-
const scopeTsConfigs = /* @__PURE__ */ new Map();
|
|
183
|
-
/**
|
|
184
|
-
* Emit a typed ModelLoadFailed event for a failed model load, then
|
|
185
|
-
* convert the typed failure to a defect — a missing or unparsable
|
|
186
|
-
* model remains fatal to the build, exactly as before, but the
|
|
187
|
-
* event now rides the error channel instead of a sync-island seam.
|
|
188
|
-
*/
|
|
189
|
-
const withModelLoadEvents = (self) => self.pipe(Effect.tapError((error) => emit(PluginEvent.ModelLoadFailed({
|
|
190
|
-
ctx: { buildId },
|
|
191
|
-
level: "error",
|
|
192
|
-
modelPath: "modelPath" in error ? error.modelPath : "<loader function>",
|
|
193
|
-
reason: error.message
|
|
194
|
-
}))), Effect.orDie);
|
|
195
|
-
/**
|
|
196
|
-
* Helper to process a single API model (shared by single and multi modes).
|
|
197
|
-
*/
|
|
198
|
-
const processSimpleApi = (api, model, outputDir, fullRoute, wantTrace) => Effect.gen(function* () {
|
|
199
|
-
const { apiPackage, source: loaderSource } = yield* withModelLoadEvents(loadApiModel(model));
|
|
200
|
-
return yield* Effect.promise(async () => {
|
|
201
|
-
const resolvedCategories = categoryResolver.resolveCategoryConfig(pluginDefaults, api.categories);
|
|
202
|
-
const resolvedSource = categoryResolver.resolveSourceConfig(api.source, loaderSource);
|
|
203
|
-
const resolvedLlms = mergeLlmsPluginConfig(options.llmsPlugin, api.llmsPlugin);
|
|
204
|
-
const packageJson = api.packageJson ? await loadPackageJson(api.packageJson) : void 0;
|
|
205
|
-
validateExternalPackages(api.externalPackages, packageJson);
|
|
206
|
-
const externalPackages = api.externalPackages || extractAutoDetectedPackages(packageJson, api.autoDetectDependencies);
|
|
207
|
-
if (externalPackages && externalPackages.length > 0) Effect.runSync(Metric.update(BuildMetrics.externalPackagesTotal, externalPackages.length));
|
|
208
|
-
const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).generateVfs();
|
|
209
|
-
const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
|
|
210
|
-
const resolvedOgImage = api.ogImage ?? options.ogImage;
|
|
211
|
-
const resolvedTheme = normalizeThemeConfig(api.theme);
|
|
212
|
-
return {
|
|
213
|
-
vfs,
|
|
214
|
-
vfsPayloads,
|
|
215
|
-
externalPackages: externalPackages || [],
|
|
216
|
-
config: {
|
|
217
|
-
apiPackage,
|
|
218
|
-
packageName: api.packageName,
|
|
219
|
-
...api.name != null ? { apiName: api.name } : {},
|
|
220
|
-
outputDir,
|
|
221
|
-
baseRoute: fullRoute,
|
|
222
|
-
categories: resolvedCategories,
|
|
223
|
-
...resolvedSource != null ? { source: resolvedSource } : {},
|
|
224
|
-
...packageJson != null ? { packageJson } : {},
|
|
225
|
-
...resolvedLlms != null ? { llmsPlugin: resolvedLlms } : {},
|
|
226
|
-
...options.siteUrl != null ? { siteUrl: options.siteUrl } : {},
|
|
227
|
-
...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
|
|
228
|
-
docsDir: path.dirname(outputDir),
|
|
229
|
-
...docsRoot != null ? { docsRoot } : {},
|
|
230
|
-
...resolvedTheme != null ? { theme: resolvedTheme } : {}
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
yield* withPhase("modelLoad", { buildId }, Effect.gen(function* () {
|
|
236
|
-
if (options.api) {
|
|
237
|
-
const api = options.api;
|
|
238
|
-
const baseRoute = yield* pathService.normalizeBaseRoute(api.baseRoute ?? "/");
|
|
239
|
-
firstApiTsconfig = api.tsconfig;
|
|
240
|
-
firstApiCompilerOptions = api.compilerOptions;
|
|
241
|
-
scopeTsConfigs.set(apiScopeOf(baseRoute, api.packageName), rawTsConfig(api));
|
|
242
|
-
if (rspressMultiVersion && api.versions) {
|
|
243
|
-
const versionResults = yield* Effect.forEach(Object.entries(api.versions), ([version, versionValue]) => Effect.gen(function* () {
|
|
244
|
-
const versionDp = (yield* pathService.derivePaths({
|
|
245
|
-
mode: "single",
|
|
246
|
-
docsRoot: rspressRoot,
|
|
247
|
-
baseRoute,
|
|
248
|
-
apiFolder: api.apiFolder ?? "api",
|
|
249
|
-
locales: rspressLocales,
|
|
250
|
-
defaultLang: rspressLang,
|
|
251
|
-
versions: [version],
|
|
252
|
-
defaultVersion: rspressMultiVersion?.default
|
|
253
|
-
}))[0];
|
|
254
|
-
if (!versionDp) return {
|
|
255
|
-
vfs: /* @__PURE__ */ new Map(),
|
|
256
|
-
vfsPayloads: [],
|
|
257
|
-
externalPackages: [],
|
|
258
|
-
config: null
|
|
259
|
-
};
|
|
260
|
-
const versionConfig = isVersionConfig(versionValue) ? versionValue : { model: versionValue };
|
|
261
|
-
const { apiPackage, packageJson: versionPackageJson, categories: versionCategories, source: versionSource, externalPackages: versionExternalPackages, autoDetectDependencies: versionAutoDetectDependencies, llmsPlugin: versionLlms, ogImage: versionOgImage } = yield* withModelLoadEvents(loadVersionModel(versionConfig));
|
|
262
|
-
return yield* Effect.promise(async () => {
|
|
263
|
-
Effect.runSync(Metric.update(BuildMetrics.apiVersionsLoaded, 1));
|
|
264
|
-
const resolvedCategories = categoryResolver.resolveCategoryConfig(pluginDefaults, api.categories, versionCategories);
|
|
265
|
-
const resolvedSource = categoryResolver.resolveSourceConfig(api.source, versionSource);
|
|
266
|
-
const resolvedLlms = mergeLlmsPluginConfig(options.llmsPlugin, api.llmsPlugin, versionLlms);
|
|
267
|
-
const packageJson = versionPackageJson || (api.packageJson ? await loadPackageJson(api.packageJson) : void 0);
|
|
268
|
-
validateExternalPackages(versionExternalPackages || api.externalPackages, packageJson);
|
|
269
|
-
const autoDetectOptions = versionAutoDetectDependencies || api.autoDetectDependencies;
|
|
270
|
-
const externalPackages = versionExternalPackages || api.externalPackages || extractAutoDetectedPackages(packageJson, autoDetectOptions);
|
|
271
|
-
if (externalPackages && externalPackages.length > 0) Effect.runSync(Metric.update(BuildMetrics.externalPackagesTotal, externalPackages.length));
|
|
272
|
-
const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).generateVfs();
|
|
273
|
-
const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
|
|
274
|
-
const resolvedOgImage = versionOgImage ?? api.ogImage ?? options.ogImage;
|
|
275
|
-
const resolvedTheme = normalizeThemeConfig(api.theme);
|
|
276
|
-
const outputDir = versionDp.outputDir;
|
|
277
|
-
const fullRoute = versionDp.routeBase;
|
|
278
|
-
return {
|
|
279
|
-
vfs,
|
|
280
|
-
vfsPayloads,
|
|
281
|
-
externalPackages: externalPackages || [],
|
|
282
|
-
config: {
|
|
283
|
-
apiPackage,
|
|
284
|
-
packageName: `${api.packageName} (${version})`,
|
|
285
|
-
...api.name != null ? { apiName: api.name } : {},
|
|
286
|
-
outputDir,
|
|
287
|
-
baseRoute: fullRoute,
|
|
288
|
-
categories: resolvedCategories,
|
|
289
|
-
...resolvedSource != null ? { source: resolvedSource } : {},
|
|
290
|
-
...packageJson != null ? { packageJson } : {},
|
|
291
|
-
...resolvedLlms != null ? { llmsPlugin: resolvedLlms } : {},
|
|
292
|
-
...options.siteUrl != null ? { siteUrl: options.siteUrl } : {},
|
|
293
|
-
...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
|
|
294
|
-
docsDir: path.dirname(outputDir),
|
|
295
|
-
...docsRoot != null ? { docsRoot } : {},
|
|
296
|
-
...resolvedTheme != null ? { theme: resolvedTheme } : {}
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
});
|
|
300
|
-
}), { concurrency: "unbounded" });
|
|
301
|
-
for (const result of versionResults) {
|
|
302
|
-
for (const [filepath, content] of result.vfs.entries()) combinedVfs.set(filepath, content);
|
|
303
|
-
if (result.externalPackages.length > 0) allExternalPackages.push(...result.externalPackages);
|
|
304
|
-
if (result.config) apiConfigs.push(result.config);
|
|
305
|
-
for (const payload of result.vfsPayloads) {
|
|
306
|
-
yield* emit(PluginEvent.VfsGenerated({
|
|
307
|
-
ctx: {
|
|
308
|
-
buildId: "",
|
|
309
|
-
packageName: api.packageName,
|
|
310
|
-
...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
|
|
311
|
-
},
|
|
312
|
-
level: "debug",
|
|
313
|
-
file: payload.file,
|
|
314
|
-
declCount: payload.declCount,
|
|
315
|
-
contentHash: payload.contentHash,
|
|
316
|
-
...wantTrace && payload.content ? { content: payload.content } : {}
|
|
317
|
-
}));
|
|
318
|
-
if (payload.hasImports) yield* emit(PluginEvent.ImportsPrepended({
|
|
319
|
-
ctx: {
|
|
320
|
-
buildId: "",
|
|
321
|
-
packageName: api.packageName,
|
|
322
|
-
...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
|
|
323
|
-
},
|
|
324
|
-
level: "debug",
|
|
325
|
-
file: payload.file,
|
|
326
|
-
imports: wantTrace ? payload.importRefs : []
|
|
327
|
-
}));
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
} else if (api.model) {
|
|
331
|
-
const dp = (yield* pathService.derivePaths({
|
|
332
|
-
mode: "single",
|
|
333
|
-
docsRoot: rspressRoot,
|
|
334
|
-
baseRoute,
|
|
335
|
-
apiFolder: api.apiFolder ?? "api",
|
|
336
|
-
locales: rspressLocales,
|
|
337
|
-
defaultLang: rspressLang,
|
|
338
|
-
versions: [],
|
|
339
|
-
defaultVersion: void 0
|
|
340
|
-
}))[0];
|
|
341
|
-
if (dp) {
|
|
342
|
-
const result = yield* processSimpleApi(api, api.model, dp.outputDir, dp.routeBase, wantTrace);
|
|
343
|
-
for (const [filepath, content] of result.vfs.entries()) combinedVfs.set(filepath, content);
|
|
344
|
-
if (result.externalPackages.length > 0) allExternalPackages.push(...result.externalPackages);
|
|
345
|
-
apiConfigs.push(result.config);
|
|
346
|
-
for (const payload of result.vfsPayloads) {
|
|
347
|
-
yield* emit(PluginEvent.VfsGenerated({
|
|
348
|
-
ctx: {
|
|
349
|
-
buildId: "",
|
|
350
|
-
packageName: api.packageName,
|
|
351
|
-
...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
|
|
352
|
-
},
|
|
353
|
-
level: "debug",
|
|
354
|
-
file: payload.file,
|
|
355
|
-
declCount: payload.declCount,
|
|
356
|
-
contentHash: payload.contentHash,
|
|
357
|
-
...wantTrace && payload.content ? { content: payload.content } : {}
|
|
358
|
-
}));
|
|
359
|
-
if (payload.hasImports) yield* emit(PluginEvent.ImportsPrepended({
|
|
360
|
-
ctx: {
|
|
361
|
-
buildId: "",
|
|
362
|
-
packageName: api.packageName,
|
|
363
|
-
...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
|
|
364
|
-
},
|
|
365
|
-
level: "debug",
|
|
366
|
-
file: payload.file,
|
|
367
|
-
imports: wantTrace ? payload.importRefs : []
|
|
368
|
-
}));
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
} else if (options.apis) {
|
|
373
|
-
const apisWithTsconfig = options.apis.filter((a) => a.tsconfig);
|
|
374
|
-
if (apisWithTsconfig.length > 0) firstApiTsconfig = apisWithTsconfig[0].tsconfig;
|
|
375
|
-
const apisWithCompilerOptions = options.apis.filter((a) => a.compilerOptions);
|
|
376
|
-
if (apisWithCompilerOptions.length > 0) firstApiCompilerOptions = apisWithCompilerOptions[0].compilerOptions;
|
|
377
|
-
for (const a of options.apis) {
|
|
378
|
-
const scopeRoute = yield* pathService.normalizeBaseRoute(a.baseRoute ?? `/${unscopedName(a.packageName)}`);
|
|
379
|
-
scopeTsConfigs.set(apiScopeOf(scopeRoute, a.packageName), rawTsConfig(a));
|
|
380
|
-
}
|
|
381
|
-
const multiResults = yield* Effect.forEach(options.apis, (api) => Effect.gen(function* () {
|
|
382
|
-
const apiBaseRoute = yield* pathService.normalizeBaseRoute(api.baseRoute ?? `/${unscopedName(api.packageName)}`);
|
|
383
|
-
const dp = (yield* pathService.derivePaths({
|
|
384
|
-
mode: "multi",
|
|
385
|
-
docsRoot: rspressRoot,
|
|
386
|
-
baseRoute: apiBaseRoute,
|
|
387
|
-
apiFolder: api.apiFolder ?? "api",
|
|
388
|
-
locales: rspressLocales,
|
|
389
|
-
defaultLang: rspressLang,
|
|
390
|
-
versions: [],
|
|
391
|
-
defaultVersion: void 0
|
|
392
|
-
}))[0];
|
|
393
|
-
if (!dp) return [];
|
|
394
|
-
const result = yield* processSimpleApi(api, api.model, dp.outputDir, dp.routeBase, wantTrace);
|
|
395
|
-
for (const payload of result.vfsPayloads) {
|
|
396
|
-
yield* emit(PluginEvent.VfsGenerated({
|
|
397
|
-
ctx: {
|
|
398
|
-
buildId: "",
|
|
399
|
-
packageName: api.packageName,
|
|
400
|
-
...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
|
|
401
|
-
},
|
|
402
|
-
level: "debug",
|
|
403
|
-
file: payload.file,
|
|
404
|
-
declCount: payload.declCount,
|
|
405
|
-
contentHash: payload.contentHash,
|
|
406
|
-
...wantTrace && payload.content ? { content: payload.content } : {}
|
|
407
|
-
}));
|
|
408
|
-
if (payload.hasImports) yield* emit(PluginEvent.ImportsPrepended({
|
|
409
|
-
ctx: {
|
|
410
|
-
buildId: "",
|
|
411
|
-
packageName: api.packageName,
|
|
412
|
-
...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
|
|
413
|
-
},
|
|
414
|
-
level: "debug",
|
|
415
|
-
file: payload.file,
|
|
416
|
-
imports: wantTrace ? payload.importRefs : []
|
|
417
|
-
}));
|
|
418
|
-
}
|
|
419
|
-
return [result];
|
|
420
|
-
}), { concurrency: "unbounded" });
|
|
421
|
-
for (const results of multiResults) for (const result of results) {
|
|
422
|
-
for (const [filepath, content] of result.vfs.entries()) combinedVfs.set(filepath, content);
|
|
423
|
-
if (result.externalPackages.length > 0) allExternalPackages.push(...result.externalPackages);
|
|
424
|
-
apiConfigs.push(result.config);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}), resolvedThresholds ?? DEFAULT_THRESHOLDS);
|
|
428
|
-
const loadMs = performance.now() - loadStart;
|
|
429
|
-
yield* emit(PluginEvent.ModelLoaded({
|
|
430
|
-
ctx: { buildId: "" },
|
|
431
|
-
level: "debug",
|
|
432
|
-
entryPoints: apiConfigs.length,
|
|
433
|
-
itemCount: apiConfigs.reduce((sum, cfg) => sum + cfg.apiPackage.entryPoints.reduce((s, ep) => s + ep.members.length, 0), 0),
|
|
434
|
-
durationMs: Math.round(loadMs)
|
|
435
|
-
}));
|
|
436
|
-
const projectRoot = process.cwd();
|
|
437
|
-
let globalTsConfig;
|
|
438
|
-
if (firstApiTsconfig || firstApiCompilerOptions) {
|
|
439
|
-
globalTsConfig = {};
|
|
440
|
-
if (firstApiTsconfig != null) globalTsConfig.tsconfig = firstApiTsconfig;
|
|
441
|
-
if (firstApiCompilerOptions != null) globalTsConfig.compilerOptions = firstApiCompilerOptions;
|
|
442
|
-
}
|
|
443
|
-
const resolvedCompilerOptions = yield* Effect.promise(() => resolveTypeScriptConfig(projectRoot, globalTsConfig));
|
|
444
|
-
yield* emit(PluginEvent.TsCacheCreated({
|
|
445
|
-
ctx: { buildId: "" },
|
|
446
|
-
level: "debug",
|
|
447
|
-
compilerOptions: `target=${resolvedCompilerOptions.target}, module=${resolvedCompilerOptions.module}, lib=[${resolvedCompilerOptions.lib?.join(", ") ?? ""}]`,
|
|
448
|
-
durationMs: 0
|
|
449
|
-
}));
|
|
450
|
-
const documentedPackageNames = new Set(apiConfigs.map((config) => config.packageName));
|
|
451
|
-
const externalPackagesToLoad = allExternalPackages.filter((pkg) => !documentedPackageNames.has(pkg.name));
|
|
452
|
-
const typeLoadResult = yield* Effect.result(Effect.gen(function* () {
|
|
453
|
-
if (externalPackagesToLoad.length > 0) {
|
|
454
|
-
const resolvedPackages = yield* typeRegistry.resolveVersions(externalPackagesToLoad);
|
|
455
|
-
const droppedCount = externalPackagesToLoad.length - resolvedPackages.length;
|
|
456
|
-
if (droppedCount > 0) yield* emit(PluginEvent.ExternalPackageSkipped({
|
|
457
|
-
ctx: { buildId: "" },
|
|
458
|
-
level: "debug",
|
|
459
|
-
reason: `${droppedCount} unresolvable package(s) (unpublished or workspace-only)`
|
|
460
|
-
}));
|
|
461
|
-
if (resolvedPackages.length > 0) {
|
|
462
|
-
const result = yield* typeRegistry.loadPackages(resolvedPackages);
|
|
463
|
-
for (const [filePath, content] of result.vfs.entries()) combinedVfs.set(filePath, content);
|
|
464
|
-
yield* emit(PluginEvent.VfsMerged({
|
|
465
|
-
ctx: { buildId: "" },
|
|
466
|
-
level: "debug",
|
|
467
|
-
totalFiles: result.vfs.size,
|
|
468
|
-
packages: resolvedPackages.map((p) => p.name)
|
|
469
|
-
}));
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}));
|
|
473
|
-
if (typeLoadResult._tag === "Failure") yield* emit(PluginEvent.ConfigCascadeWarning({
|
|
474
|
-
ctx: { buildId: "" },
|
|
475
|
-
level: "warn",
|
|
476
|
-
field: "externalTypes",
|
|
477
|
-
chosen: "empty VFS",
|
|
478
|
-
ignored: [typeLoadResult.failure.message ?? String(typeLoadResult.failure)]
|
|
479
|
-
}));
|
|
480
|
-
const twoslashEnv = twoslashEnvHash(combinedVfs, `typescript@${ts.version}`);
|
|
481
|
-
const restored = yield* (yield* TwoslashCacheService).load(twoslashEnv);
|
|
482
|
-
const twoslashCache = makeTwoslashCache(restored);
|
|
483
|
-
yield* emit(PluginEvent.TwoslashCacheLoaded({
|
|
484
|
-
ctx: { buildId: "" },
|
|
485
|
-
level: "debug",
|
|
486
|
-
envHash: twoslashEnv,
|
|
487
|
-
entries: restored.size
|
|
488
|
-
}));
|
|
489
|
-
const twoslashStartMs = performance.now();
|
|
490
|
-
const manager = TwoslashManager.getInstance();
|
|
491
|
-
manager.initialize(combinedVfs, void 0, void 0, void 0, resolvedCompilerOptions, twoslashCache);
|
|
492
|
-
const resolvedByRawConfig = /* @__PURE__ */ new Map();
|
|
493
|
-
for (const [apiScope, rawConfig] of scopeTsConfigs) {
|
|
494
|
-
if (rawConfig === void 0) {
|
|
495
|
-
manager.registerScope(apiScope, resolvedCompilerOptions);
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
const rawKey = JSON.stringify([String(rawConfig.tsconfig ?? ""), rawConfig.compilerOptions ?? null]);
|
|
499
|
-
let scopeOptions = resolvedByRawConfig.get(rawKey);
|
|
500
|
-
if (scopeOptions === void 0) {
|
|
501
|
-
scopeOptions = yield* Effect.promise(() => resolveTypeScriptConfig(projectRoot, rawConfig));
|
|
502
|
-
resolvedByRawConfig.set(rawKey, scopeOptions);
|
|
503
|
-
}
|
|
504
|
-
manager.initialize(combinedVfs, void 0, void 0, void 0, scopeOptions, twoslashCache);
|
|
505
|
-
manager.registerScope(apiScope, scopeOptions);
|
|
506
|
-
}
|
|
507
|
-
yield* emit(PluginEvent.TwoslashInitialized({
|
|
508
|
-
ctx: { buildId: "" },
|
|
509
|
-
level: "debug",
|
|
510
|
-
durationMs: Math.round(performance.now() - twoslashStartMs),
|
|
511
|
-
vfsFileCount: combinedVfs.size
|
|
512
|
-
}));
|
|
513
|
-
const shikiStartMs = performance.now();
|
|
514
|
-
const themeSet = /* @__PURE__ */ new Set();
|
|
515
|
-
const customThemes = [];
|
|
516
|
-
for (const config of apiConfigs) {
|
|
517
|
-
const theme = config.theme ?? {
|
|
518
|
-
light: DEFAULT_SHIKI_THEMES.light,
|
|
519
|
-
dark: DEFAULT_SHIKI_THEMES.dark
|
|
520
|
-
};
|
|
521
|
-
if (typeof theme.light === "string") themeSet.add(theme.light);
|
|
522
|
-
else if (typeof theme.light === "object") customThemes.push(theme.light);
|
|
523
|
-
if (typeof theme.dark === "string") themeSet.add(theme.dark);
|
|
524
|
-
else if (typeof theme.dark === "object") customThemes.push(theme.dark);
|
|
525
|
-
}
|
|
526
|
-
if (typeof DEFAULT_SHIKI_THEMES.light === "string") themeSet.add(DEFAULT_SHIKI_THEMES.light);
|
|
527
|
-
if (typeof DEFAULT_SHIKI_THEMES.dark === "string") themeSet.add(DEFAULT_SHIKI_THEMES.dark);
|
|
528
|
-
const themes = [...themeSet, ...customThemes];
|
|
529
|
-
const langs = [
|
|
530
|
-
"typescript",
|
|
531
|
-
"javascript",
|
|
532
|
-
"json",
|
|
533
|
-
"bash",
|
|
534
|
-
"sh"
|
|
535
|
-
];
|
|
536
|
-
const highlighter = yield* Effect.promise(() => createHighlighter({
|
|
537
|
-
themes,
|
|
538
|
-
langs
|
|
539
|
-
}));
|
|
540
|
-
yield* emit(PluginEvent.PhaseCompleted({
|
|
541
|
-
ctx: { buildId: "" },
|
|
542
|
-
level: "debug",
|
|
543
|
-
phase: "shikiInit",
|
|
544
|
-
durationMs: Math.round(performance.now() - shikiStartMs)
|
|
545
|
-
}));
|
|
546
|
-
const ogResolver = options.siteUrl ? new OpenGraphResolver({
|
|
547
|
-
siteUrl: options.siteUrl,
|
|
548
|
-
...docsRoot != null ? { docsRoot } : {}
|
|
549
|
-
}) : null;
|
|
550
|
-
const hideCutTransformer = MemberFormatTransformer;
|
|
551
|
-
const hideCutLinesTransformer = HideCutLinesTransformer;
|
|
552
|
-
const twoslashTransformer = TwoslashManager.getInstance().getTransformer() ?? void 0;
|
|
553
|
-
const logLevel = options.logLevel ?? "info";
|
|
554
|
-
const suppressExampleErrors = options.errors?.example !== "show";
|
|
555
|
-
return {
|
|
556
|
-
apiConfigs,
|
|
557
|
-
combinedVfs,
|
|
558
|
-
highlighter,
|
|
559
|
-
resolvedCompilerOptions,
|
|
560
|
-
ogResolver,
|
|
561
|
-
shikiCrossLinker,
|
|
562
|
-
hideCutTransformer,
|
|
563
|
-
hideCutLinesTransformer,
|
|
564
|
-
twoslashTransformer,
|
|
565
|
-
twoslashCache,
|
|
566
|
-
twoslashEnvHash: twoslashEnv,
|
|
567
|
-
pageConcurrency: os.cpus().length,
|
|
568
|
-
logLevel: logLevel === "none" ? "info" : logLevel,
|
|
569
|
-
suppressExampleErrors,
|
|
570
|
-
thresholds: resolvedThresholds ?? DEFAULT_THRESHOLDS,
|
|
571
|
-
buildId
|
|
572
|
-
};
|
|
573
|
-
}) };
|
|
574
|
-
}));
|
|
575
|
-
}
|
|
576
|
-
/**
|
|
577
|
-
* Derive the API scope key from a base route, matching the derivation in
|
|
578
|
-
* `build-program.ts` so config resolution and the remark plugins agree on the
|
|
579
|
-
* name a code block is attributed to.
|
|
580
|
-
*/
|
|
581
|
-
function apiScopeOf(baseRoute, packageName) {
|
|
582
|
-
return baseRoute.replace(/^\//, "").split("/")[0] || packageName;
|
|
583
|
-
}
|
|
584
|
-
/** The raw TypeScript config an API declares, or undefined when it declares none. */
|
|
585
|
-
function rawTsConfig(api) {
|
|
586
|
-
if (api.tsconfig == null && api.compilerOptions == null) return void 0;
|
|
587
|
-
const cfg = {};
|
|
588
|
-
if (api.tsconfig != null) cfg.tsconfig = api.tsconfig;
|
|
589
|
-
if (api.compilerOptions != null) cfg.compilerOptions = api.compilerOptions;
|
|
590
|
-
return cfg;
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Strip npm scope from a package name.
|
|
594
|
-
*/
|
|
595
|
-
function unscopedName(packageName) {
|
|
596
|
-
return packageName.startsWith("@") ? packageName.split("/")[1] ?? packageName : packageName;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
//#endregion
|
|
600
|
-
export { ConfigServiceLive };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { deriveOutputPaths, normalizeBaseRoute } from "../path-derivation.js";
|
|
2
|
-
import { PathDerivationService } from "../services/PathDerivationService.js";
|
|
3
|
-
import { Effect, Layer } from "effect";
|
|
4
|
-
|
|
5
|
-
//#region src/layers/PathDerivationServiceLive.ts
|
|
6
|
-
const PathDerivationServiceLive = Layer.succeed(PathDerivationService, {
|
|
7
|
-
derivePaths: (input) => Effect.succeed(deriveOutputPaths({
|
|
8
|
-
...input,
|
|
9
|
-
locales: [...input.locales],
|
|
10
|
-
versions: [...input.versions]
|
|
11
|
-
})),
|
|
12
|
-
normalizeBaseRoute: (route) => Effect.succeed(normalizeBaseRoute(route))
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
//#endregion
|
|
16
|
-
export { PathDerivationServiceLive };
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { TwoslashCacheService } from "../services/TwoslashCacheService.js";
|
|
2
|
-
import { decodeTwoslashCache, encodeTwoslashCache, twoslashBlobKey } from "../twoslash-cache.js";
|
|
3
|
-
import { NodeFileSystem } from "@effect/platform-node";
|
|
4
|
-
import { Effect, Layer, Option, Path } from "effect";
|
|
5
|
-
import { Cache } from "@effected/store";
|
|
6
|
-
import { AppDirs, Xdg } from "@effected/xdg";
|
|
7
|
-
|
|
8
|
-
//#region src/layers/TwoslashCacheServiceLive.ts
|
|
9
|
-
const PlatformLive = Layer.mergeAll(NodeFileSystem.layer, Path.layer);
|
|
10
|
-
/**
|
|
11
|
-
* XDG app dirs under the same `tsdoctor` namespace the type registry uses, so
|
|
12
|
-
* every derived-artifact cache this plugin keeps lives in one place.
|
|
13
|
-
*/
|
|
14
|
-
const AppDirsLive = AppDirs.layer({ namespace: "tsdoctor" }).pipe(Layer.provide(Layer.mergeAll(Xdg.layer, PlatformLive)));
|
|
15
|
-
/**
|
|
16
|
-
* A sqlite-backed `@effected/store` Cache in the XDG cache dir, separate from
|
|
17
|
-
* the registry's `metadata.sqlite`.
|
|
18
|
-
*
|
|
19
|
-
* XDG rather than the repo: these are regenerable results derived from content
|
|
20
|
-
* hashes, so they belong with the user's other caches — shared across worktrees
|
|
21
|
-
* and checkouts of the same project, and untouched by cleaning `dist/`. Nothing
|
|
22
|
-
* here needs to be committed for a build to be correct.
|
|
23
|
-
*/
|
|
24
|
-
const CacheLive = Layer.unwrap(Effect.gen(function* () {
|
|
25
|
-
const appDirs = yield* AppDirs;
|
|
26
|
-
const path = yield* Path.Path;
|
|
27
|
-
const cacheDir = yield* appDirs.ensureCache;
|
|
28
|
-
return Cache.layerSqlite({ filename: path.join(cacheDir, "twoslash.sqlite") });
|
|
29
|
-
})).pipe(Layer.provide(Layer.mergeAll(AppDirsLive, PlatformLive)));
|
|
30
|
-
/**
|
|
31
|
-
* Live Twoslash cache persistence.
|
|
32
|
-
*
|
|
33
|
-
* Both operations swallow every failure by design — see the service docs. A
|
|
34
|
-
* missing HOME, an unwritable cache dir or a corrupt database degrades the
|
|
35
|
-
* build to "type-check everything", which is exactly the behaviour before this
|
|
36
|
-
* cache existed.
|
|
37
|
-
*/
|
|
38
|
-
const TwoslashCacheServiceLive = Layer.succeed(TwoslashCacheService, {
|
|
39
|
-
load: (envHash) => Effect.gen(function* () {
|
|
40
|
-
const entry = yield* (yield* Cache).get(twoslashBlobKey(envHash));
|
|
41
|
-
return Option.isSome(entry) ? decodeTwoslashCache(entry.value.value) : /* @__PURE__ */ new Map();
|
|
42
|
-
}).pipe(Effect.provide(CacheLive), Effect.catchCause(() => Effect.succeed(/* @__PURE__ */ new Map()))),
|
|
43
|
-
save: (envHash, entries) => Effect.gen(function* () {
|
|
44
|
-
yield* (yield* Cache).set({
|
|
45
|
-
key: twoslashBlobKey(envHash),
|
|
46
|
-
value: encodeTwoslashCache(entries),
|
|
47
|
-
tags: ["twoslash"]
|
|
48
|
-
});
|
|
49
|
-
}).pipe(Effect.provide(CacheLive), Effect.catchCause(() => Effect.void))
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
//#endregion
|
|
53
|
-
export { TwoslashCacheServiceLive };
|