rspress-plugin-api-extractor 0.10.0 → 0.11.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.
@@ -1,60 +1,32 @@
1
+ import { BuildId } from "../BuildEnv.js";
1
2
  import { PluginEvent } from "../observability/events.js";
2
3
  import { emit, wantsLevel } from "../observability/EventBus.js";
3
4
  import { BuildMetrics } from "./build-metrics.js";
4
5
  import "./ObservabilityLive.js";
5
6
  import { TypeReferenceExtractor } from "../type-reference-extractor.js";
6
- import { OpenGraphResolver } from "../og-resolver.js";
7
7
  import { withPhase } from "../observability/spans.js";
8
+ import { TwoslashEnvironments } from "../services/TwoslashEnvironments.js";
8
9
  import { resolveTypeScriptConfig } from "../typescript-config.js";
9
- import { TwoslashManager } from "../twoslash-transformer.js";
10
+ import { apiScopeOf, deriveOutputPaths, normalizeBaseRoute, unscopedName } from "../path-derivation.js";
10
11
  import { classifyApiConfig, extractAutoDetectedPackages, isVersionConfig, mergeLlmsPluginConfig, validateExternalPackages } from "../config-utils.js";
11
12
  import { ApiExtractedPackage } from "../api-extracted-package.js";
12
13
  import { CategoryResolver } from "../category-resolver.js";
13
14
  import { ConfigValidationError } from "../errors.js";
14
- import { HideCutLinesTransformer, MemberFormatTransformer } from "../hide-cut-transformer.js";
15
- import { DEFAULT_SHIKI_THEMES } from "../markdown/shiki-utils.js";
15
+ import { normalizeThemeConfig } from "../markdown/shiki-utils.js";
16
16
  import { loadApiModel, loadPackageJson, loadVersionModel } from "../model-loader.js";
17
17
  import { DEFAULT_CATEGORIES } from "../schemas/config.js";
18
18
  import "../schemas/index.js";
19
19
  import { ConfigService } from "../services/ConfigService.js";
20
- import { PathDerivationService } from "../services/PathDerivationService.js";
20
+ import { PluginConfig } from "../services/PluginConfig.js";
21
21
  import { TwoslashCacheService } from "../services/TwoslashCacheService.js";
22
22
  import { TypeRegistryService } from "../services/TypeRegistryService.js";
23
- import { makeTwoslashCache, twoslashEnvHash } from "../twoslash-cache.js";
23
+ import { twoslashEnvHash } from "../twoslash-cache.js";
24
24
  import path from "node:path";
25
25
  import { hashContent } from "@tsdoctor/snapshot";
26
26
  import { Effect, Layer, Metric } from "effect";
27
27
  import ts from "typescript";
28
- import os from "node:os";
29
- import { createHighlighter } from "shiki";
30
28
 
31
29
  //#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
30
  /**
59
31
  * Prepend import statements for external type references to the VFS declaration files.
60
32
  * Returns per-entry payloads for event emission (heavy content/importRefs gated on wantTrace).
@@ -132,7 +104,7 @@ function validateOptions(options, rspressConfig) {
132
104
  });
133
105
  } else {
134
106
  if (api.versions) yield* emit(PluginEvent.ConfigCascadeWarning({
135
- ctx: { buildId: "" },
107
+ ctx: {},
136
108
  level: "warn",
137
109
  field: "versions",
138
110
  chosen: "(none — multiVersion not configured)",
@@ -147,255 +119,171 @@ function validateOptions(options, rspressConfig) {
147
119
  });
148
120
  }
149
121
  /**
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.
122
+ * Resolve plugin options + RSPress config into the API configs the pipeline
123
+ * runs over.
124
+ *
125
+ * @remarks
126
+ * A module-level `const`, not a factory. It took the plugin options as an
127
+ * argument and was called inline at the merge site, which made it a
128
+ * layer-returning function: layers memoize by reference, so a second call
129
+ * would build a second `ConfigService` with its own captured `TypeRegistry`.
130
+ * The options come from {@link PluginConfig} now, so there is nothing to pass
131
+ * and nothing to call twice.
153
132
  */
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
- });
133
+ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
134
+ const typeRegistry = yield* TypeRegistryService;
135
+ const options = yield* PluginConfig;
136
+ return { resolve: (rspressConfig) => Effect.gen(function* () {
137
+ const buildId = yield* BuildId;
138
+ const loadStart = performance.now();
139
+ const wantTrace = yield* wantsLevel("trace");
140
+ yield* validateOptions(options, { ...rspressConfig.multiVersion ? { multiVersion: {
141
+ default: rspressConfig.multiVersion.default,
142
+ versions: [...rspressConfig.multiVersion.versions]
143
+ } } : {} });
144
+ const rspressMultiVersion = rspressConfig.multiVersion;
145
+ const rspressLocales = rspressConfig.locales?.map((l) => l.lang) ?? [];
146
+ const rspressLang = rspressConfig.lang;
147
+ const docsRoot = rspressConfig.root;
148
+ const rspressRoot = docsRoot || process.cwd();
149
+ const categoryResolver = new CategoryResolver();
150
+ const pluginDefaults = categoryResolver.mergeCategories(DEFAULT_CATEGORIES, options.defaultCategories);
151
+ const apiConfigs = [];
152
+ const combinedVfs = /* @__PURE__ */ new Map();
153
+ const allExternalPackages = [];
154
+ let firstApiTsconfig;
155
+ let firstApiCompilerOptions;
156
+ /**
157
+ * Raw TypeScript config per API scope. Each documented package is
158
+ * type-checked under its OWN configuration; the build no longer picks
159
+ * one and applies it to everything.
160
+ */
161
+ const scopeTsConfigs = /* @__PURE__ */ new Map();
162
+ /**
163
+ * Emit a typed ModelLoadFailed event for a failed model load, then
164
+ * convert the typed failure to a defect — a missing or unparsable
165
+ * model remains fatal to the build, exactly as before, but the
166
+ * event now rides the error channel instead of a sync-island seam.
167
+ */
168
+ const withModelLoadEvents = (self) => self.pipe(Effect.tapError((error) => emit(PluginEvent.ModelLoadFailed({
169
+ ctx: { buildId },
170
+ level: "error",
171
+ modelPath: "modelPath" in error ? error.modelPath : "<loader function>",
172
+ reason: error.message
173
+ }))), Effect.orDie);
174
+ /**
175
+ * Helper to process a single API model (shared by single and multi modes).
176
+ */
177
+ const processSimpleApi = (api, model, outputDir, fullRoute, wantTrace) => Effect.gen(function* () {
178
+ const { apiPackage, source: loaderSource } = yield* withModelLoadEvents(loadApiModel(model));
179
+ return yield* Effect.promise(async () => {
180
+ const resolvedCategories = categoryResolver.resolveCategoryConfig(pluginDefaults, api.categories);
181
+ const resolvedSource = categoryResolver.resolveSourceConfig(api.source, loaderSource);
182
+ const resolvedLlms = mergeLlmsPluginConfig(options.llmsPlugin, api.llmsPlugin);
183
+ const packageJson = api.packageJson ? await loadPackageJson(api.packageJson) : void 0;
184
+ validateExternalPackages(api.externalPackages, packageJson);
185
+ const externalPackages = api.externalPackages || extractAutoDetectedPackages(packageJson, api.autoDetectDependencies);
186
+ if (externalPackages && externalPackages.length > 0) Effect.runSync(Metric.update(BuildMetrics.externalPackagesTotal, externalPackages.length));
187
+ const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).generateVfs();
188
+ const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
189
+ const resolvedOgImage = api.ogImage ?? options.ogImage;
190
+ const resolvedTheme = normalizeThemeConfig(api.theme);
191
+ return {
192
+ vfs,
193
+ vfsPayloads,
194
+ externalPackages: externalPackages || [],
195
+ config: {
196
+ apiPackage,
197
+ packageName: api.packageName,
198
+ ...api.name != null ? { apiName: api.name } : {},
199
+ outputDir,
200
+ baseRoute: fullRoute,
201
+ categories: resolvedCategories,
202
+ ...resolvedSource != null ? { source: resolvedSource } : {},
203
+ ...packageJson != null ? { packageJson } : {},
204
+ ...resolvedLlms != null ? { llmsPlugin: resolvedLlms } : {},
205
+ ...options.siteUrl != null ? { siteUrl: options.siteUrl } : {},
206
+ ...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
207
+ docsDir: path.dirname(outputDir),
208
+ ...docsRoot != null ? { docsRoot } : {},
209
+ ...resolvedTheme != null ? { theme: resolvedTheme } : {}
210
+ }
211
+ };
234
212
  });
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({
213
+ });
214
+ yield* withPhase("modelLoad", { buildId }, Effect.gen(function* () {
215
+ if (options.api) {
216
+ const api = options.api;
217
+ const baseRoute = normalizeBaseRoute(api.baseRoute ?? "/");
218
+ firstApiTsconfig = api.tsconfig;
219
+ firstApiCompilerOptions = api.compilerOptions;
220
+ scopeTsConfigs.set(apiScopeOf(baseRoute, api.packageName), rawTsConfig(api));
221
+ if (rspressMultiVersion && api.versions) {
222
+ const versionResults = yield* Effect.forEach(Object.entries(api.versions), ([version, versionValue]) => Effect.gen(function* () {
223
+ const versionDp = deriveOutputPaths({
332
224
  mode: "single",
333
225
  docsRoot: rspressRoot,
334
226
  baseRoute,
335
227
  apiFolder: api.apiFolder ?? "api",
336
228
  locales: rspressLocales,
337
229
  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);
230
+ versions: [version],
231
+ defaultVersion: rspressMultiVersion?.default
232
+ })[0];
233
+ if (!versionDp) return {
234
+ vfs: /* @__PURE__ */ new Map(),
235
+ vfsPayloads: [],
236
+ externalPackages: [],
237
+ config: null
238
+ };
239
+ const versionConfig = isVersionConfig(versionValue) ? versionValue : { model: versionValue };
240
+ const { apiPackage, packageJson: versionPackageJson, categories: versionCategories, source: versionSource, externalPackages: versionExternalPackages, autoDetectDependencies: versionAutoDetectDependencies, llmsPlugin: versionLlms, ogImage: versionOgImage } = yield* withModelLoadEvents(loadVersionModel(versionConfig));
241
+ return yield* Effect.promise(async () => {
242
+ Effect.runSync(Metric.update(BuildMetrics.apiVersionsLoaded, 1));
243
+ const resolvedCategories = categoryResolver.resolveCategoryConfig(pluginDefaults, api.categories, versionCategories);
244
+ const resolvedSource = categoryResolver.resolveSourceConfig(api.source, versionSource);
245
+ const resolvedLlms = mergeLlmsPluginConfig(options.llmsPlugin, api.llmsPlugin, versionLlms);
246
+ const packageJson = versionPackageJson || (api.packageJson ? await loadPackageJson(api.packageJson) : void 0);
247
+ validateExternalPackages(versionExternalPackages || api.externalPackages, packageJson);
248
+ const autoDetectOptions = versionAutoDetectDependencies || api.autoDetectDependencies;
249
+ const externalPackages = versionExternalPackages || api.externalPackages || extractAutoDetectedPackages(packageJson, autoDetectOptions);
250
+ if (externalPackages && externalPackages.length > 0) Effect.runSync(Metric.update(BuildMetrics.externalPackagesTotal, externalPackages.length));
251
+ const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).generateVfs();
252
+ const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
253
+ const resolvedOgImage = versionOgImage ?? api.ogImage ?? options.ogImage;
254
+ const resolvedTheme = normalizeThemeConfig(api.theme);
255
+ const outputDir = versionDp.outputDir;
256
+ const fullRoute = versionDp.routeBase;
257
+ return {
258
+ vfs,
259
+ vfsPayloads,
260
+ externalPackages: externalPackages || [],
261
+ config: {
262
+ apiPackage,
263
+ packageName: `${api.packageName} (${version})`,
264
+ ...api.name != null ? { apiName: api.name } : {},
265
+ outputDir,
266
+ baseRoute: fullRoute,
267
+ categories: resolvedCategories,
268
+ ...resolvedSource != null ? { source: resolvedSource } : {},
269
+ ...packageJson != null ? { packageJson } : {},
270
+ ...resolvedLlms != null ? { llmsPlugin: resolvedLlms } : {},
271
+ ...options.siteUrl != null ? { siteUrl: options.siteUrl } : {},
272
+ ...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
273
+ docsDir: path.dirname(outputDir),
274
+ ...docsRoot != null ? { docsRoot } : {},
275
+ ...resolvedTheme != null ? { theme: resolvedTheme } : {}
276
+ }
277
+ };
278
+ });
279
+ }), { concurrency: "unbounded" });
280
+ for (const result of versionResults) {
281
+ for (const [filepath, content] of result.vfs.entries()) combinedVfs.set(filepath, content);
282
+ if (result.externalPackages.length > 0) allExternalPackages.push(...result.externalPackages);
283
+ if (result.config) apiConfigs.push(result.config);
395
284
  for (const payload of result.vfsPayloads) {
396
285
  yield* emit(PluginEvent.VfsGenerated({
397
286
  ctx: {
398
- buildId: "",
399
287
  packageName: api.packageName,
400
288
  ...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
401
289
  },
@@ -407,7 +295,6 @@ function ConfigServiceLive(options, shikiCrossLinker, buildId = "", resolvedThre
407
295
  }));
408
296
  if (payload.hasImports) yield* emit(PluginEvent.ImportsPrepended({
409
297
  ctx: {
410
- buildId: "",
411
298
  packageName: api.packageName,
412
299
  ...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
413
300
  },
@@ -416,171 +303,196 @@ function ConfigServiceLive(options, shikiCrossLinker, buildId = "", resolvedThre
416
303
  imports: wantTrace ? payload.importRefs : []
417
304
  }));
418
305
  }
419
- return [result];
420
- }), { concurrency: "unbounded" });
421
- for (const results of multiResults) for (const result of results) {
306
+ }
307
+ } else if (api.model) {
308
+ const dp = deriveOutputPaths({
309
+ mode: "single",
310
+ docsRoot: rspressRoot,
311
+ baseRoute,
312
+ apiFolder: api.apiFolder ?? "api",
313
+ locales: rspressLocales,
314
+ defaultLang: rspressLang,
315
+ versions: [],
316
+ defaultVersion: void 0
317
+ })[0];
318
+ if (dp) {
319
+ const result = yield* processSimpleApi(api, api.model, dp.outputDir, dp.routeBase, wantTrace);
422
320
  for (const [filepath, content] of result.vfs.entries()) combinedVfs.set(filepath, content);
423
321
  if (result.externalPackages.length > 0) allExternalPackages.push(...result.externalPackages);
424
322
  apiConfigs.push(result.config);
323
+ for (const payload of result.vfsPayloads) {
324
+ yield* emit(PluginEvent.VfsGenerated({
325
+ ctx: {
326
+ packageName: api.packageName,
327
+ ...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
328
+ },
329
+ level: "debug",
330
+ file: payload.file,
331
+ declCount: payload.declCount,
332
+ contentHash: payload.contentHash,
333
+ ...wantTrace && payload.content ? { content: payload.content } : {}
334
+ }));
335
+ if (payload.hasImports) yield* emit(PluginEvent.ImportsPrepended({
336
+ ctx: {
337
+ packageName: api.packageName,
338
+ ...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
339
+ },
340
+ level: "debug",
341
+ file: payload.file,
342
+ imports: wantTrace ? payload.importRefs : []
343
+ }));
344
+ }
425
345
  }
426
346
  }
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: "" },
347
+ } else if (options.apis) {
348
+ const apisWithTsconfig = options.apis.filter((a) => a.tsconfig);
349
+ if (apisWithTsconfig.length > 0) firstApiTsconfig = apisWithTsconfig[0].tsconfig;
350
+ const apisWithCompilerOptions = options.apis.filter((a) => a.compilerOptions);
351
+ if (apisWithCompilerOptions.length > 0) firstApiCompilerOptions = apisWithCompilerOptions[0].compilerOptions;
352
+ for (const a of options.apis) {
353
+ const scopeRoute = normalizeBaseRoute(a.baseRoute ?? `/${unscopedName(a.packageName)}`);
354
+ scopeTsConfigs.set(apiScopeOf(scopeRoute, a.packageName), rawTsConfig(a));
355
+ }
356
+ const multiResults = yield* Effect.forEach(options.apis, (api) => Effect.gen(function* () {
357
+ const apiBaseRoute = normalizeBaseRoute(api.baseRoute ?? `/${unscopedName(api.packageName)}`);
358
+ const dp = deriveOutputPaths({
359
+ mode: "multi",
360
+ docsRoot: rspressRoot,
361
+ baseRoute: apiBaseRoute,
362
+ apiFolder: api.apiFolder ?? "api",
363
+ locales: rspressLocales,
364
+ defaultLang: rspressLang,
365
+ versions: [],
366
+ defaultVersion: void 0
367
+ })[0];
368
+ if (!dp) return [];
369
+ const result = yield* processSimpleApi(api, api.model, dp.outputDir, dp.routeBase, wantTrace);
370
+ for (const payload of result.vfsPayloads) {
371
+ yield* emit(PluginEvent.VfsGenerated({
372
+ ctx: {
373
+ packageName: api.packageName,
374
+ ...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
375
+ },
376
+ level: "debug",
377
+ file: payload.file,
378
+ declCount: payload.declCount,
379
+ contentHash: payload.contentHash,
380
+ ...wantTrace && payload.content ? { content: payload.content } : {}
381
+ }));
382
+ if (payload.hasImports) yield* emit(PluginEvent.ImportsPrepended({
383
+ ctx: {
384
+ packageName: api.packageName,
385
+ ...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
386
+ },
466
387
  level: "debug",
467
- totalFiles: result.vfs.size,
468
- packages: resolvedPackages.map((p) => p.name)
388
+ file: payload.file,
389
+ imports: wantTrace ? payload.importRefs : []
469
390
  }));
470
391
  }
392
+ return [result];
393
+ }), { concurrency: "unbounded" });
394
+ for (const results of multiResults) for (const result of results) {
395
+ for (const [filepath, content] of result.vfs.entries()) combinedVfs.set(filepath, content);
396
+ if (result.externalPackages.length > 0) allExternalPackages.push(...result.externalPackages);
397
+ apiConfigs.push(result.config);
471
398
  }
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);
399
+ }
400
+ }));
401
+ const loadMs = performance.now() - loadStart;
402
+ yield* emit(PluginEvent.ModelLoaded({
403
+ ctx: {},
404
+ level: "debug",
405
+ entryPoints: apiConfigs.length,
406
+ itemCount: apiConfigs.reduce((sum, cfg) => sum + cfg.apiPackage.entryPoints.reduce((s, ep) => s + ep.members.length, 0), 0),
407
+ durationMs: Math.round(loadMs)
408
+ }));
409
+ const projectRoot = process.cwd();
410
+ let globalTsConfig;
411
+ if (firstApiTsconfig || firstApiCompilerOptions) {
412
+ globalTsConfig = {};
413
+ if (firstApiTsconfig != null) globalTsConfig.tsconfig = firstApiTsconfig;
414
+ if (firstApiCompilerOptions != null) globalTsConfig.compilerOptions = firstApiCompilerOptions;
415
+ }
416
+ const resolvedCompilerOptions = yield* Effect.promise(() => resolveTypeScriptConfig(projectRoot, globalTsConfig));
417
+ yield* emit(PluginEvent.TsCacheCreated({
418
+ ctx: {},
419
+ level: "debug",
420
+ compilerOptions: `target=${resolvedCompilerOptions.target}, module=${resolvedCompilerOptions.module}, lib=[${resolvedCompilerOptions.lib?.join(", ") ?? ""}]`,
421
+ durationMs: 0
422
+ }));
423
+ const documentedPackageNames = new Set(apiConfigs.map((config) => config.packageName));
424
+ const externalPackagesToLoad = allExternalPackages.filter((pkg) => !documentedPackageNames.has(pkg.name));
425
+ const typeLoadResult = yield* Effect.result(Effect.gen(function* () {
426
+ if (externalPackagesToLoad.length > 0) {
427
+ const resolvedPackages = yield* typeRegistry.resolveVersions(externalPackagesToLoad);
428
+ const droppedCount = externalPackagesToLoad.length - resolvedPackages.length;
429
+ if (droppedCount > 0) yield* emit(PluginEvent.ExternalPackageSkipped({
430
+ ctx: {},
431
+ level: "debug",
432
+ reason: `${droppedCount} unresolvable package(s) (unpublished or workspace-only)`
433
+ }));
434
+ if (resolvedPackages.length > 0) {
435
+ const result = yield* typeRegistry.loadPackages(resolvedPackages);
436
+ for (const [filePath, content] of result.vfs.entries()) combinedVfs.set(filePath, content);
437
+ yield* emit(PluginEvent.VfsMerged({
438
+ ctx: {},
439
+ level: "debug",
440
+ totalFiles: result.vfs.size,
441
+ packages: resolvedPackages.map((p) => p.name)
442
+ }));
503
443
  }
504
- manager.initialize(combinedVfs, void 0, void 0, void 0, scopeOptions, twoslashCache);
505
- manager.registerScope(apiScope, scopeOptions);
506
444
  }
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);
445
+ }));
446
+ if (typeLoadResult._tag === "Failure") yield* emit(PluginEvent.ConfigCascadeWarning({
447
+ ctx: {},
448
+ level: "warn",
449
+ field: "externalTypes",
450
+ chosen: "empty VFS",
451
+ ignored: [typeLoadResult.failure.message ?? String(typeLoadResult.failure)]
452
+ }));
453
+ const twoslashEnv = twoslashEnvHash(combinedVfs, `typescript@${ts.version}`);
454
+ const twoslashCache = yield* (yield* TwoslashCacheService).open(twoslashEnv);
455
+ yield* emit(PluginEvent.TwoslashCacheLoaded({
456
+ ctx: {},
457
+ level: "debug",
458
+ envHash: twoslashEnv,
459
+ entries: twoslashCache.entries().size
460
+ }));
461
+ const twoslashStartMs = performance.now();
462
+ const environments = yield* TwoslashEnvironments;
463
+ environments.registerEnvironment({
464
+ vfs: combinedVfs,
465
+ compilerOptions: resolvedCompilerOptions,
466
+ typesCache: twoslashCache
467
+ });
468
+ const resolvedByRawConfig = /* @__PURE__ */ new Map();
469
+ for (const [apiScope, rawConfig] of scopeTsConfigs) {
470
+ if (rawConfig === void 0) {
471
+ environments.registerScope(apiScope, resolvedCompilerOptions);
472
+ continue;
525
473
  }
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
- }
474
+ const rawKey = JSON.stringify([String(rawConfig.tsconfig ?? ""), rawConfig.compilerOptions ?? null]);
475
+ let scopeOptions = resolvedByRawConfig.get(rawKey);
476
+ if (scopeOptions === void 0) {
477
+ scopeOptions = yield* Effect.promise(() => resolveTypeScriptConfig(projectRoot, rawConfig));
478
+ resolvedByRawConfig.set(rawKey, scopeOptions);
479
+ }
480
+ environments.registerEnvironment({
481
+ vfs: combinedVfs,
482
+ compilerOptions: scopeOptions,
483
+ typesCache: twoslashCache
484
+ });
485
+ environments.registerScope(apiScope, scopeOptions);
486
+ }
487
+ yield* emit(PluginEvent.TwoslashInitialized({
488
+ ctx: {},
489
+ level: "debug",
490
+ durationMs: Math.round(performance.now() - twoslashStartMs),
491
+ vfsFileCount: combinedVfs.size
492
+ }));
493
+ return apiConfigs;
494
+ }) };
495
+ }));
584
496
  /** The raw TypeScript config an API declares, or undefined when it declares none. */
585
497
  function rawTsConfig(api) {
586
498
  if (api.tsconfig == null && api.compilerOptions == null) return void 0;
@@ -589,12 +501,6 @@ function rawTsConfig(api) {
589
501
  if (api.compilerOptions != null) cfg.compilerOptions = api.compilerOptions;
590
502
  return cfg;
591
503
  }
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
504
 
599
505
  //#endregion
600
506
  export { ConfigServiceLive };