rspress-plugin-api-extractor 0.2.2 → 0.3.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/api-extracted-package.js +2 -1
- package/build-program.js +20 -12
- package/build-stages.js +123 -29
- package/config-utils.js +36 -7
- package/index.d.ts +329 -202
- package/layers/ConfigServiceLive.js +300 -136
- package/layers/ObservabilityLive.js +49 -85
- package/layers/TypeRegistryServiceLive.js +122 -21
- package/layers/build-metrics.js +61 -0
- package/llms-program.js +29 -7
- package/loader.js +16 -2
- package/markdown/shiki-utils.js +16 -2
- package/observability/EventBus.js +38 -0
- package/observability/events.js +17 -0
- package/observability/sinks/console-sink.js +63 -0
- package/observability/sinks/metrics-sink.js +68 -0
- package/observability/sinks/trace-sink.js +38 -0
- package/observability/spans.js +57 -0
- package/og-resolver.js +37 -5
- package/package.json +4 -4
- package/plugin.js +73 -19
- package/prettier-formatter.js +15 -4
- package/remark-api-codeblocks.js +22 -3
- package/remark-with-api.js +27 -14
- package/runtime/components/ApiExample/index.js +5 -5
- package/runtime/components/ApiMember/index.js +5 -7
- package/runtime/components/ApiSignature/index.js +4 -6
- package/runtime/components/EnumMembersTable/index.js +5 -0
- package/runtime/components/ExampleBlock/index.js +5 -3
- package/runtime/components/MemberSignature/index.js +4 -2
- package/runtime/components/ParametersTable/index.js +5 -0
- package/runtime/components/SignatureBlock/index.js +4 -2
- package/runtime/components/shared/variables.css +0 -15
- package/runtime/index.d.ts +65 -399
- package/runtime/index.js +1 -5
- package/runtime/utils/hast-renderer.js +1 -0
- package/schemas/config.js +105 -4
- package/schemas/index.js +2 -1
- package/schemas/observability.js +62 -0
- package/schemas/opengraph.js +30 -0
- package/schemas/performance.js +1 -1
- package/serve.js +13 -0
- package/twoslash-transformer.js +93 -8
|
@@ -1,102 +1,61 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { makeEventBusLayer } from "../observability/EventBus.js";
|
|
2
|
+
import { makeConsoleSink } from "../observability/sinks/console-sink.js";
|
|
3
|
+
import { BuildMetrics } from "./build-metrics.js";
|
|
4
|
+
import { makeMetricsSink } from "../observability/sinks/metrics-sink.js";
|
|
5
|
+
import { makeTraceSink } from "../observability/sinks/trace-sink.js";
|
|
6
|
+
import { Effect, Layer, LogLevel, Logger, Metric } from "effect";
|
|
2
7
|
|
|
3
8
|
//#region src/layers/ObservabilityLive.ts
|
|
4
|
-
/**
|
|
5
|
-
* All build metrics as named counters/histograms.
|
|
6
|
-
*
|
|
7
|
-
* Note: Effect Metrics use a process-wide registry. In tests, counters
|
|
8
|
-
* accumulate across test cases within the same process. Test assertions
|
|
9
|
-
* should use loose matching (toContain) rather than exact count checks.
|
|
10
|
-
*/
|
|
11
|
-
const BuildMetrics = {
|
|
12
|
-
filesTotal: Metric.counter("files.total"),
|
|
13
|
-
filesNew: Metric.counter("files.new"),
|
|
14
|
-
filesModified: Metric.counter("files.modified"),
|
|
15
|
-
filesUnchanged: Metric.counter("files.unchanged"),
|
|
16
|
-
codeblockDuration: Metric.histogram("codeblock.duration", MetricBoundaries.fromIterable([
|
|
17
|
-
10,
|
|
18
|
-
25,
|
|
19
|
-
50,
|
|
20
|
-
100,
|
|
21
|
-
200,
|
|
22
|
-
500,
|
|
23
|
-
1e3
|
|
24
|
-
])),
|
|
25
|
-
codeblockShikiDuration: Metric.histogram("codeblock.shiki.duration", MetricBoundaries.fromIterable([
|
|
26
|
-
5,
|
|
27
|
-
10,
|
|
28
|
-
25,
|
|
29
|
-
50,
|
|
30
|
-
100,
|
|
31
|
-
250
|
|
32
|
-
])),
|
|
33
|
-
codeblockTotal: Metric.counter("codeblock.total"),
|
|
34
|
-
codeblockSlow: Metric.counter("codeblock.slow"),
|
|
35
|
-
twoslashErrors: Metric.counter("twoslash.errors"),
|
|
36
|
-
prettierErrors: Metric.counter("prettier.errors"),
|
|
37
|
-
pagesGenerated: Metric.counter("pages.generated"),
|
|
38
|
-
apiVersionsLoaded: Metric.counter("api.versions.loaded"),
|
|
39
|
-
externalPackagesTotal: Metric.counter("external.packages.total")
|
|
40
|
-
};
|
|
41
|
-
/**
|
|
42
|
-
* Format a Date as HH:MM:SS for console output.
|
|
43
|
-
*/
|
|
44
9
|
function formatTime(date) {
|
|
45
10
|
return date.toTimeString().slice(0, 8);
|
|
46
11
|
}
|
|
47
12
|
/**
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Create a custom plugin logger for the given mode.
|
|
57
|
-
* Uses a closure to capture debugMode — no mutable module state.
|
|
13
|
+
* A slim Effect Logger layer that gates the residual `Effect.log*` calls in
|
|
14
|
+
* `build-program.ts` and `logBuildSummary` at the configured level.
|
|
15
|
+
*
|
|
16
|
+
* Level mapping: none→None, error→Error, warn→Warning, info→Info,
|
|
17
|
+
* debug/trace→Debug. Format: `[HH:MM:SS] <prefix><message>` with
|
|
18
|
+
* `⚠️ ` / `🔴 ` prefixes for Warning / Error to match the EventBus
|
|
19
|
+
* console-sink style.
|
|
58
20
|
*/
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
...annotationsToObject(annotations)
|
|
67
|
-
};
|
|
68
|
-
console.log(JSON.stringify(entry));
|
|
69
|
-
} else {
|
|
70
|
-
const time = formatTime(date);
|
|
71
|
-
const msg = typeof message === "string" ? message : String(message);
|
|
72
|
-
const prefix = logLevel._tag === "Warning" ? "⚠️ " : logLevel._tag === "Error" ? "🔴 " : "";
|
|
73
|
-
console.log(`[${time}] ${prefix}${msg}`);
|
|
74
|
-
}
|
|
21
|
+
function makeSummaryLoggerLayer(logLevel) {
|
|
22
|
+
const effectLevel = logLevel === "none" ? LogLevel.None : logLevel === "error" ? LogLevel.Error : logLevel === "warn" ? LogLevel.Warning : logLevel === "info" ? LogLevel.Info : LogLevel.Debug;
|
|
23
|
+
const pluginLogger = Logger.make(({ logLevel: lvl, message, date }) => {
|
|
24
|
+
const time = formatTime(date);
|
|
25
|
+
const msg = typeof message === "string" ? message : String(message);
|
|
26
|
+
const prefix = lvl._tag === "Warning" ? "⚠️ " : lvl._tag === "Error" ? "🔴 " : "";
|
|
27
|
+
console.log(`[${time}] ${prefix}${msg}`);
|
|
75
28
|
});
|
|
29
|
+
return Layer.mergeAll(Logger.replace(Logger.defaultLogger, pluginLogger), Logger.minimumLogLevel(effectLevel));
|
|
76
30
|
}
|
|
77
31
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
32
|
+
* Compose the console + metrics (+ optional trace) sinks into an EventBus layer.
|
|
33
|
+
*
|
|
34
|
+
* When `traceIsDefault` is true the trace path was derived from the guessed
|
|
35
|
+
* outDir at factory time. In that case we create the sink in deferred mode
|
|
36
|
+
* (no `initialPath`) so no stray empty file is written to the guessed path;
|
|
37
|
+
* `plugin.ts` must call `trace.setPath(realPath)` in the `config()` hook once
|
|
38
|
+
* the real RSPress `outDir` is known.
|
|
80
39
|
*
|
|
81
|
-
*
|
|
40
|
+
* When `traceIsDefault` is false the caller supplied an explicit path string,
|
|
41
|
+
* so we open the file eagerly (existing behaviour).
|
|
82
42
|
*/
|
|
83
|
-
function
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
none: LogLevel.None
|
|
92
|
-
}[logLevel];
|
|
93
|
-
return Layer.mergeAll(Logger.replace(Logger.defaultLogger, pluginLogger), Logger.minimumLogLevel(effectLogLevel));
|
|
43
|
+
function buildEventBus(obs, traceIsDefault = false) {
|
|
44
|
+
const sinks = [makeConsoleSink(obs.logLevel, { json: obs.json }), makeMetricsSink()];
|
|
45
|
+
const trace = obs.tracePath ? makeTraceSink(traceIsDefault ? void 0 : obs.tracePath) : null;
|
|
46
|
+
if (trace) sinks.push(trace);
|
|
47
|
+
return {
|
|
48
|
+
layer: makeEventBusLayer(sinks),
|
|
49
|
+
trace
|
|
50
|
+
};
|
|
94
51
|
}
|
|
95
52
|
/**
|
|
96
53
|
* Log a build summary by reading all metric snapshots.
|
|
54
|
+
* Accepts the configured slow-codeblock threshold so the warning message
|
|
55
|
+
* interpolates the actual threshold rather than a hard-coded 100ms.
|
|
97
56
|
* Replaces the 4 separate logSummary() calls in afterBuild.
|
|
98
57
|
*/
|
|
99
|
-
const logBuildSummary = Effect.gen(function* () {
|
|
58
|
+
const logBuildSummary = (slowCodeBlockMs) => Effect.gen(function* () {
|
|
100
59
|
const filesTotal = yield* Metric.value(BuildMetrics.filesTotal);
|
|
101
60
|
const filesNew = yield* Metric.value(BuildMetrics.filesNew);
|
|
102
61
|
const filesModified = yield* Metric.value(BuildMetrics.filesModified);
|
|
@@ -105,6 +64,9 @@ const logBuildSummary = Effect.gen(function* () {
|
|
|
105
64
|
const prettierErrors = yield* Metric.value(BuildMetrics.prettierErrors);
|
|
106
65
|
const codeblockTotal = yield* Metric.value(BuildMetrics.codeblockTotal);
|
|
107
66
|
const codeblockSlow = yield* Metric.value(BuildMetrics.codeblockSlow);
|
|
67
|
+
const pagesGenerated = yield* Metric.value(BuildMetrics.pagesGenerated);
|
|
68
|
+
const externalPackages = yield* Metric.value(BuildMetrics.externalPackagesTotal);
|
|
69
|
+
const phaseDurationSnapshot = yield* Metric.value(BuildMetrics.phaseDuration);
|
|
108
70
|
const total = filesTotal.count;
|
|
109
71
|
const newCount = filesNew.count;
|
|
110
72
|
const modified = filesModified.count;
|
|
@@ -122,15 +84,17 @@ const logBuildSummary = Effect.gen(function* () {
|
|
|
122
84
|
if (unchanged > 0) parts.push(`${unchanged} unchanged`);
|
|
123
85
|
yield* Effect.log(`📝 ${total} files (${parts.join(", ")})`);
|
|
124
86
|
}
|
|
125
|
-
if (
|
|
87
|
+
if (pagesGenerated.count > 0) yield* Effect.log(`🧩 ${pagesGenerated.count} pages, ${externalPackages.count} external package(s)`);
|
|
88
|
+
if (phaseDurationSnapshot.count > 0) yield* Effect.log(`⏱ ${phaseDurationSnapshot.count} phase(s) timed`);
|
|
89
|
+
if (blocks > 0 && slowBlocks > 0) yield* Effect.logWarning(`Code block performance: ${slowBlocks} of ${blocks} blocks were slow (>${slowCodeBlockMs}ms)`);
|
|
126
90
|
const totalErrors = tsErrors + prErrors;
|
|
127
91
|
if (totalErrors > 0) {
|
|
128
92
|
const errorParts = [];
|
|
129
93
|
if (tsErrors > 0) errorParts.push(`${tsErrors} Twoslash`);
|
|
130
94
|
if (prErrors > 0) errorParts.push(`${prErrors} Prettier`);
|
|
131
|
-
yield* Effect.logWarning(
|
|
95
|
+
yield* Effect.logWarning(`${totalErrors} error(s) in code blocks (${errorParts.join(", ")})`);
|
|
132
96
|
}
|
|
133
97
|
});
|
|
134
98
|
|
|
135
99
|
//#endregion
|
|
136
|
-
export {
|
|
100
|
+
export { buildEventBus, logBuildSummary, makeSummaryLoggerLayer };
|
|
@@ -1,20 +1,134 @@
|
|
|
1
|
+
import { PluginEvent } from "../observability/events.js";
|
|
2
|
+
import { emit } from "../observability/EventBus.js";
|
|
3
|
+
import { resolveExternalPackageVersions } from "../config-utils.js";
|
|
1
4
|
import { TypeRegistryError } from "../errors.js";
|
|
2
5
|
import { TypeRegistryService } from "../services/TypeRegistryService.js";
|
|
3
6
|
import { Effect, Layer } from "effect";
|
|
4
|
-
import { PackageSpec, TypeRegistry } from "type-registry-effect";
|
|
5
|
-
import { NodeLayer
|
|
7
|
+
import { PackageSpec, RegistryEvent, TypeRegistry, TypeRegistryObserver } from "type-registry-effect";
|
|
8
|
+
import { NodeLayer } from "type-registry-effect/node";
|
|
6
9
|
|
|
7
10
|
//#region src/layers/TypeRegistryServiceLive.ts
|
|
8
11
|
/**
|
|
9
|
-
*
|
|
12
|
+
* Forward type-registry-effect's typed `RegistryEvent`s to the plugin's Effect
|
|
13
|
+
* logger. Since v1 the library emits no logs of its own — observers are the only
|
|
14
|
+
* diagnostic surface — so this restores the build output and routes it through
|
|
15
|
+
* the plugin's configured log level/format (a single source, no duplication).
|
|
10
16
|
*
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
17
|
+
* The summary (`BatchComplete`) and failures are surfaced at info/warning;
|
|
18
|
+
* per-package detail stays at debug so a normal build is quiet.
|
|
19
|
+
*/
|
|
20
|
+
const RegistryObserverLayer = Layer.succeed(TypeRegistryObserver, { emit: (event) => RegistryEvent.$match(event, {
|
|
21
|
+
VersionResolved: ({ package: pkg, requested, resolved }) => emit(PluginEvent.TypeRegistryEvent({
|
|
22
|
+
ctx: {
|
|
23
|
+
buildId: "",
|
|
24
|
+
packageName: pkg
|
|
25
|
+
},
|
|
26
|
+
level: "debug",
|
|
27
|
+
kind: "VersionResolved",
|
|
28
|
+
detail: `${requested} -> ${resolved}`
|
|
29
|
+
})),
|
|
30
|
+
VersionResolveFailed: ({ package: pkg, requested, reason }) => emit(PluginEvent.TypeRegistryEvent({
|
|
31
|
+
ctx: {
|
|
32
|
+
buildId: "",
|
|
33
|
+
packageName: pkg
|
|
34
|
+
},
|
|
35
|
+
level: "debug",
|
|
36
|
+
kind: "VersionResolveFailed",
|
|
37
|
+
detail: `${requested}: ${reason}`
|
|
38
|
+
})),
|
|
39
|
+
CacheHit: ({ package: pkg, version }) => emit(PluginEvent.TypeRegistryEvent({
|
|
40
|
+
ctx: {
|
|
41
|
+
buildId: "",
|
|
42
|
+
packageName: pkg,
|
|
43
|
+
version
|
|
44
|
+
},
|
|
45
|
+
level: "debug",
|
|
46
|
+
kind: "CacheHit",
|
|
47
|
+
detail: ""
|
|
48
|
+
})),
|
|
49
|
+
CacheStale: ({ package: pkg, version }) => emit(PluginEvent.TypeRegistryEvent({
|
|
50
|
+
ctx: {
|
|
51
|
+
buildId: "",
|
|
52
|
+
packageName: pkg,
|
|
53
|
+
version
|
|
54
|
+
},
|
|
55
|
+
level: "debug",
|
|
56
|
+
kind: "CacheStale",
|
|
57
|
+
detail: ""
|
|
58
|
+
})),
|
|
59
|
+
CacheMiss: ({ package: pkg, version }) => emit(PluginEvent.TypeRegistryEvent({
|
|
60
|
+
ctx: {
|
|
61
|
+
buildId: "",
|
|
62
|
+
packageName: pkg,
|
|
63
|
+
version
|
|
64
|
+
},
|
|
65
|
+
level: "debug",
|
|
66
|
+
kind: "CacheMiss",
|
|
67
|
+
detail: ""
|
|
68
|
+
})),
|
|
69
|
+
FetchStart: ({ package: pkg, version }) => emit(PluginEvent.TypeRegistryEvent({
|
|
70
|
+
ctx: {
|
|
71
|
+
buildId: "",
|
|
72
|
+
packageName: pkg,
|
|
73
|
+
version
|
|
74
|
+
},
|
|
75
|
+
level: "debug",
|
|
76
|
+
kind: "FetchStart",
|
|
77
|
+
detail: ""
|
|
78
|
+
})),
|
|
79
|
+
FetchFailed: ({ url, status, bodySnippet }) => emit(PluginEvent.TypeRegistryEvent({
|
|
80
|
+
ctx: { buildId: "" },
|
|
81
|
+
level: "debug",
|
|
82
|
+
kind: "FetchFailed",
|
|
83
|
+
detail: `HTTP ${status}: ${url}${bodySnippet ? ` — ${bodySnippet}` : ""}`
|
|
84
|
+
})),
|
|
85
|
+
PackageLoaded: ({ package: pkg, version, files, source }) => emit(PluginEvent.TypeRegistryEvent({
|
|
86
|
+
ctx: {
|
|
87
|
+
buildId: "",
|
|
88
|
+
packageName: pkg,
|
|
89
|
+
version
|
|
90
|
+
},
|
|
91
|
+
level: "debug",
|
|
92
|
+
kind: "PackageLoaded",
|
|
93
|
+
detail: `${files} files, ${source}`
|
|
94
|
+
})),
|
|
95
|
+
PackageLoadFailed: ({ package: pkg, version, kind, message }) => emit(PluginEvent.TypeRegistryEvent({
|
|
96
|
+
ctx: {
|
|
97
|
+
buildId: "",
|
|
98
|
+
packageName: pkg,
|
|
99
|
+
version
|
|
100
|
+
},
|
|
101
|
+
level: "warn",
|
|
102
|
+
kind: "PackageLoadFailed",
|
|
103
|
+
detail: `[${kind}] ${message}`
|
|
104
|
+
})),
|
|
105
|
+
BatchStart: ({ total }) => emit(PluginEvent.TypeRegistryEvent({
|
|
106
|
+
ctx: { buildId: "" },
|
|
107
|
+
level: "debug",
|
|
108
|
+
kind: "BatchStart",
|
|
109
|
+
detail: `${total} package(s)`
|
|
110
|
+
})),
|
|
111
|
+
BatchComplete: ({ loaded, total, totalFiles, durationMs }) => emit(PluginEvent.TypeRegistryEvent({
|
|
112
|
+
ctx: { buildId: "" },
|
|
113
|
+
level: "info",
|
|
114
|
+
kind: "BatchComplete",
|
|
115
|
+
detail: `${loaded}/${total} packages, ${totalFiles} files, ${durationMs}ms`
|
|
116
|
+
}))
|
|
117
|
+
}) });
|
|
118
|
+
/**
|
|
119
|
+
* type-registry-effect runtime: the Node platform layer plus the observer that
|
|
120
|
+
* forwards registry events to the plugin logger.
|
|
13
121
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
122
|
+
* `NodeLayer` provides CacheService, PackageFetcher, and TypeResolver with
|
|
123
|
+
* Node.js platform implementations (FileSystem, HttpClient). Built-in metrics
|
|
124
|
+
* (packagesLoaded, packagesFailed, cacheHits, etc.) are tracked by the library.
|
|
125
|
+
*/
|
|
126
|
+
const RegistryLayer = Layer.merge(NodeLayer, RegistryObserverLayer);
|
|
127
|
+
/**
|
|
128
|
+
* TypeRegistryServiceLive: uses type-registry-effect Effect programs directly.
|
|
16
129
|
*/
|
|
17
130
|
const TypeRegistryServiceLive = Layer.succeed(TypeRegistryService, {
|
|
131
|
+
resolveVersions: (packages) => resolveExternalPackageVersions(packages, (pkg) => TypeRegistry.resolveVersion(pkg.name, pkg.version)).pipe(Effect.provide(RegistryLayer)),
|
|
18
132
|
loadPackages: (packages) => Effect.gen(function* () {
|
|
19
133
|
if (packages.length === 0) return { vfs: /* @__PURE__ */ new Map() };
|
|
20
134
|
const specs = packages.map((pkg) => new PackageSpec({
|
|
@@ -26,20 +140,7 @@ const TypeRegistryServiceLive = Layer.succeed(TypeRegistryService, {
|
|
|
26
140
|
version: packages.map((p) => p.version).join(", "),
|
|
27
141
|
reason: error.message ?? String(error)
|
|
28
142
|
})))) };
|
|
29
|
-
}).pipe(Effect.provide(
|
|
30
|
-
createTypeScriptCache: (packages, compilerOptions) => Effect.tryPromise({
|
|
31
|
-
try: () => {
|
|
32
|
-
return createTypeScriptCache(packages.map((pkg) => new PackageSpec({
|
|
33
|
-
name: pkg.name,
|
|
34
|
-
version: pkg.version
|
|
35
|
-
})), compilerOptions);
|
|
36
|
-
},
|
|
37
|
-
catch: (error) => new TypeRegistryError({
|
|
38
|
-
packageName: packages.map((p) => p.name).join(", "),
|
|
39
|
-
version: "",
|
|
40
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
41
|
-
})
|
|
42
|
-
})
|
|
143
|
+
}).pipe(Effect.provide(RegistryLayer))
|
|
43
144
|
});
|
|
44
145
|
|
|
45
146
|
//#endregion
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Metric, MetricBoundaries } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/layers/build-metrics.ts
|
|
4
|
+
/**
|
|
5
|
+
* All build metrics as named counters/histograms.
|
|
6
|
+
*
|
|
7
|
+
* Note: Effect Metrics use a process-wide registry. In tests, counters
|
|
8
|
+
* accumulate across test cases within the same process. Test assertions
|
|
9
|
+
* should use loose matching (toContain) rather than exact count checks.
|
|
10
|
+
*
|
|
11
|
+
* Extracted into its own module so that `metrics-sink.ts` can import it
|
|
12
|
+
* without creating a circular dependency through `ObservabilityLive.ts`
|
|
13
|
+
* (which itself imports `metrics-sink.ts`).
|
|
14
|
+
*/
|
|
15
|
+
const BuildMetrics = {
|
|
16
|
+
filesTotal: Metric.counter("files.total"),
|
|
17
|
+
filesNew: Metric.counter("files.new"),
|
|
18
|
+
filesModified: Metric.counter("files.modified"),
|
|
19
|
+
filesUnchanged: Metric.counter("files.unchanged"),
|
|
20
|
+
codeblockDuration: Metric.histogram("codeblock.duration", MetricBoundaries.fromIterable([
|
|
21
|
+
10,
|
|
22
|
+
25,
|
|
23
|
+
50,
|
|
24
|
+
100,
|
|
25
|
+
200,
|
|
26
|
+
500,
|
|
27
|
+
1e3
|
|
28
|
+
])),
|
|
29
|
+
codeblockShikiDuration: Metric.histogram("codeblock.shiki.duration", MetricBoundaries.fromIterable([
|
|
30
|
+
5,
|
|
31
|
+
10,
|
|
32
|
+
25,
|
|
33
|
+
50,
|
|
34
|
+
100,
|
|
35
|
+
250
|
|
36
|
+
])),
|
|
37
|
+
codeblockTotal: Metric.counter("codeblock.total"),
|
|
38
|
+
codeblockSlow: Metric.counter("codeblock.slow"),
|
|
39
|
+
twoslashErrors: Metric.counter("twoslash.errors"),
|
|
40
|
+
prettierErrors: Metric.counter("prettier.errors"),
|
|
41
|
+
pagesGenerated: Metric.counter("pages.generated"),
|
|
42
|
+
apiVersionsLoaded: Metric.counter("api.versions.loaded"),
|
|
43
|
+
externalPackagesTotal: Metric.counter("external.packages.total"),
|
|
44
|
+
phaseDuration: Metric.histogram("phase.duration", MetricBoundaries.fromIterable([
|
|
45
|
+
50,
|
|
46
|
+
100,
|
|
47
|
+
250,
|
|
48
|
+
500,
|
|
49
|
+
1e3,
|
|
50
|
+
2500,
|
|
51
|
+
5e3,
|
|
52
|
+
1e4
|
|
53
|
+
])),
|
|
54
|
+
vfsFiles: Metric.counter("vfs.files"),
|
|
55
|
+
importsPrepended: Metric.counter("imports.prepended"),
|
|
56
|
+
twoslashDiagnostics: Metric.counter("twoslash.diagnostics"),
|
|
57
|
+
configDefaultsApplied: Metric.counter("config.defaults.applied")
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { BuildMetrics };
|
package/llms-program.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PluginEvent } from "./observability/events.js";
|
|
2
|
+
import { emit } from "./observability/EventBus.js";
|
|
1
3
|
import { filterLlmsFullTxt, filterLlmsTxt, generatePackageLlmsFullTxt, generatePackageLlmsTxt, generateStructuredLlmsTxt, parseLlmsTxtLine } from "./llms-processing.js";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { Effect } from "effect";
|
|
@@ -173,19 +175,24 @@ function collectApiPageContent(globalLlmsFullContent, result) {
|
|
|
173
175
|
function processLlmsFiles(input) {
|
|
174
176
|
return Effect.gen(function* () {
|
|
175
177
|
const fs = yield* FileSystem.FileSystem;
|
|
176
|
-
const { outDir, buildResults, llmsPlugin, packageRoutes } = input;
|
|
178
|
+
const { outDir, buildResults, llmsPlugin, packageRoutes, buildId } = input;
|
|
179
|
+
const ctx = { buildId };
|
|
177
180
|
if (buildResults.length === 0) return;
|
|
178
181
|
const apiRoutes = buildApiRoutes(buildResults);
|
|
179
|
-
yield*
|
|
182
|
+
yield* emit(PluginEvent.LlmsRoutesBuilt({
|
|
183
|
+
ctx,
|
|
184
|
+
level: "debug",
|
|
185
|
+
count: apiRoutes.size
|
|
186
|
+
}));
|
|
180
187
|
if (apiRoutes.size === 0) return;
|
|
181
188
|
const prefixes = discoverPrefixes(buildResults);
|
|
182
|
-
yield* Effect.forEach([...prefixes], (prefix) => processPrefix(fs, outDir, prefix, buildResults, apiRoutes, llmsPlugin, packageRoutes), { concurrency: "unbounded" });
|
|
189
|
+
yield* Effect.forEach([...prefixes], (prefix) => processPrefix(fs, outDir, prefix, buildResults, apiRoutes, llmsPlugin, packageRoutes, ctx), { concurrency: "unbounded" });
|
|
183
190
|
});
|
|
184
191
|
}
|
|
185
192
|
/**
|
|
186
193
|
* Process global and per-package LLMs files for a single prefix.
|
|
187
194
|
*/
|
|
188
|
-
function processPrefix(fs, outDir, prefix, buildResults, apiRoutes, llmsPlugin, packageRoutes) {
|
|
195
|
+
function processPrefix(fs, outDir, prefix, buildResults, apiRoutes, llmsPlugin, packageRoutes, ctx) {
|
|
189
196
|
return Effect.gen(function* () {
|
|
190
197
|
const prefixDir = prefix ? path.join(outDir, prefix) : outDir;
|
|
191
198
|
const llmsTxtPath = path.join(prefixDir, "llms.txt");
|
|
@@ -216,19 +223,25 @@ function processPrefix(fs, outDir, prefix, buildResults, apiRoutes, llmsPlugin,
|
|
|
216
223
|
}
|
|
217
224
|
if (llmsPlugin.scopes) {
|
|
218
225
|
const prefixResults = prefix === "" ? [...buildResults] : buildResults.filter((r) => r.baseRoute.startsWith(`/${prefix}/`));
|
|
219
|
-
yield* Effect.forEach(prefixResults, (result) => generatePerPackageFiles(fs, outDir, result, llmsTxtContent, llmsFullTxtContent, apiRoutes, llmsPlugin, packageRoutes.get(result.packageName) ?? result.baseRoute), { concurrency: "unbounded" });
|
|
226
|
+
yield* Effect.forEach(prefixResults, (result) => generatePerPackageFiles(fs, outDir, result, llmsTxtContent, llmsFullTxtContent, apiRoutes, llmsPlugin, packageRoutes.get(result.packageName) ?? result.baseRoute, ctx), { concurrency: "unbounded" });
|
|
220
227
|
}
|
|
228
|
+
yield* emit(PluginEvent.LlmsPrefixProcessed({
|
|
229
|
+
ctx,
|
|
230
|
+
level: "debug",
|
|
231
|
+
prefix
|
|
232
|
+
}));
|
|
221
233
|
});
|
|
222
234
|
}
|
|
223
235
|
/**
|
|
224
236
|
* Generate per-package LLMs files (llms.txt, llms-full.txt, llms-docs.txt, llms-api.txt).
|
|
225
237
|
*/
|
|
226
|
-
function generatePerPackageFiles(fs, outDir, result, globalLlmsTxtContent, globalLlmsFullContent, apiRoutes, llmsPlugin, packageRoute) {
|
|
238
|
+
function generatePerPackageFiles(fs, outDir, result, globalLlmsTxtContent, globalLlmsFullContent, apiRoutes, llmsPlugin, packageRoute, ctx) {
|
|
227
239
|
return Effect.gen(function* () {
|
|
228
240
|
const pkgRouteSegment = packageRoute.replace(/^\//, "");
|
|
229
241
|
const packageLlmsDir = pkgRouteSegment ? path.join(outDir, pkgRouteSegment) : outDir;
|
|
230
242
|
yield* fs.makeDirectory(packageLlmsDir, { recursive: true }).pipe(Effect.orDie);
|
|
231
243
|
const displayName = result.apiName ?? result.packageName;
|
|
244
|
+
const writtenFiles = [];
|
|
232
245
|
const apiEntries = collectApiEntries(globalLlmsTxtContent, result);
|
|
233
246
|
const guideEntries = collectGuideEntries(globalLlmsTxtContent, apiRoutes, packageRoute);
|
|
234
247
|
const packageLlmsTxt = generatePackageLlmsTxt({
|
|
@@ -238,6 +251,7 @@ function generatePerPackageFiles(fs, outDir, result, globalLlmsTxtContent, globa
|
|
|
238
251
|
apiPages: apiEntries
|
|
239
252
|
});
|
|
240
253
|
yield* fs.writeFileString(path.join(packageLlmsDir, "llms.txt"), packageLlmsTxt).pipe(Effect.orDie);
|
|
254
|
+
writtenFiles.push("llms.txt");
|
|
241
255
|
const apiPageContent = collectApiPageContent(globalLlmsFullContent, result);
|
|
242
256
|
const guideRouteUrls = new Set(guideEntries.map((e) => e.url));
|
|
243
257
|
const guidePageContent = globalLlmsFullContent ? extractSections(globalLlmsFullContent, (url) => guideRouteUrls.has(url)) : [];
|
|
@@ -245,16 +259,24 @@ function generatePerPackageFiles(fs, outDir, result, globalLlmsTxtContent, globa
|
|
|
245
259
|
if (fullPageContent.length > 0) {
|
|
246
260
|
const packageLlmsFullTxt = generatePackageLlmsFullTxt(fullPageContent);
|
|
247
261
|
yield* fs.writeFileString(path.join(packageLlmsDir, "llms-full.txt"), packageLlmsFullTxt).pipe(Effect.orDie);
|
|
262
|
+
writtenFiles.push("llms-full.txt");
|
|
248
263
|
}
|
|
249
264
|
if (llmsPlugin.apiTxt && apiPageContent.length > 0) {
|
|
250
265
|
const apiTxtContent = generatePackageLlmsFullTxt(apiPageContent);
|
|
251
266
|
yield* fs.writeFileString(path.join(packageLlmsDir, "llms-api.txt"), apiTxtContent).pipe(Effect.orDie);
|
|
267
|
+
writtenFiles.push("llms-api.txt");
|
|
252
268
|
}
|
|
253
269
|
if (guidePageContent.length > 0) {
|
|
254
270
|
const docsTxtContent = generatePackageLlmsFullTxt(guidePageContent);
|
|
255
271
|
yield* fs.writeFileString(path.join(packageLlmsDir, "llms-docs.txt"), docsTxtContent).pipe(Effect.orDie);
|
|
272
|
+
writtenFiles.push("llms-docs.txt");
|
|
256
273
|
}
|
|
257
|
-
yield*
|
|
274
|
+
yield* emit(PluginEvent.LlmsPackageFilesGenerated({
|
|
275
|
+
ctx,
|
|
276
|
+
level: "debug",
|
|
277
|
+
dir: packageLlmsDir,
|
|
278
|
+
files: writtenFiles
|
|
279
|
+
}));
|
|
258
280
|
});
|
|
259
281
|
}
|
|
260
282
|
|
package/loader.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { PluginEvent } from "./observability/events.js";
|
|
1
2
|
import { ApiDocumentedItem, ApiItemKind } from "@microsoft/api-extractor-model";
|
|
2
3
|
import { extractPlainText, getDeprecation, getExamples, getParams, getReleaseTag, getReturns, getSummary, hasModifierTag } from "api-extractor-llms";
|
|
3
4
|
|
|
4
5
|
//#region src/loader.ts
|
|
6
|
+
/** Module-level emitter injected by plugin.ts at startup. */
|
|
7
|
+
let emitEvent = () => {};
|
|
8
|
+
let currentBuildId = "";
|
|
9
|
+
function setLoaderEventEmitter(fn, buildId = "") {
|
|
10
|
+
emitEvent = fn;
|
|
11
|
+
currentBuildId = buildId;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* Parser for extracting and analyzing information from API Extractor models and TSDoc comments
|
|
7
15
|
*/
|
|
@@ -53,7 +61,13 @@ var ApiParser = class ApiParser {
|
|
|
53
61
|
break;
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
|
-
if (!categorized && typeof process !== "undefined" && !process.env.VITEST)
|
|
64
|
+
if (!categorized && typeof process !== "undefined" && !process.env.VITEST) emitEvent(PluginEvent.ItemSkipped({
|
|
65
|
+
ctx: { buildId: currentBuildId },
|
|
66
|
+
item: member.displayName,
|
|
67
|
+
kind: String(member.kind),
|
|
68
|
+
reason: "uncategorized",
|
|
69
|
+
level: "warn"
|
|
70
|
+
}));
|
|
57
71
|
}
|
|
58
72
|
return items;
|
|
59
73
|
}
|
|
@@ -183,4 +197,4 @@ var ApiParser = class ApiParser {
|
|
|
183
197
|
};
|
|
184
198
|
|
|
185
199
|
//#endregion
|
|
186
|
-
export { ApiParser };
|
|
200
|
+
export { ApiParser, setLoaderEventEmitter };
|
package/markdown/shiki-utils.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import { PluginEvent } from "../observability/events.js";
|
|
2
|
+
|
|
1
3
|
//#region src/markdown/shiki-utils.ts
|
|
4
|
+
/** Module-level emitter injected by plugin.ts at startup. */
|
|
5
|
+
let emitEvent = () => {};
|
|
6
|
+
let currentBuildId = "";
|
|
7
|
+
function setShikiUtilsEventEmitter(fn, buildId = "") {
|
|
8
|
+
emitEvent = fn;
|
|
9
|
+
currentBuildId = buildId;
|
|
10
|
+
}
|
|
2
11
|
/**
|
|
3
12
|
* Default Shiki theme configuration
|
|
4
13
|
*/
|
|
@@ -39,10 +48,15 @@ async function generateShikiHast(code, highlighter, transformers, enableTwoslash
|
|
|
39
48
|
if (enableTwoslash) options.meta = { __raw: "twoslash" };
|
|
40
49
|
return await highlighter.codeToHast(code, options);
|
|
41
50
|
} catch (error) {
|
|
42
|
-
|
|
51
|
+
emitEvent(PluginEvent.ShikiError({
|
|
52
|
+
ctx: { buildId: currentBuildId },
|
|
53
|
+
file: "unknown",
|
|
54
|
+
reason: String(error),
|
|
55
|
+
level: "warn"
|
|
56
|
+
}));
|
|
43
57
|
return null;
|
|
44
58
|
}
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
//#endregion
|
|
48
|
-
export { DEFAULT_SHIKI_THEMES, generateShikiHast };
|
|
62
|
+
export { DEFAULT_SHIKI_THEMES, generateShikiHast, setShikiUtilsEventEmitter };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { LEVEL_RANK, levelOf } from "./events.js";
|
|
2
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/observability/EventBus.ts
|
|
5
|
+
var EventBus = class extends Context.Tag("rspress-plugin-api-extractor/EventBus")() {};
|
|
6
|
+
function makeShape(sinks) {
|
|
7
|
+
const maxAdmitted = sinks.filter((s) => s.capturesPayload === true).reduce((max, s) => Math.max(max, LEVEL_RANK[s.minLevel]), -1);
|
|
8
|
+
return {
|
|
9
|
+
emit: (event) => Effect.sync(() => {
|
|
10
|
+
const rank = LEVEL_RANK[levelOf(event)];
|
|
11
|
+
for (const sink of sinks) if (rank <= LEVEL_RANK[sink.minLevel]) sink.handle(event);
|
|
12
|
+
}),
|
|
13
|
+
wantsLevel: (level) => Effect.succeed(LEVEL_RANK[level] <= maxAdmitted)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function makeEventBusLayer(sinks) {
|
|
17
|
+
return Layer.succeed(EventBus, makeShape(sinks));
|
|
18
|
+
}
|
|
19
|
+
/** No sinks: every emit is a no-op, wantsLevel always false. */
|
|
20
|
+
const EventBusNoop = makeEventBusLayer([]);
|
|
21
|
+
/** Emit when a bus is in context; silently no-op otherwise. */
|
|
22
|
+
function emit(event) {
|
|
23
|
+
return Effect.serviceOption(EventBus).pipe(Effect.flatMap((maybe) => Option.isSome(maybe) ? maybe.value.emit(event) : Effect.void));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns true when a bus is in context and has at least one sink admitted at
|
|
27
|
+
* `level`; false otherwise. R = never, safe to use anywhere emit is used.
|
|
28
|
+
*/
|
|
29
|
+
function wantsLevel(level) {
|
|
30
|
+
return Effect.serviceOption(EventBus).pipe(Effect.flatMap((maybe) => Option.isSome(maybe) ? maybe.value.wantsLevel(level) : Effect.succeed(false)));
|
|
31
|
+
}
|
|
32
|
+
/** Bind a runtime so non-Effect (sync island) callbacks can emit. */
|
|
33
|
+
function makeRuntimeEmitter(runtime) {
|
|
34
|
+
return (event) => runtime.runSync(emit(event));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { emit, makeEventBusLayer, makeRuntimeEmitter, wantsLevel };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/observability/events.ts
|
|
4
|
+
const LEVEL_RANK = {
|
|
5
|
+
error: 0,
|
|
6
|
+
warn: 1,
|
|
7
|
+
info: 2,
|
|
8
|
+
debug: 3,
|
|
9
|
+
trace: 4
|
|
10
|
+
};
|
|
11
|
+
const PluginEvent = Data.taggedEnum();
|
|
12
|
+
function levelOf(event) {
|
|
13
|
+
return event.level;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { LEVEL_RANK, PluginEvent, levelOf };
|