rspress-plugin-api-extractor 0.11.0 → 0.13.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 +3 -3
- package/README.md +1 -0
- package/build-program.js +14 -5
- package/build-stages.js +88 -49
- package/config-helpers.js +7 -7
- package/errors.js +1 -5
- package/index.d.ts +1 -86
- package/layers/AppLayer.js +67 -0
- package/layers/api-results.js +83 -0
- package/layers/build-metrics.js +1 -1
- package/layers/{ConfigServiceLive.js → config-resolution.js} +115 -175
- 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 +1 -1
- package/markdown/helpers.js +8 -33
- package/markdown/shiki-utils.js +1 -1
- package/model-loader.js +3 -3
- package/observability/EventBus.js +2 -2
- package/observability/heartbeat.js +1 -1
- package/observability/sinks/metrics-sink.js +1 -1
- package/observability/sinks/trace-sink.js +4 -4
- package/package.json +5 -4
- package/plugin.js +30 -33
- package/prettier-formatter.js +1 -1
- package/remark-api-codeblocks.js +1 -1
- package/remark-with-api.js +1 -1
- package/schemas/config.js +1 -3
- package/services/ConfigService.js +37 -2
- package/services/HighlighterService.js +48 -3
- package/services/OgService.js +154 -2
- package/services/PluginConfig.js +1 -1
- package/services/TwoslashCacheService.js +128 -2
- package/services/TwoslashEnvironments.js +30 -2
- package/services/TypeRegistryService.js +178 -2
- package/shiki-transformer.js +1 -1
- package/sync-node-fs.js +6 -6
- package/tsconfig-parser.js +77 -95
- package/twoslash-access.js +1 -1
- package/twoslash-transformer.js +1 -1
- package/layers/HighlighterServiceLive.js +0 -52
- package/layers/OgServiceLive.js +0 -134
- package/layers/TwoslashCacheServiceLive.js +0 -108
- package/layers/TwoslashEnvironmentsLive.js +0 -33
- package/layers/TypeRegistryServiceLive.js +0 -162
- package/markdown/index.js +0 -11
- package/og-resolver.js +0 -64
- package/schemas/index.js +0 -6
- package/schemas/opengraph.js +0 -56
|
@@ -1,32 +1,29 @@
|
|
|
1
1
|
import { BuildId } from "../BuildEnv.js";
|
|
2
|
+
import { BuildMetrics } from "./build-metrics.js";
|
|
2
3
|
import { PluginEvent } from "../observability/events.js";
|
|
3
4
|
import { emit, wantsLevel } from "../observability/EventBus.js";
|
|
4
|
-
import { BuildMetrics } from "./build-metrics.js";
|
|
5
|
-
import "./ObservabilityLive.js";
|
|
6
5
|
import { TypeReferenceExtractor } from "../type-reference-extractor.js";
|
|
7
6
|
import { withPhase } from "../observability/spans.js";
|
|
8
|
-
import {
|
|
9
|
-
import { resolveTypeScriptConfig } from "../typescript-config.js";
|
|
7
|
+
import { normalizeThemeConfig } from "../markdown/shiki-utils.js";
|
|
10
8
|
import { apiScopeOf, deriveOutputPaths, normalizeBaseRoute, unscopedName } from "../path-derivation.js";
|
|
11
9
|
import { classifyApiConfig, extractAutoDetectedPackages, isVersionConfig, mergeLlmsPluginConfig, validateExternalPackages } from "../config-utils.js";
|
|
12
10
|
import { ApiExtractedPackage } from "../api-extracted-package.js";
|
|
13
11
|
import { CategoryResolver } from "../category-resolver.js";
|
|
14
12
|
import { ConfigValidationError } from "../errors.js";
|
|
15
|
-
import { normalizeThemeConfig } from "../markdown/shiki-utils.js";
|
|
16
13
|
import { loadApiModel, loadPackageJson, loadVersionModel } from "../model-loader.js";
|
|
17
14
|
import { DEFAULT_CATEGORIES } from "../schemas/config.js";
|
|
18
|
-
import "../schemas/index.js";
|
|
19
|
-
import { ConfigService } from "../services/ConfigService.js";
|
|
20
15
|
import { PluginConfig } from "../services/PluginConfig.js";
|
|
21
|
-
import { TwoslashCacheService } from "../services/TwoslashCacheService.js";
|
|
22
16
|
import { TypeRegistryService } from "../services/TypeRegistryService.js";
|
|
23
|
-
import {
|
|
17
|
+
import { emitVfsPayloadEvents, mergeApiResult } from "./api-results.js";
|
|
18
|
+
import { mergeExternalTypes } from "./external-types.js";
|
|
19
|
+
import { registerTypeEnvironments, resolveTsConfigTyped } from "./type-environment.js";
|
|
24
20
|
import path from "node:path";
|
|
21
|
+
import { Effect, Metric } from "effect";
|
|
22
|
+
import { deriveSiteUrl } from "@tsdoctor/seo";
|
|
25
23
|
import { hashContent } from "@tsdoctor/snapshot";
|
|
26
|
-
import {
|
|
27
|
-
import ts from "typescript";
|
|
24
|
+
import { PackageManifest } from "@effected/package-json";
|
|
28
25
|
|
|
29
|
-
//#region src/layers/
|
|
26
|
+
//#region src/layers/config-resolution.ts
|
|
30
27
|
/**
|
|
31
28
|
* Prepend import statements for external type references to the VFS declaration files.
|
|
32
29
|
* Returns per-entry payloads for event emission (heavy content/importRefs gated on wantTrace).
|
|
@@ -65,6 +62,40 @@ function prependImportsToVfs(vfs, apiPackage, packageName, wantTrace) {
|
|
|
65
62
|
return payloads;
|
|
66
63
|
}
|
|
67
64
|
/**
|
|
65
|
+
* Decode a loaded package.json into a typed {@link PackageManifest}, degrading
|
|
66
|
+
* to `undefined` when it does not satisfy the codec.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* `PackageManifest` is presence-lenient but shape-strict — the private
|
|
70
|
+
* workspace-root shape decodes fine, but one malformed field (a `version` of
|
|
71
|
+
* `"1.0"`, an `author` that is neither a string nor an object) fails the whole
|
|
72
|
+
* decode. That is the right strictness for the SEO layer, which needs real
|
|
73
|
+
* `Person` / `Repository` values rather than the discovery tier's raw unions,
|
|
74
|
+
* but it must never fail a docs build: the failure is surfaced as a
|
|
75
|
+
* `ConfigValidationWarning` (which reaches `issues.json`) and the manifest is
|
|
76
|
+
* simply absent. The same posture as the OG image path in `build-stages.ts`.
|
|
77
|
+
*/
|
|
78
|
+
function decodeManifest(packageJson, buildId, packageName) {
|
|
79
|
+
return Effect.gen(function* () {
|
|
80
|
+
if (packageJson == null) return void 0;
|
|
81
|
+
const decoded = yield* Effect.result(PackageManifest.decode(packageJson));
|
|
82
|
+
if (decoded._tag === "Failure") {
|
|
83
|
+
yield* emit(PluginEvent.ConfigValidationWarning({
|
|
84
|
+
ctx: {
|
|
85
|
+
buildId,
|
|
86
|
+
packageName
|
|
87
|
+
},
|
|
88
|
+
field: "packageJson",
|
|
89
|
+
value: packageName,
|
|
90
|
+
reason: decoded.failure.message,
|
|
91
|
+
level: "warn"
|
|
92
|
+
}));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
return decoded.success;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
68
99
|
* Validate plugin options and return an Effect that fails with ConfigValidationError.
|
|
69
100
|
*/
|
|
70
101
|
function validateOptions(options, rspressConfig) {
|
|
@@ -129,8 +160,12 @@ function validateOptions(options, rspressConfig) {
|
|
|
129
160
|
* would build a second `ConfigService` with its own captured `TypeRegistry`.
|
|
130
161
|
* The options come from {@link PluginConfig} now, so there is nothing to pass
|
|
131
162
|
* and nothing to call twice.
|
|
163
|
+
*
|
|
164
|
+
* The `Layer` around this lives on the service, as `ConfigService.layer`. The
|
|
165
|
+
* implementation stays here rather than in the service module because it is
|
|
166
|
+
* the bulk of config resolution; the service module declares the contract.
|
|
132
167
|
*/
|
|
133
|
-
const
|
|
168
|
+
const makeConfigService = Effect.gen(function* () {
|
|
134
169
|
const typeRegistry = yield* TypeRegistryService;
|
|
135
170
|
const options = yield* PluginConfig;
|
|
136
171
|
return { resolve: (rspressConfig) => Effect.gen(function* () {
|
|
@@ -146,11 +181,18 @@ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
|
|
|
146
181
|
const rspressLang = rspressConfig.lang;
|
|
147
182
|
const docsRoot = rspressConfig.root;
|
|
148
183
|
const rspressRoot = docsRoot || process.cwd();
|
|
184
|
+
const siteUrl = deriveSiteUrl(rspressConfig.siteOrigin, rspressConfig.base);
|
|
149
185
|
const categoryResolver = new CategoryResolver();
|
|
150
186
|
const pluginDefaults = categoryResolver.mergeCategories(DEFAULT_CATEGORIES, options.defaultCategories);
|
|
151
187
|
const apiConfigs = [];
|
|
152
188
|
const combinedVfs = /* @__PURE__ */ new Map();
|
|
153
189
|
const allExternalPackages = [];
|
|
190
|
+
/** The three above, as one value the merge helper can take. */
|
|
191
|
+
const acc = {
|
|
192
|
+
apiConfigs,
|
|
193
|
+
combinedVfs,
|
|
194
|
+
allExternalPackages
|
|
195
|
+
};
|
|
154
196
|
let firstApiTsconfig;
|
|
155
197
|
let firstApiCompilerOptions;
|
|
156
198
|
/**
|
|
@@ -176,14 +218,29 @@ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
|
|
|
176
218
|
*/
|
|
177
219
|
const processSimpleApi = (api, model, outputDir, fullRoute, wantTrace) => Effect.gen(function* () {
|
|
178
220
|
const { apiPackage, source: loaderSource } = yield* withModelLoadEvents(loadApiModel(model));
|
|
179
|
-
|
|
221
|
+
{
|
|
180
222
|
const resolvedCategories = categoryResolver.resolveCategoryConfig(pluginDefaults, api.categories);
|
|
181
223
|
const resolvedSource = categoryResolver.resolveSourceConfig(api.source, loaderSource);
|
|
182
224
|
const resolvedLlms = mergeLlmsPluginConfig(options.llmsPlugin, api.llmsPlugin);
|
|
183
|
-
const packageJson = api.packageJson ?
|
|
184
|
-
|
|
225
|
+
const packageJson = api.packageJson ? yield* Effect.tryPromise({
|
|
226
|
+
try: () => loadPackageJson(api.packageJson),
|
|
227
|
+
catch: (cause) => new ConfigValidationError({
|
|
228
|
+
field: "packageJson",
|
|
229
|
+
reason: cause instanceof Error ? cause.message : String(cause),
|
|
230
|
+
cause
|
|
231
|
+
})
|
|
232
|
+
}) : void 0;
|
|
233
|
+
const manifest = yield* decodeManifest(packageJson, buildId, api.packageName);
|
|
234
|
+
yield* Effect.try({
|
|
235
|
+
try: () => validateExternalPackages(api.externalPackages, packageJson),
|
|
236
|
+
catch: (cause) => new ConfigValidationError({
|
|
237
|
+
field: "externalPackages",
|
|
238
|
+
reason: cause instanceof Error ? cause.message : String(cause),
|
|
239
|
+
cause
|
|
240
|
+
})
|
|
241
|
+
});
|
|
185
242
|
const externalPackages = api.externalPackages || extractAutoDetectedPackages(packageJson, api.autoDetectDependencies);
|
|
186
|
-
if (externalPackages && externalPackages.length > 0)
|
|
243
|
+
if (externalPackages && externalPackages.length > 0) yield* Metric.update(BuildMetrics.externalPackagesTotal, externalPackages.length);
|
|
187
244
|
const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).generateVfs();
|
|
188
245
|
const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
|
|
189
246
|
const resolvedOgImage = api.ogImage ?? options.ogImage;
|
|
@@ -201,15 +258,16 @@ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
|
|
|
201
258
|
categories: resolvedCategories,
|
|
202
259
|
...resolvedSource != null ? { source: resolvedSource } : {},
|
|
203
260
|
...packageJson != null ? { packageJson } : {},
|
|
261
|
+
...manifest != null ? { manifest } : {},
|
|
204
262
|
...resolvedLlms != null ? { llmsPlugin: resolvedLlms } : {},
|
|
205
|
-
...
|
|
263
|
+
...siteUrl != null ? { siteUrl } : {},
|
|
206
264
|
...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
|
|
207
265
|
docsDir: path.dirname(outputDir),
|
|
208
266
|
...docsRoot != null ? { docsRoot } : {},
|
|
209
267
|
...resolvedTheme != null ? { theme: resolvedTheme } : {}
|
|
210
268
|
}
|
|
211
269
|
};
|
|
212
|
-
}
|
|
270
|
+
}
|
|
213
271
|
});
|
|
214
272
|
yield* withPhase("modelLoad", { buildId }, Effect.gen(function* () {
|
|
215
273
|
if (options.api) {
|
|
@@ -238,16 +296,31 @@ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
|
|
|
238
296
|
};
|
|
239
297
|
const versionConfig = isVersionConfig(versionValue) ? versionValue : { model: versionValue };
|
|
240
298
|
const { apiPackage, packageJson: versionPackageJson, categories: versionCategories, source: versionSource, externalPackages: versionExternalPackages, autoDetectDependencies: versionAutoDetectDependencies, llmsPlugin: versionLlms, ogImage: versionOgImage } = yield* withModelLoadEvents(loadVersionModel(versionConfig));
|
|
241
|
-
|
|
242
|
-
|
|
299
|
+
{
|
|
300
|
+
yield* Metric.update(BuildMetrics.apiVersionsLoaded, 1);
|
|
243
301
|
const resolvedCategories = categoryResolver.resolveCategoryConfig(pluginDefaults, api.categories, versionCategories);
|
|
244
302
|
const resolvedSource = categoryResolver.resolveSourceConfig(api.source, versionSource);
|
|
245
303
|
const resolvedLlms = mergeLlmsPluginConfig(options.llmsPlugin, api.llmsPlugin, versionLlms);
|
|
246
|
-
const packageJson = versionPackageJson || (api.packageJson ?
|
|
247
|
-
|
|
304
|
+
const packageJson = versionPackageJson || (api.packageJson ? yield* Effect.tryPromise({
|
|
305
|
+
try: () => loadPackageJson(api.packageJson),
|
|
306
|
+
catch: (cause) => new ConfigValidationError({
|
|
307
|
+
field: "packageJson",
|
|
308
|
+
reason: cause instanceof Error ? cause.message : String(cause),
|
|
309
|
+
cause
|
|
310
|
+
})
|
|
311
|
+
}) : void 0);
|
|
312
|
+
const manifest = yield* decodeManifest(packageJson, buildId, api.packageName);
|
|
313
|
+
yield* Effect.try({
|
|
314
|
+
try: () => validateExternalPackages(versionExternalPackages || api.externalPackages, packageJson),
|
|
315
|
+
catch: (cause) => new ConfigValidationError({
|
|
316
|
+
field: "externalPackages",
|
|
317
|
+
reason: cause instanceof Error ? cause.message : String(cause),
|
|
318
|
+
cause
|
|
319
|
+
})
|
|
320
|
+
});
|
|
248
321
|
const autoDetectOptions = versionAutoDetectDependencies || api.autoDetectDependencies;
|
|
249
322
|
const externalPackages = versionExternalPackages || api.externalPackages || extractAutoDetectedPackages(packageJson, autoDetectOptions);
|
|
250
|
-
if (externalPackages && externalPackages.length > 0)
|
|
323
|
+
if (externalPackages && externalPackages.length > 0) yield* Metric.update(BuildMetrics.externalPackagesTotal, externalPackages.length);
|
|
251
324
|
const vfs = ApiExtractedPackage.fromPackage(apiPackage, api.packageName).generateVfs();
|
|
252
325
|
const vfsPayloads = prependImportsToVfs(vfs, apiPackage, api.packageName, wantTrace);
|
|
253
326
|
const resolvedOgImage = versionOgImage ?? api.ogImage ?? options.ogImage;
|
|
@@ -267,42 +340,20 @@ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
|
|
|
267
340
|
categories: resolvedCategories,
|
|
268
341
|
...resolvedSource != null ? { source: resolvedSource } : {},
|
|
269
342
|
...packageJson != null ? { packageJson } : {},
|
|
343
|
+
...manifest != null ? { manifest } : {},
|
|
270
344
|
...resolvedLlms != null ? { llmsPlugin: resolvedLlms } : {},
|
|
271
|
-
...
|
|
345
|
+
...siteUrl != null ? { siteUrl } : {},
|
|
272
346
|
...resolvedOgImage != null ? { ogImage: resolvedOgImage } : {},
|
|
273
347
|
docsDir: path.dirname(outputDir),
|
|
274
348
|
...docsRoot != null ? { docsRoot } : {},
|
|
275
349
|
...resolvedTheme != null ? { theme: resolvedTheme } : {}
|
|
276
350
|
}
|
|
277
351
|
};
|
|
278
|
-
}
|
|
352
|
+
}
|
|
279
353
|
}), { concurrency: "unbounded" });
|
|
280
354
|
for (const result of versionResults) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (result.config) apiConfigs.push(result.config);
|
|
284
|
-
for (const payload of result.vfsPayloads) {
|
|
285
|
-
yield* emit(PluginEvent.VfsGenerated({
|
|
286
|
-
ctx: {
|
|
287
|
-
packageName: api.packageName,
|
|
288
|
-
...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
|
|
289
|
-
},
|
|
290
|
-
level: "debug",
|
|
291
|
-
file: payload.file,
|
|
292
|
-
declCount: payload.declCount,
|
|
293
|
-
contentHash: payload.contentHash,
|
|
294
|
-
...wantTrace && payload.content ? { content: payload.content } : {}
|
|
295
|
-
}));
|
|
296
|
-
if (payload.hasImports) yield* emit(PluginEvent.ImportsPrepended({
|
|
297
|
-
ctx: {
|
|
298
|
-
packageName: api.packageName,
|
|
299
|
-
...payload.entryPoint ? { entryPoint: payload.entryPoint } : {}
|
|
300
|
-
},
|
|
301
|
-
level: "debug",
|
|
302
|
-
file: payload.file,
|
|
303
|
-
imports: wantTrace ? payload.importRefs : []
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
355
|
+
mergeApiResult(acc, result);
|
|
356
|
+
yield* emitVfsPayloadEvents(api.packageName, result.vfsPayloads, wantTrace);
|
|
306
357
|
}
|
|
307
358
|
} else if (api.model) {
|
|
308
359
|
const dp = deriveOutputPaths({
|
|
@@ -317,31 +368,8 @@ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
|
|
|
317
368
|
})[0];
|
|
318
369
|
if (dp) {
|
|
319
370
|
const result = yield* processSimpleApi(api, api.model, dp.outputDir, dp.routeBase, wantTrace);
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
}
|
|
371
|
+
mergeApiResult(acc, result);
|
|
372
|
+
yield* emitVfsPayloadEvents(api.packageName, result.vfsPayloads, wantTrace);
|
|
345
373
|
}
|
|
346
374
|
}
|
|
347
375
|
} else if (options.apis) {
|
|
@@ -367,35 +395,10 @@ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
|
|
|
367
395
|
})[0];
|
|
368
396
|
if (!dp) return [];
|
|
369
397
|
const result = yield* processSimpleApi(api, api.model, dp.outputDir, dp.routeBase, wantTrace);
|
|
370
|
-
|
|
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
|
-
},
|
|
387
|
-
level: "debug",
|
|
388
|
-
file: payload.file,
|
|
389
|
-
imports: wantTrace ? payload.importRefs : []
|
|
390
|
-
}));
|
|
391
|
-
}
|
|
398
|
+
yield* emitVfsPayloadEvents(api.packageName, result.vfsPayloads, wantTrace);
|
|
392
399
|
return [result];
|
|
393
400
|
}), { 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);
|
|
398
|
-
}
|
|
401
|
+
for (const results of multiResults) for (const result of results) mergeApiResult(acc, result);
|
|
399
402
|
}
|
|
400
403
|
}));
|
|
401
404
|
const loadMs = performance.now() - loadStart;
|
|
@@ -413,86 +416,23 @@ const ConfigServiceLive = Layer.effect(ConfigService, Effect.gen(function* () {
|
|
|
413
416
|
if (firstApiTsconfig != null) globalTsConfig.tsconfig = firstApiTsconfig;
|
|
414
417
|
if (firstApiCompilerOptions != null) globalTsConfig.compilerOptions = firstApiCompilerOptions;
|
|
415
418
|
}
|
|
416
|
-
const resolvedCompilerOptions = yield*
|
|
419
|
+
const resolvedCompilerOptions = yield* resolveTsConfigTyped(projectRoot, globalTsConfig);
|
|
417
420
|
yield* emit(PluginEvent.TsCacheCreated({
|
|
418
421
|
ctx: {},
|
|
419
422
|
level: "debug",
|
|
420
423
|
compilerOptions: `target=${resolvedCompilerOptions.target}, module=${resolvedCompilerOptions.module}, lib=[${resolvedCompilerOptions.lib?.join(", ") ?? ""}]`,
|
|
421
424
|
durationMs: 0
|
|
422
425
|
}));
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
}));
|
|
443
|
-
}
|
|
444
|
-
}
|
|
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
|
|
426
|
+
yield* mergeExternalTypes(typeRegistry, combinedVfs, apiConfigs, allExternalPackages);
|
|
427
|
+
yield* registerTypeEnvironments({
|
|
428
|
+
combinedVfs,
|
|
429
|
+
resolvedCompilerOptions,
|
|
430
|
+
scopeTsConfigs,
|
|
431
|
+
projectRoot
|
|
467
432
|
});
|
|
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;
|
|
473
|
-
}
|
|
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
433
|
return apiConfigs;
|
|
494
434
|
}) };
|
|
495
|
-
})
|
|
435
|
+
});
|
|
496
436
|
/** The raw TypeScript config an API declares, or undefined when it declares none. */
|
|
497
437
|
function rawTsConfig(api) {
|
|
498
438
|
if (api.tsconfig == null && api.compilerOptions == null) return void 0;
|
|
@@ -503,4 +443,4 @@ function rawTsConfig(api) {
|
|
|
503
443
|
}
|
|
504
444
|
|
|
505
445
|
//#endregion
|
|
506
|
-
export {
|
|
446
|
+
export { makeConfigService };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { PluginEvent } from "../observability/events.js";
|
|
2
|
+
import { emit } from "../observability/EventBus.js";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/layers/external-types.ts
|
|
6
|
+
/**
|
|
7
|
+
* Merging external package declarations into the build's VFS.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* The one phase of config resolution that **degrades rather than fails**.
|
|
11
|
+
* External types are an enhancement: without them, code blocks render without
|
|
12
|
+
* Twoslash enrichment, which is a worse page rather than a broken build. The
|
|
13
|
+
* caller therefore never sees this fail — the failure is reported as a warning
|
|
14
|
+
* and the VFS is left as it was.
|
|
15
|
+
*
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Fetch external package declarations and merge them into `combinedVfs`.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* **First-party packages are excluded, and that exclusion is load-bearing.**
|
|
23
|
+
* The packages being documented are already served from their api.json-derived
|
|
24
|
+
* virtual VFS, which is authoritative. Their published version may not exist
|
|
25
|
+
* yet (an optimistic next version), and if it did, fetching it would clobber
|
|
26
|
+
* the generated declarations with the previous release's — silently
|
|
27
|
+
* documenting the wrong API.
|
|
28
|
+
*
|
|
29
|
+
* Versions are resolved to exact published ones first: the CDN behind
|
|
30
|
+
* `loadPackages` 404s on a range or an unpublished package, so a spec that
|
|
31
|
+
* cannot be resolved is dropped with a debug event rather than failing the
|
|
32
|
+
* batch it is in.
|
|
33
|
+
*
|
|
34
|
+
* Mutates `combinedVfs` in place, matching the other resolution phases.
|
|
35
|
+
*
|
|
36
|
+
* The registry arrives as an argument rather than being pulled from context.
|
|
37
|
+
* `ConfigService.layer` resolves it ONCE at layer construction, and yielding
|
|
38
|
+
* the tag here instead would move it into `resolve`'s per-call requirement
|
|
39
|
+
* channel — a different resolution point, and a widened public signature, for
|
|
40
|
+
* a dependency that does not vary per call.
|
|
41
|
+
*/
|
|
42
|
+
const mergeExternalTypes = (typeRegistry, combinedVfs, apiConfigs, allExternalPackages) => Effect.gen(function* () {
|
|
43
|
+
const documentedPackageNames = new Set(apiConfigs.map((config) => config.packageName));
|
|
44
|
+
const externalPackagesToLoad = allExternalPackages.filter((pkg) => !documentedPackageNames.has(pkg.name));
|
|
45
|
+
const typeLoadResult = yield* Effect.result(Effect.gen(function* () {
|
|
46
|
+
if (externalPackagesToLoad.length === 0) return;
|
|
47
|
+
const resolvedPackages = yield* typeRegistry.resolveVersions(externalPackagesToLoad);
|
|
48
|
+
const droppedCount = externalPackagesToLoad.length - resolvedPackages.length;
|
|
49
|
+
if (droppedCount > 0) yield* emit(PluginEvent.ExternalPackageSkipped({
|
|
50
|
+
ctx: {},
|
|
51
|
+
level: "debug",
|
|
52
|
+
reason: `${droppedCount} unresolvable package(s) (unpublished or workspace-only)`
|
|
53
|
+
}));
|
|
54
|
+
if (resolvedPackages.length === 0) return;
|
|
55
|
+
const result = yield* typeRegistry.loadPackages(resolvedPackages);
|
|
56
|
+
for (const [filePath, content] of result.vfs.entries()) combinedVfs.set(filePath, content);
|
|
57
|
+
yield* emit(PluginEvent.VfsMerged({
|
|
58
|
+
ctx: {},
|
|
59
|
+
level: "debug",
|
|
60
|
+
totalFiles: result.vfs.size,
|
|
61
|
+
packages: resolvedPackages.map((p) => p.name)
|
|
62
|
+
}));
|
|
63
|
+
}));
|
|
64
|
+
if (typeLoadResult._tag === "Failure") yield* emit(PluginEvent.ConfigCascadeWarning({
|
|
65
|
+
ctx: {},
|
|
66
|
+
level: "warn",
|
|
67
|
+
field: "externalTypes",
|
|
68
|
+
chosen: "empty VFS",
|
|
69
|
+
ignored: [typeLoadResult.failure.message ?? String(typeLoadResult.failure)]
|
|
70
|
+
}));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { mergeExternalTypes };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { BuildMetrics, makeMetricStore } from "./build-metrics.js";
|
|
1
2
|
import { makeEventBusLayer } from "../observability/EventBus.js";
|
|
2
3
|
import { formatCodeBlockReport, seriesFor } from "../observability/metric-report.js";
|
|
3
|
-
import { BuildMetrics, makeMetricStore } from "./build-metrics.js";
|
|
4
4
|
import { makeConsoleSink } from "../observability/sinks/console-sink.js";
|
|
5
5
|
import { makeIssuesSink } from "../observability/sinks/issues-sink.js";
|
|
6
6
|
import { makeMetricsSink } from "../observability/sinks/metrics-sink.js";
|
|
@@ -8,7 +8,7 @@ import { makeRenderSink } from "../observability/sinks/render-sink.js";
|
|
|
8
8
|
import { makeTraceSink } from "../observability/sinks/trace-sink.js";
|
|
9
9
|
import { Effect, Layer, Logger, Metric, References } from "effect";
|
|
10
10
|
|
|
11
|
-
//#region src/layers/
|
|
11
|
+
//#region src/layers/observability.ts
|
|
12
12
|
function formatTime(date) {
|
|
13
13
|
return date.toTimeString().slice(0, 8);
|
|
14
14
|
}
|
|
@@ -117,4 +117,4 @@ const logBuildSummary = (slowCodeBlockMs, renderPhase) => Effect.gen(function* (
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
//#endregion
|
|
120
|
-
export {
|
|
120
|
+
export { buildEventBus, logBuildSummary, makeSummaryLoggerLayer };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { PluginEvent } from "../observability/events.js";
|
|
2
|
+
import { emit } from "../observability/EventBus.js";
|
|
3
|
+
import { resolveTypeScriptConfig } from "../typescript-config.js";
|
|
4
|
+
import { TwoslashEnvironments } from "../services/TwoslashEnvironments.js";
|
|
5
|
+
import { ConfigValidationError } from "../errors.js";
|
|
6
|
+
import { twoslashEnvHash } from "../twoslash-cache.js";
|
|
7
|
+
import { TwoslashCacheService } from "../services/TwoslashCacheService.js";
|
|
8
|
+
import { Effect } from "effect";
|
|
9
|
+
import ts from "typescript";
|
|
10
|
+
|
|
11
|
+
//#region src/layers/type-environment.ts
|
|
12
|
+
/**
|
|
13
|
+
* Building the build's Twoslash type-checking environments.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* The last phase of config resolution, and the only one that has to run after
|
|
17
|
+
* every API has contributed to the VFS: the result cache is keyed on the type
|
|
18
|
+
* environment, so it can only be opened once the VFS is final, and no code
|
|
19
|
+
* block may be rendered before it is.
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a TypeScript configuration, failing typed on a malformed one.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* `resolveTypeScriptConfig` throws a `TsConfigParseError` for a missing file, a
|
|
28
|
+
* syntax error or a semantically invalid config. Both call sites used to run it
|
|
29
|
+
* inside `Effect.promise`, so all three became untyped defects: the build died
|
|
30
|
+
* with an unhandled rejection naming a file the user could fix, and wrote no
|
|
31
|
+
* `issues.json` entry for it.
|
|
32
|
+
*
|
|
33
|
+
* A malformed tsconfig is a user misconfiguration, so it is fatal and TYPED —
|
|
34
|
+
* not degraded. Falling back to default compiler options would type-check every
|
|
35
|
+
* example against a configuration the user did not ask for and silently render
|
|
36
|
+
* wrong hovers, which is the failure shape this subsystem suffers from most
|
|
37
|
+
* (see the `lib`-spelling defect in `type-loading-vfs.md`).
|
|
38
|
+
*/
|
|
39
|
+
const resolveTsConfigTyped = (projectRoot, config) => Effect.tryPromise({
|
|
40
|
+
try: () => resolveTypeScriptConfig(projectRoot, config),
|
|
41
|
+
catch: (cause) => new ConfigValidationError({
|
|
42
|
+
field: "tsconfig",
|
|
43
|
+
reason: cause instanceof Error ? cause.message : String(cause),
|
|
44
|
+
cause
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* Open the Twoslash result cache and register one environment per distinct
|
|
49
|
+
* compiler configuration.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* **The build-wide options are registered FIRST, deliberately.** An unknown
|
|
53
|
+
* scope falls back to the first environment registered, and a `with-api` fence
|
|
54
|
+
* can appear on a page outside any documented package's route — checking it
|
|
55
|
+
* under the build-wide configuration beats not checking it at all.
|
|
56
|
+
*
|
|
57
|
+
* Resolution is memoised twice over. This function memoises by RAW config, so
|
|
58
|
+
* N APIs sharing a tsconfig read it from disk once; `registerEnvironment` then
|
|
59
|
+
* dedupes by a fingerprint of the ENCODED options, so APIs that spell the same
|
|
60
|
+
* configuration differently still share one TypeScript environment. The second
|
|
61
|
+
* of those is load-bearing: when the two fingerprints drifted apart once, every
|
|
62
|
+
* scope lookup missed, per-scope type-checking silently degraded to build-wide,
|
|
63
|
+
* and a 994-test suite stayed green through it.
|
|
64
|
+
*/
|
|
65
|
+
const registerTypeEnvironments = (input) => Effect.gen(function* () {
|
|
66
|
+
const twoslashEnv = twoslashEnvHash(input.combinedVfs, `typescript@${ts.version}`);
|
|
67
|
+
const twoslashCache = yield* (yield* TwoslashCacheService).open(twoslashEnv);
|
|
68
|
+
yield* emit(PluginEvent.TwoslashCacheLoaded({
|
|
69
|
+
ctx: {},
|
|
70
|
+
level: "debug",
|
|
71
|
+
envHash: twoslashEnv,
|
|
72
|
+
entries: twoslashCache.entries().size
|
|
73
|
+
}));
|
|
74
|
+
const twoslashStartMs = performance.now();
|
|
75
|
+
const environments = yield* TwoslashEnvironments;
|
|
76
|
+
environments.registerEnvironment({
|
|
77
|
+
vfs: input.combinedVfs,
|
|
78
|
+
compilerOptions: input.resolvedCompilerOptions,
|
|
79
|
+
typesCache: twoslashCache
|
|
80
|
+
});
|
|
81
|
+
const resolvedByRawConfig = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const [apiScope, rawConfig] of input.scopeTsConfigs) {
|
|
83
|
+
if (rawConfig === void 0) {
|
|
84
|
+
environments.registerScope(apiScope, input.resolvedCompilerOptions);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const rawKey = JSON.stringify([String(rawConfig.tsconfig ?? ""), rawConfig.compilerOptions ?? null]);
|
|
88
|
+
let scopeOptions = resolvedByRawConfig.get(rawKey);
|
|
89
|
+
if (scopeOptions === void 0) {
|
|
90
|
+
scopeOptions = yield* resolveTsConfigTyped(input.projectRoot, rawConfig);
|
|
91
|
+
resolvedByRawConfig.set(rawKey, scopeOptions);
|
|
92
|
+
}
|
|
93
|
+
environments.registerEnvironment({
|
|
94
|
+
vfs: input.combinedVfs,
|
|
95
|
+
compilerOptions: scopeOptions,
|
|
96
|
+
typesCache: twoslashCache
|
|
97
|
+
});
|
|
98
|
+
environments.registerScope(apiScope, scopeOptions);
|
|
99
|
+
}
|
|
100
|
+
yield* emit(PluginEvent.TwoslashInitialized({
|
|
101
|
+
ctx: {},
|
|
102
|
+
level: "debug",
|
|
103
|
+
durationMs: Math.round(performance.now() - twoslashStartMs),
|
|
104
|
+
vfsFileCount: input.combinedVfs.size
|
|
105
|
+
}));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
export { registerTypeEnvironments, resolveTsConfigTyped };
|