@savvy-web/rslib-builder 0.1.2 → 0.2.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/index.d.ts CHANGED
@@ -41,6 +41,7 @@
41
41
 
42
42
  import type { ConfigParams } from '@rslib/core';
43
43
  import type { PackageJson } from 'type-fest';
44
+ import type { PathLike } from 'node:fs';
44
45
  import type { RawCopyPattern } from '@rspack/binding';
45
46
  import type { RsbuildPlugin } from '@rsbuild/core';
46
47
  import type { RslibConfig } from '@rslib/core';
@@ -90,13 +91,38 @@ export declare interface ApiModelOptions {
90
91
  * ```
91
92
  */
92
93
  localPaths?: string[];
94
+ /**
95
+ * TSDoc configuration for custom tag definitions.
96
+ * Passed to API Extractor for documentation processing.
97
+ *
98
+ * @remarks
99
+ * By default, all standard tag groups (core, extended, discretionary) are
100
+ * enabled. Custom tags defined in `tagDefinitions` are automatically
101
+ * supported. Use `supportForTags` only to disable specific tags.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * apiModel: {
106
+ * enabled: true,
107
+ * tsdoc: {
108
+ * tagDefinitions: [{ tagName: "@error", syntaxKind: "inline" }]
109
+ * }
110
+ * }
111
+ * ```
112
+ */
113
+ tsdoc?: TsDocOptions;
114
+ /**
115
+ * Options for tsdoc-metadata.json generation.
116
+ * @defaultValue true (enabled when apiModel is enabled)
117
+ */
118
+ tsdocMetadata?: TsDocMetadataOptions | boolean;
93
119
  }
94
120
 
95
121
  /**
96
122
  * Plugin to read package.json and configure entry points based on exports.
97
123
  *
98
- * @param options - Plugin configuration options
99
- * @param options.exportsAsIndexes - When true, export paths create index files in nested directories
124
+ * @param options - Plugin configuration options with properties:
125
+ * - `exportsAsIndexes`: When true, export paths create index files in nested directories
100
126
  * @public
101
127
  */
102
128
  export declare const AutoEntryPlugin: (options?: {
@@ -122,7 +148,7 @@ export declare function collectDtsFiles(dir: string, baseDir?: string): Promise<
122
148
  * Plugin to generate TypeScript declaration files using tsgo and emit them through Rslib's asset pipeline.
123
149
  *
124
150
  * @remarks
125
- * This plugin uses tsgo (@typescript/native-preview) for faster declaration file generation
151
+ * This plugin uses tsgo (`\@typescript/native-preview`) for faster declaration file generation
126
152
  * and integrates with Rslib's build system by emitting generated files as compilation assets.
127
153
  *
128
154
  * ## Features
@@ -205,7 +231,7 @@ export declare interface DtsPluginOptions {
205
231
  /**
206
232
  * Packages whose types should be bundled (inlined) into the output .d.ts files.
207
233
  * Only applies when bundle is true.
208
- * Supports glob patterns (e.g., '@commitlint/*', 'type-fest')
234
+ * Supports glob patterns (e.g., '\@commitlint/*', 'type-fest')
209
235
  * @defaultValue []
210
236
  */
211
237
  bundledPackages?: string[];
@@ -250,7 +276,7 @@ export declare function ensureTempDeclarationDir(cwd: string, name: string): Pro
250
276
  * TypeScript source files.
251
277
  *
252
278
  * **Export Path Mapping:**
253
- * - Converts export keys to entry names (e.g., "./utils" -> "utils")
279
+ * - Converts export keys to entry names (e.g., "./utils" becomes "utils")
254
280
  * - Maps the root export "." to "index" entry
255
281
  * - Replaces path separators with hyphens for nested exports (default)
256
282
  * - When `exportsAsIndexes` is true, preserves path structure
@@ -506,7 +532,7 @@ export declare interface NodeLibraryBuilderOptions {
506
532
  * specify which packages (including transitive dependencies) should have their types bundled.
507
533
  * This is particularly useful for ensuring devDependencies are fully inlined without external imports.
508
534
  *
509
- * Supports minimatch patterns (e.g., '@pnpm/**', 'picocolors')
535
+ * Supports minimatch patterns (e.g., '\@pnpm/**', 'picocolors')
510
536
  *
511
537
  * @example
512
538
  * ```typescript
@@ -520,10 +546,10 @@ export declare interface NodeLibraryBuilderOptions {
520
546
  * Optional callback to transform files after they're built but before the files array is finalized.
521
547
  * Useful for copying/renaming files or adding additional files to the build output.
522
548
  *
523
- * @param context - Transform context containing compilation context and files set
524
- * @param context.compilation - Rspack compilation object with assets
525
- * @param context.filesArray - Set of files that will be included in package.json files field
526
- * @param context.target - Current build target (dev/npm/jsr)
549
+ * @param context - Transform context with properties:
550
+ * - `compilation`: Rspack compilation object with assets
551
+ * - `filesArray`: Set of files that will be included in package.json files field
552
+ * - `target`: Current build target (dev/npm/jsr)
527
553
  *
528
554
  * @example
529
555
  * ```typescript
@@ -603,7 +629,7 @@ export declare interface NodeLibraryBuilderOptions {
603
629
  *
604
630
  * @remarks
605
631
  * This class consolidates all package.json transformation logic including:
606
- * - Path transformations (src/ -> dist/, .ts -> .js)
632
+ * - Path transformations (src/ to dist/, .ts to .js)
607
633
  * - Export conditions generation (types, import, require)
608
634
  * - Bin field transformations
609
635
  * - PNPM catalog and workspace resolution
@@ -680,9 +706,9 @@ export declare class PackageJsonTransformer {
680
706
  * Performs the complete package.json transformation.
681
707
  *
682
708
  * @param packageJson - The source package.json
683
- * @param context - Transform context
684
- * @param context.isProduction - Whether this is a production build
685
- * @param context.customTransform - Optional custom transform function
709
+ * @param context - Transform context with properties:
710
+ * - `isProduction`: Whether this is a production build
711
+ * - `customTransform`: Optional custom transform function
686
712
  * @returns The transformed package.json
687
713
  */
688
714
  transform(packageJson: PackageJson, context?: {
@@ -723,7 +749,7 @@ export declare interface PackageJsonTransformOptions {
723
749
  */
724
750
  processTSExports?: boolean;
725
751
  /**
726
- * Whether to collapse index files (./foo/index.ts -> ./foo.js).
752
+ * Whether to collapse index files (./foo/index.ts becomes ./foo.js).
727
753
  * This is typically true for bundled builds.
728
754
  * @defaultValue false
729
755
  */
@@ -816,7 +842,7 @@ export declare class PnpmCatalog {
816
842
  * @param dir - The directory containing the package (defaults to cwd)
817
843
  * @returns The resolved package.json
818
844
  *
819
- * @throws {Error} When resolution fails for critical dependencies
845
+ * @throws When resolution fails for critical dependencies
820
846
  */
821
847
  resolvePackageJson(packageJson: PackageJson, dir?: string): Promise<PackageJson>;
822
848
  /**
@@ -859,4 +885,203 @@ export declare type TransformPackageJsonFn = (context: {
859
885
  pkg: PackageJson;
860
886
  }) => PackageJson;
861
887
 
888
+ /**
889
+ * Builder for TSDoc configuration files.
890
+ * Handles tag group expansion, config generation, and file persistence.
891
+ * @public
892
+ */
893
+ export declare class TsDocConfigBuilder {
894
+ /** All available TSDoc tag groups. */
895
+ static readonly ALL_GROUPS: TsDocTagGroup[];
896
+ /** Maps group names to TSDoc Standardization enum values. */
897
+ private static readonly GROUP_TO_STANDARDIZATION;
898
+ /**
899
+ * Standard TSDoc tag definitions organized by standardization group.
900
+ * Lazily computed from `\@microsoft/tsdoc` StandardTags.
901
+ */
902
+ static readonly TAG_GROUPS: Record<TsDocTagGroup, TsDocTagDefinition[]>;
903
+ /**
904
+ * Detects if running in a CI environment.
905
+ * @returns true if CI or GITHUB_ACTIONS environment variable is "true"
906
+ */
907
+ static isCI(): boolean;
908
+ /**
909
+ * Gets standard TSDoc tag definitions for a specific group.
910
+ * Uses StandardTags from `\@microsoft/tsdoc` package.
911
+ */
912
+ static getTagsForGroup(group: TsDocTagGroup): TsDocTagDefinition[];
913
+ /**
914
+ * Determines if the TSDoc config should be persisted to disk.
915
+ * @param persistConfig - The persistConfig option value
916
+ * @returns true if the config should be persisted
917
+ */
918
+ static shouldPersist(persistConfig: boolean | PathLike | undefined): boolean;
919
+ /**
920
+ * Gets the output path for the tsdoc.json file.
921
+ * @param persistConfig - The persistConfig option value
922
+ * @param cwd - The current working directory
923
+ * @returns The absolute path where tsdoc.json should be written
924
+ */
925
+ static getConfigPath(persistConfig: boolean | PathLike | undefined, cwd: string): string;
926
+ /**
927
+ * Builds the complete TSDoc configuration from options.
928
+ *
929
+ * @remarks
930
+ * When all groups are enabled (default), returns `useStandardTags: true` to signal
931
+ * that the generated config should use `noStandardTags: false` and let TSDoc
932
+ * automatically load all standard tags. However, `supportForTags` is still populated
933
+ * because API Extractor requires explicit support declarations for each tag.
934
+ *
935
+ * When a subset of groups is specified, returns `useStandardTags: false` to signal
936
+ * that we must explicitly define which tags to include via `noStandardTags: true`.
937
+ */
938
+ static build(options?: TsDocOptions): {
939
+ tagDefinitions: TsDocTagDefinition[];
940
+ supportForTags: Record<string, boolean>;
941
+ useStandardTags: boolean;
942
+ };
943
+ /**
944
+ * Generates a tsdoc.json file from options.
945
+ *
946
+ * @remarks
947
+ * When all groups are enabled (default), generates a minimal config with
948
+ * `noStandardTags: false` so TSDoc automatically loads all standard tags.
949
+ * Only custom tags need to be defined in this case.
950
+ *
951
+ * When a subset of groups is specified, generates a config with
952
+ * `noStandardTags: true` and explicitly defines only the tags from
953
+ * the enabled groups.
954
+ */
955
+ static writeConfigFile(options: TsDocOptions | undefined, outputDir: string): Promise<string>;
956
+ /** Converts TSDocTagSyntaxKind enum to string format. */
957
+ private static syntaxKindToString;
958
+ }
959
+
960
+ /**
961
+ * Options for tsdoc-metadata.json generation.
962
+ * @public
963
+ */
964
+ export declare interface TsDocMetadataOptions {
965
+ /**
966
+ * Whether to generate tsdoc-metadata.json.
967
+ * @defaultValue true (when apiModel is enabled)
968
+ */
969
+ enabled?: boolean;
970
+ /**
971
+ * Custom filename for the TSDoc metadata file.
972
+ * @defaultValue "tsdoc-metadata.json"
973
+ */
974
+ filename?: string;
975
+ }
976
+
977
+ /**
978
+ * TSDoc configuration options for API Extractor.
979
+ * @remarks
980
+ * Provides ergonomic defaults - standard tags are auto-enabled via `groups`,
981
+ * custom tags are auto-supported, and `supportForTags` is only needed to
982
+ * disable specific tags.
983
+ *
984
+ * **Config optimization:** When all groups are enabled (default), the generated
985
+ * `tsdoc.json` uses `noStandardTags: false` to let TSDoc automatically load
986
+ * all standard tags, producing a minimal config file. When a subset of groups
987
+ * is specified, `noStandardTags: true` is used and only the enabled groups'
988
+ * tags are explicitly defined.
989
+ *
990
+ * @public
991
+ */
992
+ export declare interface TsDocOptions {
993
+ /**
994
+ * TSDoc tag groups to enable. Each group includes predefined standard tags
995
+ * from the official `\@microsoft/tsdoc` package.
996
+ * - "core": Essential TSDoc tags (\@param, \@returns, \@remarks, \@deprecated, etc.)
997
+ * - "extended": Additional tags (\@example, \@defaultValue, \@see, \@throws, etc.)
998
+ * - "discretionary": Release stage tags (\@alpha, \@beta, \@public, \@internal)
999
+ *
1000
+ * @remarks
1001
+ * When all groups are enabled (the default), the generated config uses
1002
+ * `noStandardTags: false` and TSDoc loads standard tags automatically.
1003
+ * When a subset is specified, `noStandardTags: true` is used and only
1004
+ * the tags from enabled groups are defined.
1005
+ *
1006
+ * @defaultValue ["core", "extended", "discretionary"]
1007
+ */
1008
+ groups?: TsDocTagGroup[];
1009
+ /**
1010
+ * Custom TSDoc tag definitions beyond the standard groups.
1011
+ * These are automatically added to supportForTags (no need to declare twice).
1012
+ *
1013
+ * @example
1014
+ * ```typescript
1015
+ * tagDefinitions: [
1016
+ * { tagName: "@error", syntaxKind: "inline" },
1017
+ * { tagName: "@category", syntaxKind: "block", allowMultiple: false }
1018
+ * ]
1019
+ * ```
1020
+ */
1021
+ tagDefinitions?: TsDocTagDefinition[];
1022
+ /**
1023
+ * Override support for specific tags. Only needed to DISABLE tags.
1024
+ * Tags from enabled groups and custom tagDefinitions are auto-supported.
1025
+ *
1026
+ * @example
1027
+ * ```typescript
1028
+ * // Disable @beta even though "extended" group is enabled
1029
+ * supportForTags: { "@beta": false }
1030
+ * ```
1031
+ */
1032
+ supportForTags?: Record<string, boolean>;
1033
+ /**
1034
+ * Persist tsdoc.json to disk for tool integration (ESLint, IDEs).
1035
+ * - `true`: Write to project root as "tsdoc.json"
1036
+ * - `PathLike`: Write to specified path
1037
+ * - `false`: Clean up after API Extractor
1038
+ *
1039
+ * @defaultValue `true` when `CI` and `GITHUB_ACTIONS` env vars are not "true",
1040
+ * `false` otherwise (CI environments)
1041
+ */
1042
+ persistConfig?: boolean | PathLike;
1043
+ /**
1044
+ * How to handle TSDoc validation warnings from API Extractor.
1045
+ * - `"log"`: Show warnings in the console but continue the build
1046
+ * - `"fail"`: Show warnings and fail the build if any are found
1047
+ * - `"none"`: Suppress TSDoc warnings entirely
1048
+ *
1049
+ * @remarks
1050
+ * TSDoc warnings include unknown tags, malformed syntax, and other
1051
+ * documentation issues detected by API Extractor during processing.
1052
+ *
1053
+ * @example
1054
+ * ```typescript
1055
+ * // Fail build on any TSDoc issues (CI default)
1056
+ * warnings: "fail"
1057
+ *
1058
+ * // Show warnings but continue build (local default)
1059
+ * warnings: "log"
1060
+ * ```
1061
+ *
1062
+ * @defaultValue `"fail"` in CI environments (`CI` or `GITHUB_ACTIONS` env vars),
1063
+ * `"log"` otherwise
1064
+ */
1065
+ warnings?: "log" | "fail" | "none";
1066
+ }
1067
+
1068
+ /**
1069
+ * TSDoc tag definition for custom documentation tags.
1070
+ * @public
1071
+ */
1072
+ export declare interface TsDocTagDefinition {
1073
+ /** The tag name including the at-sign prefix (e.g., `\@error`, `\@category`) */
1074
+ tagName: string;
1075
+ /** How the tag is parsed: "block" | "inline" | "modifier" */
1076
+ syntaxKind: "block" | "inline" | "modifier";
1077
+ /** Whether the tag can appear multiple times on a declaration */
1078
+ allowMultiple?: boolean;
1079
+ }
1080
+
1081
+ /**
1082
+ * TSDoc standardization groups for predefined tag sets.
1083
+ * @public
1084
+ */
1085
+ export declare type TsDocTagGroup = "core" | "extended" | "discretionary";
1086
+
862
1087
  export { }
package/index.js CHANGED
@@ -8,6 +8,7 @@ import { getWorkspaceRoot } from "workspace-tools";
8
8
  import { logger as core_logger } from "@rsbuild/core";
9
9
  import picocolors from "picocolors";
10
10
  import { spawn } from "node:child_process";
11
+ import { StandardTags, Standardization, TSDocTagSyntaxKind } from "@microsoft/tsdoc";
11
12
  import { createCompilerHost, findConfigFile, formatDiagnostic, parseJsonConfigFileContent, readConfigFile, sys } from "typescript";
12
13
  import { createRequire } from "node:module";
13
14
  import { inspect } from "node:util";
@@ -394,6 +395,98 @@ const TSConfigs = {
394
395
  }
395
396
  }
396
397
  };
398
+ class TsDocConfigBuilder {
399
+ static ALL_GROUPS = [
400
+ "core",
401
+ "extended",
402
+ "discretionary"
403
+ ];
404
+ static GROUP_TO_STANDARDIZATION = {
405
+ core: Standardization.Core,
406
+ extended: Standardization.Extended,
407
+ discretionary: Standardization.Discretionary
408
+ };
409
+ static TAG_GROUPS = {
410
+ get core () {
411
+ return TsDocConfigBuilder.getTagsForGroup("core");
412
+ },
413
+ get extended () {
414
+ return TsDocConfigBuilder.getTagsForGroup("extended");
415
+ },
416
+ get discretionary () {
417
+ return TsDocConfigBuilder.getTagsForGroup("discretionary");
418
+ }
419
+ };
420
+ static isCI() {
421
+ return "true" === process.env.CI || "true" === process.env.GITHUB_ACTIONS;
422
+ }
423
+ static getTagsForGroup(group) {
424
+ const standardization = TsDocConfigBuilder.GROUP_TO_STANDARDIZATION[group];
425
+ return StandardTags.allDefinitions.filter((tag)=>tag.standardization === standardization).map((tag)=>({
426
+ tagName: tag.tagName,
427
+ syntaxKind: TsDocConfigBuilder.syntaxKindToString(tag.syntaxKind),
428
+ ...tag.allowMultiple ? {
429
+ allowMultiple: true
430
+ } : {}
431
+ }));
432
+ }
433
+ static shouldPersist(persistConfig) {
434
+ if (false === persistConfig) return false;
435
+ if (void 0 !== persistConfig) return true;
436
+ return !TsDocConfigBuilder.isCI();
437
+ }
438
+ static getConfigPath(persistConfig, cwd) {
439
+ if ("string" == typeof persistConfig) return (0, external_node_path_.isAbsolute)(persistConfig) ? persistConfig : (0, external_node_path_.join)(cwd, persistConfig);
440
+ if (persistConfig instanceof URL || Buffer.isBuffer(persistConfig)) {
441
+ const pathStr = persistConfig.toString();
442
+ return (0, external_node_path_.isAbsolute)(pathStr) ? pathStr : (0, external_node_path_.join)(cwd, pathStr);
443
+ }
444
+ return (0, external_node_path_.join)(cwd, "tsdoc.json");
445
+ }
446
+ static build(options = {}) {
447
+ const groups = options.groups ?? TsDocConfigBuilder.ALL_GROUPS;
448
+ const allGroupsEnabled = TsDocConfigBuilder.ALL_GROUPS.every((g)=>groups.includes(g));
449
+ const tagDefinitions = [];
450
+ const supportForTags = {};
451
+ for (const group of groups)for (const tag of TsDocConfigBuilder.TAG_GROUPS[group]){
452
+ supportForTags[tag.tagName] = true;
453
+ if (!allGroupsEnabled) tagDefinitions.push(tag);
454
+ }
455
+ if (options.tagDefinitions) for (const tag of options.tagDefinitions){
456
+ tagDefinitions.push(tag);
457
+ supportForTags[tag.tagName] = true;
458
+ }
459
+ if (options.supportForTags) Object.assign(supportForTags, options.supportForTags);
460
+ return {
461
+ tagDefinitions,
462
+ supportForTags,
463
+ useStandardTags: allGroupsEnabled
464
+ };
465
+ }
466
+ static async writeConfigFile(options = {}, outputDir) {
467
+ const { tagDefinitions, supportForTags, useStandardTags } = TsDocConfigBuilder.build(options);
468
+ const tsdocConfig = {
469
+ $schema: "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
470
+ noStandardTags: !useStandardTags,
471
+ reportUnsupportedHtmlElements: false
472
+ };
473
+ if (tagDefinitions.length > 0) tsdocConfig.tagDefinitions = tagDefinitions;
474
+ if (Object.keys(supportForTags).length > 0) tsdocConfig.supportForTags = supportForTags;
475
+ const configPath = (0, external_node_path_.join)(outputDir, "tsdoc.json");
476
+ await writeFile(configPath, JSON.stringify(tsdocConfig, null, 2));
477
+ return configPath;
478
+ }
479
+ static syntaxKindToString(kind) {
480
+ switch(kind){
481
+ case TSDocTagSyntaxKind.InlineTag:
482
+ return "inline";
483
+ case TSDocTagSyntaxKind.BlockTag:
484
+ return "block";
485
+ case TSDocTagSyntaxKind.ModifierTag:
486
+ return "modifier";
487
+ }
488
+ }
489
+ }
397
490
  function getUnscopedPackageName(name) {
398
491
  return name.startsWith("@") ? name.split("/")[1] ?? name : name;
399
492
  }
@@ -448,9 +541,25 @@ async function bundleDtsFiles(options) {
448
541
  const { cwd, tempDtsDir, tempOutputDir, tsconfigPath, bundledPackages, entryPoints, banner, footer, apiModel } = options;
449
542
  const bundledFiles = new Map();
450
543
  let apiModelPath;
544
+ let tsdocMetadataPath;
451
545
  const apiModelEnabled = true === apiModel || "object" == typeof apiModel && (void 0 === apiModel.enabled || true === apiModel.enabled);
452
546
  const apiModelFilename = "object" == typeof apiModel && apiModel.filename ? apiModel.filename : "api.json";
547
+ const tsdocOptions = "object" == typeof apiModel ? apiModel.tsdoc : void 0;
548
+ const tsdocMetadataOption = "object" == typeof apiModel ? apiModel.tsdocMetadata : void 0;
549
+ const tsdocWarnings = tsdocOptions?.warnings ?? (TsDocConfigBuilder.isCI() ? "fail" : "log");
550
+ const tsdocMetadataEnabled = apiModelEnabled && (void 0 === tsdocMetadataOption || true === tsdocMetadataOption || "object" == typeof tsdocMetadataOption && false !== tsdocMetadataOption.enabled);
551
+ const tsdocMetadataFilename = "object" == typeof tsdocMetadataOption && tsdocMetadataOption.filename ? tsdocMetadataOption.filename : "tsdoc-metadata.json";
453
552
  getApiExtractorPath();
553
+ const persistConfig = tsdocOptions?.persistConfig;
554
+ const shouldPersist = TsDocConfigBuilder.shouldPersist(persistConfig);
555
+ const tsdocConfigOutputPath = TsDocConfigBuilder.getConfigPath(persistConfig, cwd);
556
+ let tsdocConfigPath;
557
+ let tsdocConfigFile;
558
+ if (apiModelEnabled) {
559
+ tsdocConfigPath = await TsDocConfigBuilder.writeConfigFile(tsdocOptions ?? {}, (0, external_node_path_.dirname)(tsdocConfigOutputPath));
560
+ const { TSDocConfigFile } = await import("@microsoft/tsdoc-config");
561
+ tsdocConfigFile = TSDocConfigFile.loadForFolder((0, external_node_path_.dirname)(tsdocConfigPath));
562
+ }
454
563
  const { Extractor, ExtractorConfig } = await import("@microsoft/api-extractor");
455
564
  for (const [entryName, sourcePath] of entryPoints){
456
565
  const normalizedSourcePath = sourcePath.replace(/^\.\//, "");
@@ -472,6 +581,8 @@ async function bundleDtsFiles(options) {
472
581
  const tempBundledPath = (0, external_node_path_.join)(tempOutputDir, outputFileName);
473
582
  const generateApiModel = apiModelEnabled && "index" === entryName;
474
583
  const tempApiModelPath = generateApiModel ? (0, external_node_path_.join)(tempOutputDir, apiModelFilename) : void 0;
584
+ const generateTsdocMetadata = tsdocMetadataEnabled && "index" === entryName;
585
+ const tempTsdocMetadataPath = generateTsdocMetadata ? (0, external_node_path_.join)(tempOutputDir, tsdocMetadataFilename) : void 0;
475
586
  const extractorConfig = ExtractorConfig.prepare({
476
587
  configObject: {
477
588
  projectFolder: cwd,
@@ -487,11 +598,17 @@ async function bundleDtsFiles(options) {
487
598
  enabled: true,
488
599
  apiJsonFilePath: tempApiModelPath
489
600
  } : void 0,
601
+ tsdocMetadata: generateTsdocMetadata ? {
602
+ enabled: true,
603
+ tsdocMetadataFilePath: tempTsdocMetadataPath
604
+ } : void 0,
490
605
  bundledPackages: bundledPackages
491
606
  },
492
607
  packageJsonFullPath: (0, external_node_path_.join)(cwd, "package.json"),
493
- configObjectFullPath: void 0
608
+ configObjectFullPath: void 0,
609
+ tsdocConfigFile: tsdocConfigFile
494
610
  });
611
+ const collectedTsdocWarnings = [];
495
612
  const extractorResult = Extractor.invoke(extractorConfig, {
496
613
  localBuild: true,
497
614
  showVerboseMessages: false,
@@ -500,11 +617,35 @@ async function bundleDtsFiles(options) {
500
617
  message.logLevel = "none";
501
618
  return;
502
619
  }
503
- if (message.text?.includes("You have changed the public API signature")) message.logLevel = "none";
620
+ if (message.text?.includes("You have changed the public API signature")) {
621
+ message.logLevel = "none";
622
+ return;
623
+ }
624
+ const isTsdocMessage = message.messageId?.startsWith("tsdoc-");
625
+ if (isTsdocMessage && message.text) if ("none" === tsdocWarnings) message.logLevel = "none";
626
+ else {
627
+ collectedTsdocWarnings.push({
628
+ text: message.text,
629
+ sourceFilePath: message.sourceFilePath,
630
+ sourceFileLine: message.sourceFileLine,
631
+ sourceFileColumn: message.sourceFileColumn
632
+ });
633
+ message.logLevel = "none";
634
+ }
504
635
  }
505
636
  });
506
637
  if (!extractorResult.succeeded) throw new Error(`API Extractor failed for entry "${entryName}"`);
638
+ if (collectedTsdocWarnings.length > 0) {
639
+ const formatWarning = (warning)=>{
640
+ const location = warning.sourceFilePath ? `${picocolors.cyan((0, external_node_path_.relative)(cwd, warning.sourceFilePath))}${warning.sourceFileLine ? `:${warning.sourceFileLine}` : ""}${warning.sourceFileColumn ? `:${warning.sourceFileColumn}` : ""}` : null;
641
+ return location ? `${location}: ${picocolors.yellow(warning.text)}` : picocolors.yellow(warning.text);
642
+ };
643
+ const warningMessages = collectedTsdocWarnings.map(formatWarning).join("\n ");
644
+ if ("fail" === tsdocWarnings) throw new Error(`TSDoc validation failed for entry "${entryName}":\n ${warningMessages}`);
645
+ if ("log" === tsdocWarnings) core_logger.warn(`TSDoc warnings for entry "${entryName}":\n ${warningMessages}`);
646
+ }
507
647
  if (generateApiModel && tempApiModelPath) apiModelPath = tempApiModelPath;
648
+ if (generateTsdocMetadata && tempTsdocMetadataPath) tsdocMetadataPath = tempTsdocMetadataPath;
508
649
  if (banner || footer) {
509
650
  let content = await promises_readFile(tempBundledPath, "utf-8");
510
651
  if (banner) content = `${banner}\n${content}`;
@@ -513,9 +654,16 @@ async function bundleDtsFiles(options) {
513
654
  }
514
655
  bundledFiles.set(entryName, tempBundledPath);
515
656
  }
657
+ let persistedTsdocConfigPath;
658
+ if (tsdocConfigPath) if (shouldPersist) persistedTsdocConfigPath = tsdocConfigPath;
659
+ else try {
660
+ await unlink(tsdocConfigPath);
661
+ } catch {}
516
662
  return {
517
663
  bundledFiles,
518
- apiModelPath
664
+ apiModelPath,
665
+ tsdocMetadataPath,
666
+ tsdocConfigPath: persistedTsdocConfigPath
519
667
  };
520
668
  }
521
669
  function stripSourceMapComment(content) {
@@ -722,7 +870,7 @@ function runTsgo(options) {
722
870
  await mkdir(tempBundledDir, {
723
871
  recursive: true
724
872
  });
725
- const { bundledFiles, apiModelPath } = await bundleDtsFiles({
873
+ const { bundledFiles, apiModelPath, tsdocMetadataPath, tsdocConfigPath } = await bundleDtsFiles({
726
874
  cwd,
727
875
  tempDtsDir,
728
876
  tempOutputDir: tempBundledDir,
@@ -766,6 +914,13 @@ function runTsgo(options) {
766
914
  });
767
915
  const apiModelDestPath = (0, external_node_path_.join)(resolvedPath, apiModelFilename);
768
916
  await writeFile(apiModelDestPath, apiModelContent, "utf-8");
917
+ if (tsdocMetadataPath) {
918
+ const tsdocMetadataOption = "object" == typeof options.apiModel ? options.apiModel.tsdocMetadata : void 0;
919
+ const localTsdocFilename = "object" == typeof tsdocMetadataOption && tsdocMetadataOption.filename ? tsdocMetadataOption.filename : "tsdoc-metadata.json";
920
+ const tsdocContent = await promises_readFile(tsdocMetadataPath, "utf-8");
921
+ const tsdocDestPath = (0, external_node_path_.join)(resolvedPath, localTsdocFilename);
922
+ await writeFile(tsdocDestPath, tsdocContent, "utf-8");
923
+ }
769
924
  const packageJsonAsset = context.compilation.assets["package.json"];
770
925
  if (packageJsonAsset) {
771
926
  const rawContent = "function" == typeof packageJsonAsset.source ? packageJsonAsset.source() : packageJsonAsset.source;
@@ -773,9 +928,26 @@ function runTsgo(options) {
773
928
  const packageJsonDestPath = (0, external_node_path_.join)(resolvedPath, "package.json");
774
929
  await writeFile(packageJsonDestPath, packageJsonContent, "utf-8");
775
930
  }
776
- core_logger.info(`${picocolors.dim(`[${envId}]`)} Copied API model and package.json to: ${localPath}`);
931
+ const copiedFiles = tsdocMetadataPath ? "API model, tsdoc-metadata.json, and package.json" : "API model and package.json";
932
+ core_logger.info(`${picocolors.dim(`[${envId}]`)} Copied ${copiedFiles} to: ${localPath}`);
777
933
  }
778
934
  }
935
+ if (tsdocMetadataPath) {
936
+ const tsdocMetadataOption = "object" == typeof options.apiModel ? options.apiModel.tsdocMetadata : void 0;
937
+ const tsdocMetadataFilename = "object" == typeof tsdocMetadataOption && tsdocMetadataOption.filename ? tsdocMetadataOption.filename : "tsdoc-metadata.json";
938
+ const tsdocMetadataContent = await promises_readFile(tsdocMetadataPath, "utf-8");
939
+ const tsdocMetadataSource = new context.sources.OriginalSource(tsdocMetadataContent, tsdocMetadataFilename);
940
+ context.compilation.emitAsset(tsdocMetadataFilename, tsdocMetadataSource);
941
+ if (filesArray) filesArray.add(tsdocMetadataFilename);
942
+ core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted TSDoc metadata: ${tsdocMetadataFilename}`);
943
+ }
944
+ if (tsdocConfigPath) {
945
+ const tsdocConfigContent = await promises_readFile(tsdocConfigPath, "utf-8");
946
+ const tsdocConfigSource = new context.sources.OriginalSource(tsdocConfigContent, "tsdoc.json");
947
+ context.compilation.emitAsset("tsdoc.json", tsdocConfigSource);
948
+ if (filesArray) filesArray.add("!tsdoc.json");
949
+ core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted TSDoc config: tsdoc.json (excluded from npm publish)`);
950
+ }
779
951
  for (const [entryName] of bundledFiles){
780
952
  const bundledFileName = `${entryName}.d.ts`;
781
953
  const mapFileName = `${bundledFileName}.map`;
@@ -1514,4 +1686,4 @@ const PackageJsonTransformPlugin = (options = {})=>{
1514
1686
  });
1515
1687
  }
1516
1688
  }
1517
- export { AutoEntryPlugin, DtsPlugin, EntryExtractor, FilesArrayPlugin, NodeLibraryBuilder, PackageJsonTransformPlugin, PackageJsonTransformer, PnpmCatalog, collectDtsFiles, ensureTempDeclarationDir, findTsConfig, generateTsgoArgs, getTsgoBinPath, getUnscopedPackageName, stripSourceMapComment };
1689
+ export { AutoEntryPlugin, DtsPlugin, EntryExtractor, FilesArrayPlugin, NodeLibraryBuilder, PackageJsonTransformPlugin, PackageJsonTransformer, PnpmCatalog, TsDocConfigBuilder, collectDtsFiles, ensureTempDeclarationDir, findTsConfig, generateTsgoArgs, getTsgoBinPath, getUnscopedPackageName, stripSourceMapComment };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/rslib-builder",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "RSlib-based build system for Node.js libraries with automatic package.json transformation, TypeScript declaration bundling, and multi-target support",
6
6
  "homepage": "https://github.com/savvy-web/rslib-builder",
@@ -31,6 +31,8 @@
31
31
  "./tsconfig/node/ecma/bundleless.json": "./public/tsconfig/node/ecma/bundleless.json"
32
32
  },
33
33
  "dependencies": {
34
+ "@microsoft/tsdoc": "^0.16.0",
35
+ "@microsoft/tsdoc-config": "^0.18.0",
34
36
  "@pnpm/exportable-manifest": "^1000.3.0",
35
37
  "glob": "^13.0.0",
36
38
  "markdownlint-cli2": "^0.20.0",
@@ -80,6 +82,7 @@
80
82
  "package.json",
81
83
  "rslib-runtime.js",
82
84
  "tsconfig/node/ecma/lib.json",
83
- "tsconfig/root.json"
85
+ "tsconfig/root.json",
86
+ "tsdoc-metadata.json"
84
87
  ]
85
88
  }
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.55.2"
9
+ }
10
+ ]
11
+ }