rspress-plugin-api-extractor 0.2.2 → 0.3.1

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.
Files changed (51) hide show
  1. package/api-extracted-package.js +2 -1
  2. package/build-program.js +20 -12
  3. package/build-stages.js +124 -30
  4. package/config-utils.js +36 -7
  5. package/content-hash.js +1 -1
  6. package/errors.js +1 -1
  7. package/index.d.ts +329 -202
  8. package/layers/ConfigServiceLive.js +300 -136
  9. package/layers/ObservabilityLive.js +49 -85
  10. package/layers/TypeRegistryServiceLive.js +122 -21
  11. package/layers/build-metrics.js +61 -0
  12. package/llms-program.js +29 -7
  13. package/loader.js +16 -2
  14. package/markdown/helpers.js +1 -1
  15. package/markdown/index.js +1 -1
  16. package/markdown/shiki-utils.js +16 -2
  17. package/observability/EventBus.js +38 -0
  18. package/observability/events.js +17 -0
  19. package/observability/sinks/console-sink.js +63 -0
  20. package/observability/sinks/metrics-sink.js +68 -0
  21. package/observability/sinks/trace-sink.js +38 -0
  22. package/observability/spans.js +57 -0
  23. package/og-resolver.js +37 -5
  24. package/package.json +6 -6
  25. package/plugin.js +73 -19
  26. package/prettier-formatter.js +15 -4
  27. package/remark-api-codeblocks.js +22 -3
  28. package/remark-with-api.js +27 -14
  29. package/route-collisions.js +1 -1
  30. package/runtime/components/ApiExample/index.js +5 -5
  31. package/runtime/components/ApiMember/index.js +5 -7
  32. package/runtime/components/ApiSignature/index.js +4 -6
  33. package/runtime/components/EnumMembersTable/index.js +5 -0
  34. package/runtime/components/ExampleBlock/index.js +5 -3
  35. package/runtime/components/MemberSignature/index.js +4 -2
  36. package/runtime/components/ParametersTable/index.js +5 -0
  37. package/runtime/components/SignatureBlock/index.js +4 -2
  38. package/runtime/components/shared/variables.css +0 -15
  39. package/runtime/index.d.ts +65 -399
  40. package/runtime/index.js +1 -5
  41. package/runtime/utils/hast-renderer.js +1 -0
  42. package/schemas/config.js +105 -4
  43. package/schemas/index.js +3 -2
  44. package/schemas/observability.js +62 -0
  45. package/schemas/opengraph.js +30 -0
  46. package/schemas/performance.js +1 -1
  47. package/serve.js +13 -0
  48. package/tsconfig-parser.js +1 -1
  49. package/twoslash-patterns.js +1 -1
  50. package/twoslash-transformer.js +93 -8
  51. package/typescript-config.js +1 -1
@@ -129,7 +129,8 @@ var ApiExtractedPackage = class ApiExtractedPackage extends VirtualPackageClass
129
129
  const jsDoc = this.formatJSDoc(apiFunction);
130
130
  if (jsDoc) lines.push(jsDoc);
131
131
  const cleaned = this.cleanExcerpt(apiFunction.excerpt.text);
132
- lines.push(`export declare ${cleaned};`);
132
+ const decl = cleaned.startsWith("function ") ? cleaned : `const ${cleaned}`;
133
+ lines.push(`export declare ${decl};`);
133
134
  return lines.join("\n");
134
135
  }
135
136
  generateEnumDeclaration(apiEnum) {
package/build-program.js CHANGED
@@ -2,6 +2,7 @@ import { markdownCrossLinker } from "./markdown/cross-linker.js";
2
2
  import "./markdown/index.js";
3
3
  import { SnapshotService } from "./services/SnapshotService.js";
4
4
  import { buildPipelineForApi, cleanupAndCommit, prepareWorkItems, writeMetadata } from "./build-stages.js";
5
+ import { withPhase } from "./observability/spans.js";
5
6
  import { TwoslashManager } from "./twoslash-transformer.js";
6
7
  import { VfsRegistry } from "./vfs-registry.js";
7
8
  import path from "node:path";
@@ -28,18 +29,22 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
28
29
  const snapshotSvc = yield* SnapshotService;
29
30
  const { apiPackage, packageName, apiName, outputDir, baseRoute, categories, source, packageJson, llmsPlugin, siteUrl, ogImage } = apiConfig;
30
31
  const suppressExampleErrors = apiConfig.suppressExampleErrors ?? true;
31
- const { shikiCrossLinker, highlighter, hideCutTransformer, hideCutLinesTransformer, twoslashTransformer, ogResolver, pageConcurrency } = buildContext;
32
+ const { shikiCrossLinker, highlighter, hideCutTransformer, hideCutLinesTransformer, twoslashTransformer, ogResolver, pageConcurrency, thresholds, buildId } = buildContext;
33
+ const phaseCtx = {
34
+ buildId,
35
+ packageName
36
+ };
32
37
  const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
33
38
  const buildTime = (/* @__PURE__ */ new Date()).toISOString();
34
39
  const allSnapshots = yield* snapshotSvc.getAllForDirectory(resolvedOutputDir).pipe(Effect.orDie);
35
40
  const existingSnapshots = new Map(allSnapshots.map((s) => [s.filePath, s]));
36
41
  yield* fileSystem.makeDirectory(resolvedOutputDir, { recursive: true }).pipe(Effect.orDie);
37
- const { workItems, crossLinkData } = prepareWorkItems({
42
+ const { workItems, crossLinkData } = yield* withPhase("resolve", phaseCtx, Effect.sync(() => prepareWorkItems({
38
43
  apiPackage,
39
44
  categories,
40
45
  baseRoute,
41
46
  packageName
42
- });
47
+ })), thresholds);
43
48
  markdownCrossLinker.setRoutes(crossLinkData.routes);
44
49
  const apiScope = baseRoute.replace(/^\//, "").split("/")[0] || packageName;
45
50
  shikiCrossLinker.reinitialize(crossLinkData.routes, crossLinkData.kinds, apiScope);
@@ -58,8 +63,9 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
58
63
  if (apiConfig.theme != null) vfsConfig.theme = apiConfig.theme;
59
64
  VfsRegistry.register(apiScope, vfsConfig);
60
65
  }
61
- yield* Effect.logInfo(`Generating ${workItems.length} pages across ${Object.keys(categories).length} categories in parallel`);
62
- const fileResults = yield* buildPipelineForApi({
66
+ yield* Effect.logDebug(`Generating ${workItems.length} pages across ${Object.keys(categories).length} categories in parallel`);
67
+ const fileResults = yield* withPhase("generate", phaseCtx, buildPipelineForApi({
68
+ buildId,
63
69
  workItems,
64
70
  baseRoute,
65
71
  packageName,
@@ -75,9 +81,9 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
75
81
  ...ogResolver !== void 0 ? { ogResolver } : {},
76
82
  ...siteUrl != null ? { siteUrl } : {},
77
83
  ...ogImage != null ? { ogImage } : {}
78
- });
84
+ }), thresholds);
79
85
  const changedCount = fileResults.filter((r) => r.status !== "unchanged").length;
80
- yield* Effect.logInfo(`Generated ${changedCount} pages`);
86
+ yield* Effect.logDebug(`Generated ${changedCount} pages`);
81
87
  const generatedFiles = /* @__PURE__ */ new Set();
82
88
  for (const r of fileResults) {
83
89
  generatedFiles.add(r.relativePathWithExt);
@@ -86,7 +92,8 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
86
92
  if (packageJson?.version != null) ctx.version = packageJson.version;
87
93
  fileContextMap.set(r.absolutePath, ctx);
88
94
  }
89
- yield* writeMetadata({
95
+ yield* withPhase("write", phaseCtx, writeMetadata({
96
+ buildId,
90
97
  fileResults,
91
98
  categories,
92
99
  resolvedOutputDir,
@@ -96,13 +103,14 @@ function generateApiDocs(apiConfig, buildContext, fileContextMap) {
96
103
  packageName,
97
104
  ...apiName != null ? { apiName } : {},
98
105
  generatedFiles
99
- });
100
- yield* cleanupAndCommit({
106
+ }), thresholds);
107
+ yield* withPhase("cleanup", phaseCtx, cleanupAndCommit({
108
+ buildId,
101
109
  fileResults,
102
110
  resolvedOutputDir,
103
111
  generatedFiles
104
- });
105
- yield* Effect.logInfo(`Generated ${changedCount} API documentation files for ${packageName}`);
112
+ }), thresholds);
113
+ yield* Effect.logDebug(`Generated ${changedCount} API documentation files for ${packageName}`);
106
114
  return {
107
115
  crossLinkData,
108
116
  generatedFiles,
package/build-stages.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { hashContent, hashFrontmatter } from "./content-hash.js";
2
- import { BuildMetrics } from "./layers/ObservabilityLive.js";
2
+ import { PluginEvent } from "./observability/events.js";
3
+ import { emit } from "./observability/EventBus.js";
4
+ import { BuildMetrics } from "./layers/build-metrics.js";
5
+ import "./layers/ObservabilityLive.js";
3
6
  import { ApiParser } from "./loader.js";
4
7
  import { generateFrontmatter } from "./markdown/helpers.js";
5
8
  import { ClassPageGenerator } from "./markdown/page-generators/class-page.js";
@@ -187,8 +190,9 @@ function normalizeMarkdownSpacing(content) {
187
190
  function generateSinglePage(workItem, ctx) {
188
191
  return Effect.gen(function* () {
189
192
  const fileSystem = yield* FileSystem.FileSystem;
190
- const { existingSnapshots, baseRoute, packageName, apiScope, apiName, source, buildTime, resolvedOutputDir, suppressExampleErrors, llmsPlugin } = ctx;
193
+ const { buildId, existingSnapshots, baseRoute, packageName, apiScope, apiName, source, buildTime, resolvedOutputDir, suppressExampleErrors, llmsPlugin } = ctx;
191
194
  const { item, categoryConfig, namespaceMember } = workItem;
195
+ const pageGenStart = performance.now();
192
196
  let page = null;
193
197
  switch (item.kind) {
194
198
  case ApiItemKind.Class: {
@@ -255,7 +259,17 @@ function generateSinglePage(workItem, ctx) {
255
259
  break;
256
260
  }
257
261
  default:
258
- yield* Effect.logDebug(`Skipping item "${item.displayName}" with unsupported kind: ${item.kind} (${ApiItemKind[item.kind] || "unknown"}) in category "${categoryConfig.displayName}"`);
262
+ yield* emit(PluginEvent.ItemSkipped({
263
+ ctx: {
264
+ buildId,
265
+ packageName,
266
+ apiScope
267
+ },
268
+ item: item.displayName,
269
+ kind: String(item.kind),
270
+ reason: "unsupported kind",
271
+ level: "trace"
272
+ }));
259
273
  return null;
260
274
  }
261
275
  if (!page) return null;
@@ -267,7 +281,20 @@ function generateSinglePage(workItem, ctx) {
267
281
  content: page.content
268
282
  };
269
283
  }
270
- yield* Metric.increment(BuildMetrics.pagesGenerated);
284
+ const codeblockCount = (page.content.match(/<(ApiSignature|ApiMember|ApiExample)\b/g) ?? []).length;
285
+ yield* emit(PluginEvent.PageGenerated({
286
+ ctx: {
287
+ buildId,
288
+ packageName,
289
+ apiScope,
290
+ route: page.routePath
291
+ },
292
+ item: item.displayName,
293
+ category: categoryConfig.displayName,
294
+ codeblockCount,
295
+ durationMs: Math.round(performance.now() - pageGenStart),
296
+ level: "debug"
297
+ }));
271
298
  const parsed = matter(page.content);
272
299
  const bodyContent = normalizeMarkdownSpacing(parsed.content);
273
300
  const frontmatterData = parsed.data;
@@ -331,7 +358,7 @@ function generateSinglePage(workItem, ctx) {
331
358
  function writeSingleFile(result, ctx) {
332
359
  return Effect.gen(function* () {
333
360
  const fileSystem = yield* FileSystem.FileSystem;
334
- const { resolvedOutputDir, buildTime, ogResolver, siteUrl, ogImage, packageName, apiName } = ctx;
361
+ const { buildId, resolvedOutputDir, buildTime, ogResolver, siteUrl, ogImage, packageName, apiName } = ctx;
335
362
  const { workItem, bodyContent, frontmatter, contentHash, frontmatterHash, publishedTime, modifiedTime, isUnchanged, routePath, relativePathWithExt } = result;
336
363
  const { item, categoryKey, categoryConfig, namespaceMember } = workItem;
337
364
  const absolutePath = path.join(resolvedOutputDir, relativePathWithExt);
@@ -346,8 +373,18 @@ function writeSingleFile(result, ctx) {
346
373
  buildTime
347
374
  };
348
375
  if (isUnchanged) {
349
- yield* Metric.increment(BuildMetrics.filesTotal);
350
- yield* Metric.increment(BuildMetrics.filesUnchanged);
376
+ yield* emit(PluginEvent.FileDecision({
377
+ ctx: {
378
+ buildId,
379
+ ...packageName != null ? { packageName } : {}
380
+ },
381
+ file: relativePathWithExt,
382
+ status: "unchanged",
383
+ contentHash,
384
+ frontmatterHash,
385
+ source: "snapshot",
386
+ level: "debug"
387
+ }));
351
388
  return {
352
389
  relativePathWithExt,
353
390
  absolutePath,
@@ -379,9 +416,18 @@ function writeSingleFile(result, ctx) {
379
416
  yield* fileSystem.makeDirectory(dirPath, { recursive: true }).pipe(Effect.orDie);
380
417
  yield* fileSystem.writeFileString(absolutePath, finalContent).pipe(Effect.orDie);
381
418
  const status = fileExisted ? "modified" : "new";
382
- yield* Metric.increment(BuildMetrics.filesTotal);
383
- if (status === "new") yield* Metric.increment(BuildMetrics.filesNew);
384
- else yield* Metric.increment(BuildMetrics.filesModified);
419
+ yield* emit(PluginEvent.FileDecision({
420
+ ctx: {
421
+ buildId,
422
+ ...packageName != null ? { packageName } : {}
423
+ },
424
+ file: relativePathWithExt,
425
+ status,
426
+ contentHash,
427
+ frontmatterHash,
428
+ source: "snapshot",
429
+ level: "debug"
430
+ }));
385
431
  return {
386
432
  relativePathWithExt,
387
433
  absolutePath,
@@ -411,7 +457,7 @@ function writeMetadata(input) {
411
457
  return Effect.gen(function* () {
412
458
  const fileSystem = yield* FileSystem.FileSystem;
413
459
  const snapshotSvc = yield* SnapshotService;
414
- const { fileResults, categories, resolvedOutputDir, existingSnapshots, buildTime, baseRoute, packageName, generatedFiles } = input;
460
+ const { buildId, fileResults, categories, resolvedOutputDir, existingSnapshots, buildTime, baseRoute, packageName, generatedFiles } = input;
415
461
  const categoriesWithItems = /* @__PURE__ */ new Set();
416
462
  for (const result of fileResults) categoriesWithItems.add(result.categoryKey);
417
463
  const apiMetaEntries = [];
@@ -456,13 +502,30 @@ function writeMetadata(input) {
456
502
  }
457
503
  if (!apiMetaUnchanged) {
458
504
  yield* fileSystem.writeFileString(apiMetaJsonPath, apiMetaJsonContent).pipe(Effect.orDie);
459
- yield* Metric.increment(BuildMetrics.filesTotal);
460
- if (apiMetaOldSnapshot) yield* Metric.increment(BuildMetrics.filesModified);
461
- else yield* Metric.increment(BuildMetrics.filesNew);
462
- } else {
463
- yield* Metric.increment(BuildMetrics.filesTotal);
464
- yield* Metric.increment(BuildMetrics.filesUnchanged);
465
- }
505
+ yield* emit(PluginEvent.FileDecision({
506
+ ctx: {
507
+ buildId,
508
+ packageName
509
+ },
510
+ file: apiMetaJsonRelPath,
511
+ status: apiMetaOldSnapshot ? "modified" : "new",
512
+ contentHash: apiMetaContentHash,
513
+ frontmatterHash: "",
514
+ source: "snapshot",
515
+ level: "debug"
516
+ }));
517
+ } else yield* emit(PluginEvent.FileDecision({
518
+ ctx: {
519
+ buildId,
520
+ packageName
521
+ },
522
+ file: apiMetaJsonRelPath,
523
+ status: "unchanged",
524
+ contentHash: apiMetaContentHash,
525
+ frontmatterHash: "",
526
+ source: "snapshot",
527
+ level: "debug"
528
+ }));
466
529
  yield* snapshotSvc.upsert({
467
530
  outputDir: resolvedOutputDir,
468
531
  filePath: apiMetaJsonRelPath,
@@ -543,13 +606,30 @@ function writeMetadata(input) {
543
606
  const categoryDir = path.dirname(categoryMetaPath);
544
607
  yield* fileSystem.makeDirectory(categoryDir, { recursive: true }).pipe(Effect.orDie);
545
608
  yield* fileSystem.writeFileString(categoryMetaPath, content).pipe(Effect.orDie);
546
- yield* Metric.increment(BuildMetrics.filesTotal);
547
- if (oldSnapshot) yield* Metric.increment(BuildMetrics.filesModified);
548
- else yield* Metric.increment(BuildMetrics.filesNew);
549
- } else {
550
- yield* Metric.increment(BuildMetrics.filesTotal);
551
- yield* Metric.increment(BuildMetrics.filesUnchanged);
552
- }
609
+ yield* emit(PluginEvent.FileDecision({
610
+ ctx: {
611
+ buildId,
612
+ packageName
613
+ },
614
+ file: relPath,
615
+ status: oldSnapshot ? "modified" : "new",
616
+ contentHash,
617
+ frontmatterHash: "",
618
+ source: "snapshot",
619
+ level: "debug"
620
+ }));
621
+ } else yield* emit(PluginEvent.FileDecision({
622
+ ctx: {
623
+ buildId,
624
+ packageName
625
+ },
626
+ file: relPath,
627
+ status: "unchanged",
628
+ contentHash,
629
+ frontmatterHash: "",
630
+ source: "snapshot",
631
+ level: "debug"
632
+ }));
553
633
  generatedFiles.add(relPath);
554
634
  if (isUnchanged) return null;
555
635
  return {
@@ -582,14 +662,18 @@ function cleanupAndCommit(input) {
582
662
  return Effect.gen(function* () {
583
663
  const fileSystem = yield* FileSystem.FileSystem;
584
664
  const snapshotSvc = yield* SnapshotService;
585
- const { fileResults, resolvedOutputDir, generatedFiles } = input;
665
+ const { buildId, fileResults, resolvedOutputDir, generatedFiles } = input;
586
666
  const snapshotsToUpdate = fileResults.filter((r) => r.status !== "unchanged").map((r) => r.snapshot);
587
667
  if (snapshotsToUpdate.length > 0) yield* snapshotSvc.batchUpsert(snapshotsToUpdate).pipe(Effect.ignore);
588
668
  const staleFiles = yield* snapshotSvc.cleanupStale(resolvedOutputDir, generatedFiles).pipe(Effect.orElseSucceed(() => []));
589
669
  yield* Effect.forEach(staleFiles, (staleFile) => Effect.gen(function* () {
590
670
  const fullPath = path.join(resolvedOutputDir, staleFile);
591
671
  yield* fileSystem.remove(fullPath).pipe(Effect.ignore);
592
- yield* Effect.logDebug(`🗑️ DELETED STALE: ${staleFile}`);
672
+ yield* emit(PluginEvent.StaleDeleted({
673
+ ctx: { buildId },
674
+ file: staleFile,
675
+ level: "trace"
676
+ }));
593
677
  }), { concurrency: "unbounded" });
594
678
  const allFiles = yield* fileSystem.readDirectory(resolvedOutputDir, { recursive: true }).pipe(Effect.orElseSucceed(() => []));
595
679
  const orphanedFiles = [];
@@ -603,7 +687,11 @@ function cleanupAndCommit(input) {
603
687
  const fullPath = path.join(resolvedOutputDir, orphan);
604
688
  yield* fileSystem.remove(fullPath).pipe(Effect.ignore);
605
689
  yield* snapshotSvc.deleteSnapshot(resolvedOutputDir, orphan).pipe(Effect.ignore);
606
- yield* Effect.logDebug(`🗑️ DELETED ORPHAN: ${orphan}`);
690
+ yield* emit(PluginEvent.OrphanDeleted({
691
+ ctx: { buildId },
692
+ file: orphan,
693
+ level: "trace"
694
+ }));
607
695
  }), { concurrency: "unbounded" });
608
696
  if (orphanedFiles.length > 0) {
609
697
  const dirs = /* @__PURE__ */ new Set();
@@ -616,7 +704,11 @@ function cleanupAndCommit(input) {
616
704
  const fullDir = path.join(resolvedOutputDir, dir);
617
705
  if ((yield* fileSystem.readDirectory(fullDir).pipe(Effect.orElseSucceed(() => ["placeholder"]))).length === 0) {
618
706
  yield* fileSystem.remove(fullDir).pipe(Effect.ignore);
619
- yield* Effect.logDebug(`🗑️ REMOVED EMPTY DIR: ${dir}`);
707
+ yield* emit(PluginEvent.EmptyDirRemoved({
708
+ ctx: { buildId },
709
+ dir,
710
+ level: "trace"
711
+ }));
620
712
  }
621
713
  }
622
714
  }
@@ -637,6 +729,7 @@ function cleanupAndCommit(input) {
637
729
  */
638
730
  function buildPipelineForApi(input) {
639
731
  const generateCtx = {
732
+ buildId: input.buildId,
640
733
  existingSnapshots: input.existingSnapshots,
641
734
  baseRoute: input.baseRoute,
642
735
  packageName: input.packageName,
@@ -649,6 +742,7 @@ function buildPipelineForApi(input) {
649
742
  ...input.llmsPlugin != null ? { llmsPlugin: input.llmsPlugin } : {}
650
743
  };
651
744
  const writeCtx = {
745
+ buildId: input.buildId,
652
746
  resolvedOutputDir: input.resolvedOutputDir,
653
747
  buildTime: input.buildTime,
654
748
  ...input.ogResolver !== void 0 ? { ogResolver: input.ogResolver } : {},
@@ -661,4 +755,4 @@ function buildPipelineForApi(input) {
661
755
  }
662
756
 
663
757
  //#endregion
664
- export { buildPipelineForApi, cleanupAndCommit, prepareWorkItems, writeMetadata };
758
+ export { buildPipelineForApi, cleanupAndCommit, crossLinkKindPriority, generateSinglePage, normalizeMarkdownSpacing, prepareWorkItems, writeMetadata, writeSingleFile };
package/config-utils.js CHANGED
@@ -118,17 +118,19 @@ function extractTypeUtilities(packageJson) {
118
118
  * devDependencies: { "type-fest": "^4.0.0" }
119
119
  * };
120
120
  *
121
- * // Default: only peerDependencies + type utilities
121
+ * // Default: dependencies + peerDependencies + type utilities (devDependencies excluded).
122
+ * // The documented type surface is usually written against runtime dependencies,
123
+ * // so those must be loaded for Twoslash to resolve them.
122
124
  * extractAutoDetectedPackages(pkg);
123
- * // Returns: [{ name: "zod", version: "^3.22.4" }, { name: "type-fest", version: "^4.0.0" }]
124
- *
125
- * // Include all dependency types
126
- * extractAutoDetectedPackages(pkg, { dependencies: true, peerDependencies: true, autoDependencies: true });
127
125
  * // Returns: [{ name: "effect", ... }, { name: "zod", ... }, { name: "type-fest", ... }]
126
+ *
127
+ * // Opt out of dependencies (peerDependencies + type utilities only)
128
+ * extractAutoDetectedPackages(pkg, { dependencies: false });
129
+ * // Returns: [{ name: "zod", version: "^3.22.4" }, { name: "type-fest", version: "^4.0.0" }]
128
130
  * ```
129
131
  */
130
132
  function extractAutoDetectedPackages(packageJson, options = {}) {
131
- const { dependencies = false, devDependencies = false, peerDependencies = true, autoDependencies = true } = options;
133
+ const { dependencies = true, devDependencies = false, peerDependencies = true, autoDependencies = true } = options;
132
134
  const packages = [];
133
135
  if (dependencies && packageJson?.dependencies) packages.push(...Object.entries(packageJson.dependencies).map(([name, version]) => ({
134
136
  name,
@@ -185,6 +187,33 @@ function resolvePackageVersionConflicts(packages) {
185
187
  return resolved;
186
188
  }
187
189
  /**
190
+ * Resolve each external package's version spec to an exact, published version.
191
+ *
192
+ * The type registry's CDN (jsDelivr) requires an exact version — its flat-file
193
+ * API 404s on semver ranges (`^4.1.0`), npm tags, and unpublished/workspace
194
+ * versions. This helper maps each spec through the supplied `resolve` function
195
+ * and drops any package whose resolution fails. Failures are the intended skip
196
+ * signal: a workspace-only or unpublished package (e.g. `@scope/pkg@1.0.0` that
197
+ * was never pushed to the registry) resolves to an error and is omitted, so it
198
+ * never poisons the batch load. Input order is preserved for survivors.
199
+ *
200
+ * @param packages - External package specs (typically post-deduplication)
201
+ * @param resolve - Resolver mapping a spec to its exact published version
202
+ * @returns Effect yielding the resolved specs with unresolvable packages dropped
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * // [{ name: "vitest", version: "^4.1.0" }] -> [{ name: "vitest", version: "4.1.9" }]
207
+ * // an unpublished workspace package resolves to an error and is dropped
208
+ * ```
209
+ */
210
+ function resolveExternalPackageVersions(packages, resolve) {
211
+ return Effect.forEach(packages, (pkg) => resolve(pkg).pipe(Effect.map((version) => ({
212
+ name: pkg.name,
213
+ version
214
+ })), Effect.catchAll(() => Effect.succeed(null))), { concurrency: 5 }).pipe(Effect.map((results) => results.filter((spec) => spec !== null)));
215
+ }
216
+ /**
188
217
  * Strip range prefixes from a version string to get a clean semver.
189
218
  */
190
219
  function stripRangePrefix(version) {
@@ -255,4 +284,4 @@ function validateExternalPackages(externalPackages, packageJson) {
255
284
  }
256
285
 
257
286
  //#endregion
258
- export { extractAutoDetectedPackages, isLoadedModel, isVersionConfig, mergeLlmsPluginConfig, validateExternalPackages };
287
+ export { extractAutoDetectedPackages, extractPeerDependencies, extractTypeUtilities, isLoadedModel, isVersionConfig, mergeLlmsPluginConfig, normalizeLlmsPluginConfig, resolveExternalPackageVersions, resolvePackageVersionConflicts, validateExternalPackages };
package/content-hash.js CHANGED
@@ -76,4 +76,4 @@ function hashFrontmatter(frontmatter) {
76
76
  }
77
77
 
78
78
  //#endregion
79
- export { hashContent, hashFrontmatter };
79
+ export { hashContent, hashFrontmatter, normalizeContent };
package/errors.js CHANGED
@@ -26,4 +26,4 @@ const TwoslashProcessingErrorBase = Data.TaggedError("TwoslashProcessingError");
26
26
  const PrettierFormatErrorBase = Data.TaggedError("PrettierFormatError");
27
27
 
28
28
  //#endregion
29
- export { ConfigValidationError, SnapshotDbError, TypeRegistryError };
29
+ export { ConfigValidationError, ConfigValidationErrorBase, SnapshotDbError, SnapshotDbErrorBase, TypeRegistryError, TypeRegistryErrorBase };