rspress-plugin-api-extractor 0.7.5 → 0.8.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/config-utils.js CHANGED
@@ -15,6 +15,15 @@ function isLoadedModel(result) {
15
15
  return typeof result === "object" && result !== null && "model" in result;
16
16
  }
17
17
  /**
18
+ * Classify the `api` / `apis` options. Callers use `disabled` to skip doc
19
+ * generation without failing the build, and `missing` to fail it.
20
+ */
21
+ function classifyApiConfig(options) {
22
+ if (options.api != null) return "configured";
23
+ if (options.apis != null && options.apis.length > 0) return "configured";
24
+ return options.api !== void 0 || options.apis !== void 0 ? "disabled" : "missing";
25
+ }
26
+ /**
18
27
  * Normalize llmsPlugin config to always be an LlmsPlugin object
19
28
  */
20
29
  function normalizeLlmsPluginConfig(config) {
@@ -284,4 +293,4 @@ function validateExternalPackages(externalPackages, packageJson) {
284
293
  }
285
294
 
286
295
  //#endregion
287
- export { extractAutoDetectedPackages, extractPeerDependencies, extractTypeUtilities, isLoadedModel, isVersionConfig, mergeLlmsPluginConfig, normalizeLlmsPluginConfig, resolveExternalPackageVersions, resolvePackageVersionConflicts, validateExternalPackages };
296
+ export { classifyApiConfig, extractAutoDetectedPackages, extractPeerDependencies, extractTypeUtilities, isLoadedModel, isVersionConfig, mergeLlmsPluginConfig, normalizeLlmsPluginConfig, resolveExternalPackageVersions, resolvePackageVersionConflicts, validateExternalPackages };
package/index.d.ts CHANGED
@@ -398,11 +398,17 @@ type MultiApiConfig = typeof MultiApiConfig.Encoded;
398
398
  /**
399
399
  * Top-level options passed to {@link ApiExtractorPlugin}.
400
400
  *
401
+ * @remarks
402
+ * Supplying `api: null`, `apis: null` or `apis: []` opts into an inert plugin:
403
+ * the options are validated but no documentation is generated. This lets a site
404
+ * pre-configure the plugin before any API model exists. Omitting both keys
405
+ * entirely is still a configuration error.
406
+ *
401
407
  * @public
402
408
  */
403
409
  declare const PluginOptions: Schema.Struct<{
404
- /** Single-API configuration (mutually exclusive with `apis`). */
405
- readonly api: Schema.optional<Schema.Struct<{
410
+ /** Single-API configuration (mutually exclusive with `apis`). `null` disables generation. */
411
+ readonly api: Schema.optional<Schema.NullOr<Schema.Struct<{
406
412
  /** npm package name of the documented package. */
407
413
  readonly packageName: Schema.String;
408
414
  /** Optional display name shown in the sidebar and page titles. */
@@ -553,9 +559,9 @@ declare const PluginOptions: Schema.Struct<{
553
559
  readonly tsconfig: Schema.optional<Schema.declare<string | URL | ((...args: Array<unknown>) => unknown), string | URL | ((...args: Array<unknown>) => unknown)>>;
554
560
  /** TypeScript compiler options for Twoslash. */
555
561
  readonly compilerOptions: Schema.optional<Schema.Unknown>;
556
- }>>;
557
- /** Multi-API portal configuration (mutually exclusive with `api`). */
558
- readonly apis: Schema.optional<Schema.mutable<Schema.$Array<Schema.Struct<{
562
+ }>>>;
563
+ /** Multi-API portal configuration (mutually exclusive with `api`). `null` or `[]` disables generation. */
564
+ readonly apis: Schema.optional<Schema.NullOr<Schema.mutable<Schema.$Array<Schema.Struct<{
559
565
  /** npm package name of the documented package. */
560
566
  readonly packageName: Schema.String;
561
567
  /** Optional display name shown in the sidebar and page titles. */
@@ -646,7 +652,7 @@ declare const PluginOptions: Schema.Struct<{
646
652
  readonly tsconfig: Schema.optional<Schema.declare<string | URL | ((...args: Array<unknown>) => unknown), string | URL | ((...args: Array<unknown>) => unknown)>>;
647
653
  /** TypeScript compiler options for Twoslash. First API wins, as with `tsconfig`. */
648
654
  readonly compilerOptions: Schema.optional<Schema.Unknown>;
649
- }>>>>;
655
+ }>>>>>;
650
656
  /** Canonical site URL used for Open Graph absolute URLs. */
651
657
  readonly siteUrl: Schema.optional<Schema.String>;
652
658
  /** Global Open Graph image configuration (overridden per-API). */
@@ -8,7 +8,7 @@ import { OpenGraphResolver } from "../og-resolver.js";
8
8
  import { withPhase } from "../observability/spans.js";
9
9
  import { resolveTypeScriptConfig } from "../typescript-config.js";
10
10
  import { TwoslashManager } from "../twoslash-transformer.js";
11
- import { extractAutoDetectedPackages, isVersionConfig, mergeLlmsPluginConfig, validateExternalPackages } from "../config-utils.js";
11
+ import { classifyApiConfig, extractAutoDetectedPackages, isVersionConfig, mergeLlmsPluginConfig, validateExternalPackages } from "../config-utils.js";
12
12
  import { ApiExtractedPackage } from "../api-extracted-package.js";
13
13
  import { CategoryResolver } from "../category-resolver.js";
14
14
  import { ConfigValidationError } from "../errors.js";
@@ -94,21 +94,21 @@ function prependImportsToVfs(vfs, apiPackage, packageName, wantTrace) {
94
94
  */
95
95
  function validateOptions(options, rspressConfig) {
96
96
  return Effect.gen(function* () {
97
- const { api, apis } = options;
97
+ const api = options.api ?? void 0;
98
+ const apis = options.apis != null && options.apis.length > 0 ? options.apis : void 0;
98
99
  const { multiVersion } = rspressConfig;
99
100
  if (api && apis) return yield* new ConfigValidationError({
100
101
  field: "api/apis",
101
102
  reason: "Cannot provide both 'api' and 'apis'. Use 'api' for single-package sites or 'apis' for multi-package portals."
102
103
  });
103
- if (!api && !apis) return yield* new ConfigValidationError({
104
- field: "api/apis",
105
- reason: "Must provide either 'api' or 'apis'."
106
- });
107
- if (apis) {
108
- if (apis.length === 0) return yield* new ConfigValidationError({
109
- field: "apis",
110
- reason: "'apis' must contain at least one API configuration."
104
+ if (!api && !apis) {
105
+ if (classifyApiConfig(options) === "missing") return yield* new ConfigValidationError({
106
+ field: "api/apis",
107
+ reason: "Must provide either 'api' or 'apis'."
111
108
  });
109
+ return;
110
+ }
111
+ if (apis) {
112
112
  if (multiVersion) return yield* new ConfigValidationError({
113
113
  field: "apis",
114
114
  reason: "multiVersion is not supported with 'apis' (multi-API mode). Use 'api' (single-API mode) for versioned documentation."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rspress-plugin-api-extractor",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "RSPress plugin for generating API documentation from TypeScript API Extractor models",
6
6
  "keywords": [
package/plugin.js CHANGED
@@ -12,7 +12,7 @@ import { VfsRegistry } from "./vfs-registry.js";
12
12
  import { generateApiDocs } from "./build-program.js";
13
13
  import { deriveOutputPaths, normalizeBaseRoute, unscopedName } from "./path-derivation.js";
14
14
  import { fromDir, fromParentDir } from "./config-helpers.js";
15
- import { mergeLlmsPluginConfig } from "./config-utils.js";
15
+ import { classifyApiConfig, mergeLlmsPluginConfig } from "./config-utils.js";
16
16
  import { DEFAULT_SHIKI_THEMES, setShikiUtilsEventEmitter } from "./markdown/shiki-utils.js";
17
17
  import { setModelLoaderEventEmitter } from "./model-loader.js";
18
18
  import { resolveObservability } from "./schemas/observability.js";
@@ -73,6 +73,7 @@ function normalizeThemeConfig(theme) {
73
73
  */
74
74
  function ApiExtractorPluginImpl(rawOptions) {
75
75
  const options = Schema.decodeUnknownSync(PluginOptions)(rawOptions);
76
+ const isInert = classifyApiConfig(options) === "disabled";
76
77
  const shikiCrossLinker = new ShikiCrossLinker();
77
78
  const envLogLevel = process.env.LOG_LEVEL?.toLowerCase();
78
79
  const buildId = `${process.pid}-${performance.now().toString(36)}`;
@@ -112,7 +113,7 @@ function ApiExtractorPluginImpl(rawOptions) {
112
113
  name: "rspress-plugin-api-docs",
113
114
  async beforeBuild(_config, _isProd) {},
114
115
  async afterBuild(_config, isProd) {
115
- if (isFirstBuild) {
116
+ if (isFirstBuild && !isInert) {
116
117
  await effectRuntime.runPromise(logBuildSummary(obs.thresholds.slowCodeBlock));
117
118
  if (isProd) await effectRuntime.runPromise(Effect.gen(function* () {
118
119
  const packageName = yield* readSitePackageName;
@@ -183,7 +184,7 @@ function ApiExtractorPluginImpl(rawOptions) {
183
184
  key: dep.key,
184
185
  replacement: dep.replacement
185
186
  }));
186
- try {
187
+ if (!isInert) try {
187
188
  const rspressConfigSubset = {
188
189
  ...rspressMultiVersion != null ? { multiVersion: rspressMultiVersion } : {},
189
190
  ...rspressLocales.length > 0 ? { locales: rspressLocales.map((lang) => ({ lang })) } : {},
@@ -253,7 +254,7 @@ function ApiExtractorPluginImpl(rawOptions) {
253
254
  const updatedConfig = { ..._config };
254
255
  if (!updatedConfig.builderConfig) updatedConfig.builderConfig = {};
255
256
  if (!updatedConfig.builderConfig.source) updatedConfig.builderConfig.source = {};
256
- if (rspressLlmsEnabled && resolvedLlmsPlugin.enabled && resolvedLlmsPlugin.scopes) {
257
+ if (!isInert && rspressLlmsEnabled && resolvedLlmsPlugin.enabled && resolvedLlmsPlugin.scopes) {
257
258
  if (!updatedConfig.builderConfig.resolve) updatedConfig.builderConfig.resolve = {};
258
259
  const pluginDir = path.dirname(fileURLToPath(import.meta.url));
259
260
  const customLlmsViewOptions = path.resolve(pluginDir, "runtime/components/ApiLlmsViewOptions/index.js");
@@ -277,7 +278,7 @@ function ApiExtractorPluginImpl(rawOptions) {
277
278
  theme: remarkTheme
278
279
  }]);
279
280
  updatedConfig.markdown.remarkPlugins.push([remarkApiCodeblocks]);
280
- if (rspressLlmsEnabled && resolvedLlmsPlugin.enabled && resolvedLlmsPlugin.scopes) {
281
+ if (!isInert && rspressLlmsEnabled && resolvedLlmsPlugin.enabled && resolvedLlmsPlugin.scopes) {
281
282
  packageRoutes.clear();
282
283
  if (options.api) packageRoutes.set(options.api.packageName, normalizeBaseRoute(options.api.baseRoute ?? "/"));
283
284
  else if (options.apis) for (const api of options.apis) packageRoutes.set(api.packageName, normalizeBaseRoute(api.baseRoute ?? `/${unscopedName(api.packageName)}`));
package/schemas/config.js CHANGED
@@ -287,13 +287,19 @@ const MultiApiConfig = Schema.Struct({
287
287
  /**
288
288
  * Top-level options passed to {@link ApiExtractorPlugin}.
289
289
  *
290
+ * @remarks
291
+ * Supplying `api: null`, `apis: null` or `apis: []` opts into an inert plugin:
292
+ * the options are validated but no documentation is generated. This lets a site
293
+ * pre-configure the plugin before any API model exists. Omitting both keys
294
+ * entirely is still a configuration error.
295
+ *
290
296
  * @public
291
297
  */
292
298
  const PluginOptions = Schema.Struct({
293
- /** Single-API configuration (mutually exclusive with `apis`). */
294
- api: Schema.optional(SingleApiConfig),
295
- /** Multi-API portal configuration (mutually exclusive with `api`). */
296
- apis: Schema.optional(Schema.mutable(Schema.Array(MultiApiConfig))),
299
+ /** Single-API configuration (mutually exclusive with `apis`). `null` disables generation. */
300
+ api: Schema.optional(Schema.NullOr(SingleApiConfig)),
301
+ /** Multi-API portal configuration (mutually exclusive with `api`). `null` or `[]` disables generation. */
302
+ apis: Schema.optional(Schema.NullOr(Schema.mutable(Schema.Array(MultiApiConfig)))),
297
303
  /** Canonical site URL used for Open Graph absolute URLs. */
298
304
  siteUrl: Schema.optional(Schema.String),
299
305
  /** Global Open Graph image configuration (overridden per-API). */