@savvy-web/rslib-builder 0.1.2 → 0.2.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.
package/README.md CHANGED
@@ -50,7 +50,7 @@ Extend the provided tsconfig for optimal settings:
50
50
  ```jsonc
51
51
  // tsconfig.json
52
52
  {
53
- "extends": "@savvy-web/rslib-builder/tsconfig/node/ecma/lib.json",
53
+ "extends": "@savvy-web/rslib-builder/tsconfig/ecma/lib.json",
54
54
  "compilerOptions": {
55
55
  "outDir": "dist"
56
56
  }
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
@@ -672,6 +698,10 @@ export declare class PackageJsonTransformer {
672
698
  /**
673
699
  * Transforms the bin field for build output.
674
700
  *
701
+ * @remarks
702
+ * TypeScript bin entries are compiled to `./bin/{command-name}.js` by RSlib.
703
+ * Non-TypeScript entries (shell scripts, compiled JS) are preserved as-is.
704
+ *
675
705
  * @param bin - The bin field from package.json
676
706
  * @returns The transformed bin field
677
707
  */
@@ -680,9 +710,9 @@ export declare class PackageJsonTransformer {
680
710
  * Performs the complete package.json transformation.
681
711
  *
682
712
  * @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
713
+ * @param context - Transform context with properties:
714
+ * - `isProduction`: Whether this is a production build
715
+ * - `customTransform`: Optional custom transform function
686
716
  * @returns The transformed package.json
687
717
  */
688
718
  transform(packageJson: PackageJson, context?: {
@@ -723,7 +753,7 @@ export declare interface PackageJsonTransformOptions {
723
753
  */
724
754
  processTSExports?: boolean;
725
755
  /**
726
- * Whether to collapse index files (./foo/index.ts -> ./foo.js).
756
+ * Whether to collapse index files (./foo/index.ts becomes ./foo.js).
727
757
  * This is typically true for bundled builds.
728
758
  * @defaultValue false
729
759
  */
@@ -816,7 +846,7 @@ export declare class PnpmCatalog {
816
846
  * @param dir - The directory containing the package (defaults to cwd)
817
847
  * @returns The resolved package.json
818
848
  *
819
- * @throws {Error} When resolution fails for critical dependencies
849
+ * @throws When resolution fails for critical dependencies
820
850
  */
821
851
  resolvePackageJson(packageJson: PackageJson, dir?: string): Promise<PackageJson>;
822
852
  /**
@@ -859,4 +889,203 @@ export declare type TransformPackageJsonFn = (context: {
859
889
  pkg: PackageJson;
860
890
  }) => PackageJson;
861
891
 
892
+ /**
893
+ * Builder for TSDoc configuration files.
894
+ * Handles tag group expansion, config generation, and file persistence.
895
+ * @public
896
+ */
897
+ export declare class TsDocConfigBuilder {
898
+ /** All available TSDoc tag groups. */
899
+ static readonly ALL_GROUPS: TsDocTagGroup[];
900
+ /** Maps group names to TSDoc Standardization enum values. */
901
+ private static readonly GROUP_TO_STANDARDIZATION;
902
+ /**
903
+ * Standard TSDoc tag definitions organized by standardization group.
904
+ * Lazily computed from `\@microsoft/tsdoc` StandardTags.
905
+ */
906
+ static readonly TAG_GROUPS: Record<TsDocTagGroup, TsDocTagDefinition[]>;
907
+ /**
908
+ * Detects if running in a CI environment.
909
+ * @returns true if CI or GITHUB_ACTIONS environment variable is "true"
910
+ */
911
+ static isCI(): boolean;
912
+ /**
913
+ * Gets standard TSDoc tag definitions for a specific group.
914
+ * Uses StandardTags from `\@microsoft/tsdoc` package.
915
+ */
916
+ static getTagsForGroup(group: TsDocTagGroup): TsDocTagDefinition[];
917
+ /**
918
+ * Determines if the TSDoc config should be persisted to disk.
919
+ * @param persistConfig - The persistConfig option value
920
+ * @returns true if the config should be persisted
921
+ */
922
+ static shouldPersist(persistConfig: boolean | PathLike | undefined): boolean;
923
+ /**
924
+ * Gets the output path for the tsdoc.json file.
925
+ * @param persistConfig - The persistConfig option value
926
+ * @param cwd - The current working directory
927
+ * @returns The absolute path where tsdoc.json should be written
928
+ */
929
+ static getConfigPath(persistConfig: boolean | PathLike | undefined, cwd: string): string;
930
+ /**
931
+ * Builds the complete TSDoc configuration from options.
932
+ *
933
+ * @remarks
934
+ * When all groups are enabled (default), returns `useStandardTags: true` to signal
935
+ * that the generated config should use `noStandardTags: false` and let TSDoc
936
+ * automatically load all standard tags. However, `supportForTags` is still populated
937
+ * because API Extractor requires explicit support declarations for each tag.
938
+ *
939
+ * When a subset of groups is specified, returns `useStandardTags: false` to signal
940
+ * that we must explicitly define which tags to include via `noStandardTags: true`.
941
+ */
942
+ static build(options?: TsDocOptions): {
943
+ tagDefinitions: TsDocTagDefinition[];
944
+ supportForTags: Record<string, boolean>;
945
+ useStandardTags: boolean;
946
+ };
947
+ /**
948
+ * Generates a tsdoc.json file from options.
949
+ *
950
+ * @remarks
951
+ * When all groups are enabled (default), generates a minimal config with
952
+ * `noStandardTags: false` so TSDoc automatically loads all standard tags.
953
+ * Only custom tags need to be defined in this case.
954
+ *
955
+ * When a subset of groups is specified, generates a config with
956
+ * `noStandardTags: true` and explicitly defines only the tags from
957
+ * the enabled groups.
958
+ */
959
+ static writeConfigFile(options: TsDocOptions | undefined, outputDir: string): Promise<string>;
960
+ /** Converts TSDocTagSyntaxKind enum to string format. */
961
+ private static syntaxKindToString;
962
+ }
963
+
964
+ /**
965
+ * Options for tsdoc-metadata.json generation.
966
+ * @public
967
+ */
968
+ export declare interface TsDocMetadataOptions {
969
+ /**
970
+ * Whether to generate tsdoc-metadata.json.
971
+ * @defaultValue true (when apiModel is enabled)
972
+ */
973
+ enabled?: boolean;
974
+ /**
975
+ * Custom filename for the TSDoc metadata file.
976
+ * @defaultValue "tsdoc-metadata.json"
977
+ */
978
+ filename?: string;
979
+ }
980
+
981
+ /**
982
+ * TSDoc configuration options for API Extractor.
983
+ * @remarks
984
+ * Provides ergonomic defaults - standard tags are auto-enabled via `groups`,
985
+ * custom tags are auto-supported, and `supportForTags` is only needed to
986
+ * disable specific tags.
987
+ *
988
+ * **Config optimization:** When all groups are enabled (default), the generated
989
+ * `tsdoc.json` uses `noStandardTags: false` to let TSDoc automatically load
990
+ * all standard tags, producing a minimal config file. When a subset of groups
991
+ * is specified, `noStandardTags: true` is used and only the enabled groups'
992
+ * tags are explicitly defined.
993
+ *
994
+ * @public
995
+ */
996
+ export declare interface TsDocOptions {
997
+ /**
998
+ * TSDoc tag groups to enable. Each group includes predefined standard tags
999
+ * from the official `\@microsoft/tsdoc` package.
1000
+ * - "core": Essential TSDoc tags (\@param, \@returns, \@remarks, \@deprecated, etc.)
1001
+ * - "extended": Additional tags (\@example, \@defaultValue, \@see, \@throws, etc.)
1002
+ * - "discretionary": Release stage tags (\@alpha, \@beta, \@public, \@internal)
1003
+ *
1004
+ * @remarks
1005
+ * When all groups are enabled (the default), the generated config uses
1006
+ * `noStandardTags: false` and TSDoc loads standard tags automatically.
1007
+ * When a subset is specified, `noStandardTags: true` is used and only
1008
+ * the tags from enabled groups are defined.
1009
+ *
1010
+ * @defaultValue ["core", "extended", "discretionary"]
1011
+ */
1012
+ groups?: TsDocTagGroup[];
1013
+ /**
1014
+ * Custom TSDoc tag definitions beyond the standard groups.
1015
+ * These are automatically added to supportForTags (no need to declare twice).
1016
+ *
1017
+ * @example
1018
+ * ```typescript
1019
+ * tagDefinitions: [
1020
+ * { tagName: "@error", syntaxKind: "inline" },
1021
+ * { tagName: "@category", syntaxKind: "block", allowMultiple: false }
1022
+ * ]
1023
+ * ```
1024
+ */
1025
+ tagDefinitions?: TsDocTagDefinition[];
1026
+ /**
1027
+ * Override support for specific tags. Only needed to DISABLE tags.
1028
+ * Tags from enabled groups and custom tagDefinitions are auto-supported.
1029
+ *
1030
+ * @example
1031
+ * ```typescript
1032
+ * // Disable @beta even though "extended" group is enabled
1033
+ * supportForTags: { "@beta": false }
1034
+ * ```
1035
+ */
1036
+ supportForTags?: Record<string, boolean>;
1037
+ /**
1038
+ * Persist tsdoc.json to disk for tool integration (ESLint, IDEs).
1039
+ * - `true`: Write to project root as "tsdoc.json"
1040
+ * - `PathLike`: Write to specified path
1041
+ * - `false`: Clean up after API Extractor
1042
+ *
1043
+ * @defaultValue `true` when `CI` and `GITHUB_ACTIONS` env vars are not "true",
1044
+ * `false` otherwise (CI environments)
1045
+ */
1046
+ persistConfig?: boolean | PathLike;
1047
+ /**
1048
+ * How to handle TSDoc validation warnings from API Extractor.
1049
+ * - `"log"`: Show warnings in the console but continue the build
1050
+ * - `"fail"`: Show warnings and fail the build if any are found
1051
+ * - `"none"`: Suppress TSDoc warnings entirely
1052
+ *
1053
+ * @remarks
1054
+ * TSDoc warnings include unknown tags, malformed syntax, and other
1055
+ * documentation issues detected by API Extractor during processing.
1056
+ *
1057
+ * @example
1058
+ * ```typescript
1059
+ * // Fail build on any TSDoc issues (CI default)
1060
+ * warnings: "fail"
1061
+ *
1062
+ * // Show warnings but continue build (local default)
1063
+ * warnings: "log"
1064
+ * ```
1065
+ *
1066
+ * @defaultValue `"fail"` in CI environments (`CI` or `GITHUB_ACTIONS` env vars),
1067
+ * `"log"` otherwise
1068
+ */
1069
+ warnings?: "log" | "fail" | "none";
1070
+ }
1071
+
1072
+ /**
1073
+ * TSDoc tag definition for custom documentation tags.
1074
+ * @public
1075
+ */
1076
+ export declare interface TsDocTagDefinition {
1077
+ /** The tag name including the at-sign prefix (e.g., `\@error`, `\@category`) */
1078
+ tagName: string;
1079
+ /** How the tag is parsed: "block" | "inline" | "modifier" */
1080
+ syntaxKind: "block" | "inline" | "modifier";
1081
+ /** Whether the tag can appear multiple times on a declaration */
1082
+ allowMultiple?: boolean;
1083
+ }
1084
+
1085
+ /**
1086
+ * TSDoc standardization groups for predefined tag sets.
1087
+ * @public
1088
+ */
1089
+ export declare type TsDocTagGroup = "core" | "extended" | "discretionary";
1090
+
862
1091
  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";
@@ -268,7 +269,7 @@ var root_namespaceObject = JSON.parse('{"$schema":"https://json.schemastore.org/
268
269
  const requireCJS = createRequire(import.meta.url);
269
270
  const jsonImports = new Map([
270
271
  [
271
- (0, external_node_path_.join)(import.meta.dirname, "../public/tsconfig/node/ecma/lib.json"),
272
+ (0, external_node_path_.join)(import.meta.dirname, "../public/tsconfig/ecma/lib.json"),
272
273
  lib_namespaceObject
273
274
  ],
274
275
  [
@@ -385,7 +386,7 @@ class LibraryTSConfigFile extends TSConfigFile {
385
386
  }
386
387
  }
387
388
  const Root = new TSConfigFile("Root configuration for workspace setup", (0, external_node_path_.join)(import.meta.dirname, "../public/tsconfig/root.json"));
388
- const NodeEcmaLib = new LibraryTSConfigFile("ECMAScript library build configuration", (0, external_node_path_.join)(import.meta.dirname, "../public/tsconfig/node/ecma/lib.json"));
389
+ const NodeEcmaLib = new LibraryTSConfigFile("ECMAScript library build configuration", (0, external_node_path_.join)(import.meta.dirname, "../public/tsconfig/ecma/lib.json"));
389
390
  const TSConfigs = {
390
391
  root: Root,
391
392
  node: {
@@ -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,
@@ -752,30 +900,37 @@ function runTsgo(options) {
752
900
  context.compilation.emitAsset(apiModelFilename, apiModelSource);
753
901
  if (filesArray) filesArray.add(`!${apiModelFilename}`);
754
902
  core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted API model: ${apiModelFilename} (excluded from npm publish)`);
755
- const isCI = "true" === process.env.GITHUB_ACTIONS || "true" === process.env.CI;
756
903
  const localPaths = "object" == typeof options.apiModel ? options.apiModel.localPaths : void 0;
757
- if (localPaths && localPaths.length > 0 && !isCI) for (const localPath of localPaths){
758
- const resolvedPath = (0, external_node_path_.join)(cwd, localPath);
759
- const parentDir = (0, external_node_path_.dirname)(resolvedPath);
760
- if (!existsSync(parentDir)) {
761
- core_logger.warn(`${picocolors.dim(`[${envId}]`)} Skipping local path: parent directory does not exist: ${parentDir}`);
762
- continue;
763
- }
764
- await mkdir(resolvedPath, {
765
- recursive: true
904
+ const isCI = "true" === process.env.GITHUB_ACTIONS || "true" === process.env.CI;
905
+ if (localPaths && localPaths.length > 0 && !isCI) {
906
+ const tsdocMetadataOption = "object" == typeof options.apiModel ? options.apiModel.tsdocMetadata : void 0;
907
+ const localTsdocFilename = "object" == typeof tsdocMetadataOption && tsdocMetadataOption.filename ? tsdocMetadataOption.filename : "tsdoc-metadata.json";
908
+ api.expose("dts-local-paths-data", {
909
+ localPaths,
910
+ apiModelFilename,
911
+ localTsdocFilename,
912
+ hasTsdocMetadata: !!tsdocMetadataPath,
913
+ cwd,
914
+ distPath: `dist/${envId}`
766
915
  });
767
- const apiModelDestPath = (0, external_node_path_.join)(resolvedPath, apiModelFilename);
768
- await writeFile(apiModelDestPath, apiModelContent, "utf-8");
769
- const packageJsonAsset = context.compilation.assets["package.json"];
770
- if (packageJsonAsset) {
771
- const rawContent = "function" == typeof packageJsonAsset.source ? packageJsonAsset.source() : packageJsonAsset.source;
772
- const packageJsonContent = "string" == typeof rawContent ? rawContent : rawContent instanceof Buffer ? rawContent.toString("utf-8") : String(rawContent);
773
- const packageJsonDestPath = (0, external_node_path_.join)(resolvedPath, "package.json");
774
- await writeFile(packageJsonDestPath, packageJsonContent, "utf-8");
775
- }
776
- core_logger.info(`${picocolors.dim(`[${envId}]`)} Copied API model and package.json to: ${localPath}`);
777
916
  }
778
917
  }
918
+ if (tsdocMetadataPath) {
919
+ const tsdocMetadataOption = "object" == typeof options.apiModel ? options.apiModel.tsdocMetadata : void 0;
920
+ const tsdocMetadataFilename = "object" == typeof tsdocMetadataOption && tsdocMetadataOption.filename ? tsdocMetadataOption.filename : "tsdoc-metadata.json";
921
+ const tsdocMetadataContent = await promises_readFile(tsdocMetadataPath, "utf-8");
922
+ const tsdocMetadataSource = new context.sources.OriginalSource(tsdocMetadataContent, tsdocMetadataFilename);
923
+ context.compilation.emitAsset(tsdocMetadataFilename, tsdocMetadataSource);
924
+ if (filesArray) filesArray.add(tsdocMetadataFilename);
925
+ core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted TSDoc metadata: ${tsdocMetadataFilename}`);
926
+ }
927
+ if (tsdocConfigPath) {
928
+ const tsdocConfigContent = await promises_readFile(tsdocConfigPath, "utf-8");
929
+ const tsdocConfigSource = new context.sources.OriginalSource(tsdocConfigContent, "tsdoc.json");
930
+ context.compilation.emitAsset("tsdoc.json", tsdocConfigSource);
931
+ if (filesArray) filesArray.add("!tsdoc.json");
932
+ core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted TSDoc config: tsdoc.json (excluded from npm publish)`);
933
+ }
779
934
  for (const [entryName] of bundledFiles){
780
935
  const bundledFileName = `${entryName}.d.ts`;
781
936
  const mapFileName = `${bundledFileName}.map`;
@@ -830,6 +985,50 @@ function runTsgo(options) {
830
985
  } else if (assetName.endsWith(".d.ts.map")) assetsToDelete.push(assetName);
831
986
  for (const assetName of assetsToDelete)delete compiler.compilation.assets[assetName];
832
987
  });
988
+ api.onCloseBuild(async ()=>{
989
+ const localPathsData = api.useExposed("dts-local-paths-data");
990
+ if (!localPathsData) return;
991
+ const { localPaths, apiModelFilename, localTsdocFilename, hasTsdocMetadata, cwd, distPath } = localPathsData;
992
+ const distDir = (0, external_node_path_.join)(cwd, distPath);
993
+ for (const localPath of localPaths){
994
+ const resolvedPath = (0, external_node_path_.join)(cwd, localPath);
995
+ const parentDir = (0, external_node_path_.dirname)(resolvedPath);
996
+ if (!existsSync(parentDir)) {
997
+ core_logger.warn(`Skipping local path: parent directory does not exist: ${parentDir}`);
998
+ continue;
999
+ }
1000
+ const filesToCopy = [];
1001
+ const apiModelSrc = (0, external_node_path_.join)(distDir, apiModelFilename);
1002
+ if (existsSync(apiModelSrc)) filesToCopy.push({
1003
+ src: apiModelSrc,
1004
+ dest: (0, external_node_path_.join)(resolvedPath, apiModelFilename),
1005
+ name: apiModelFilename
1006
+ });
1007
+ if (hasTsdocMetadata) {
1008
+ const tsdocSrc = (0, external_node_path_.join)(distDir, localTsdocFilename);
1009
+ if (existsSync(tsdocSrc)) filesToCopy.push({
1010
+ src: tsdocSrc,
1011
+ dest: (0, external_node_path_.join)(resolvedPath, localTsdocFilename),
1012
+ name: localTsdocFilename
1013
+ });
1014
+ }
1015
+ const packageJsonSrc = (0, external_node_path_.join)(distDir, "package.json");
1016
+ if (existsSync(packageJsonSrc)) filesToCopy.push({
1017
+ src: packageJsonSrc,
1018
+ dest: (0, external_node_path_.join)(resolvedPath, "package.json"),
1019
+ name: "package.json"
1020
+ });
1021
+ await mkdir(resolvedPath, {
1022
+ recursive: true
1023
+ });
1024
+ for (const file of filesToCopy){
1025
+ const content = await promises_readFile(file.src, "utf-8");
1026
+ await writeFile(file.dest, content, "utf-8");
1027
+ }
1028
+ const fileNames = filesToCopy.map((f)=>f.name).join(", ");
1029
+ core_logger.info(`Copied ${fileNames} to: ${localPath}`);
1030
+ }
1031
+ });
833
1032
  }
834
1033
  };
835
1034
  };
@@ -1115,9 +1314,9 @@ class PackageJsonTransformer {
1115
1314
  }
1116
1315
  transformExportPath(path) {
1117
1316
  let transformedPath = path;
1317
+ if (transformedPath.startsWith("./src/")) transformedPath = `./${transformedPath.slice(6)}`;
1118
1318
  if (transformedPath.startsWith("./exports/")) transformedPath = `./${transformedPath.slice(10)}`;
1119
- else if (transformedPath.startsWith("./public/")) transformedPath = `./${transformedPath.slice(9)}`;
1120
- else if (transformedPath.startsWith("./src/")) transformedPath = `./${transformedPath.slice(6)}`;
1319
+ if (transformedPath.startsWith("./public/")) transformedPath = `./${transformedPath.slice(9)}`;
1121
1320
  if (this.options.processTSExports) {
1122
1321
  const { collapseIndex } = this.options;
1123
1322
  if (collapseIndex && transformedPath.endsWith("/index.ts") && "./index.ts" !== transformedPath) transformedPath = `${transformedPath.slice(0, -9)}.js`;
@@ -1143,10 +1342,14 @@ class PackageJsonTransformer {
1143
1342
  return exports;
1144
1343
  }
1145
1344
  transformBin(bin) {
1146
- if ("string" == typeof bin) return this.transformExportPath(bin);
1345
+ if ("string" == typeof bin) {
1346
+ if (bin.endsWith(".ts") || bin.endsWith(".tsx")) return "./bin/cli.js";
1347
+ return bin;
1348
+ }
1147
1349
  if (bin && "object" == typeof bin) {
1148
1350
  const transformed = {};
1149
- for (const [command, path] of Object.entries(bin))if (void 0 !== path) transformed[command] = this.transformExportPath(path);
1351
+ for (const [command, path] of Object.entries(bin))if (void 0 !== path) if (path.endsWith(".ts") || path.endsWith(".tsx")) transformed[command] = `./bin/${command}.js`;
1352
+ else transformed[command] = path;
1150
1353
  return transformed;
1151
1354
  }
1152
1355
  return bin;
@@ -1228,9 +1431,9 @@ class PackageJsonTransformer {
1228
1431
  }
1229
1432
  function transformExportPath(path, processTSExports = true, collapseIndex = false) {
1230
1433
  let transformedPath = path;
1434
+ if (transformedPath.startsWith("./src/")) transformedPath = `./${transformedPath.slice(6)}`;
1231
1435
  if (transformedPath.startsWith("./exports/")) transformedPath = `./${transformedPath.slice(10)}`;
1232
- else if (transformedPath.startsWith("./public/")) transformedPath = `./${transformedPath.slice(9)}`;
1233
- else if (transformedPath.startsWith("./src/")) transformedPath = `./${transformedPath.slice(6)}`;
1436
+ if (transformedPath.startsWith("./public/")) transformedPath = `./${transformedPath.slice(9)}`;
1234
1437
  if (processTSExports) {
1235
1438
  if (collapseIndex && transformedPath.endsWith("/index.ts") && "./index.ts" !== transformedPath) transformedPath = `${transformedPath.slice(0, -9)}.js`;
1236
1439
  else if (collapseIndex && transformedPath.endsWith("/index.tsx") && "./index.tsx" !== transformedPath) transformedPath = `${transformedPath.slice(0, -10)}.js`;
@@ -1244,11 +1447,15 @@ function createTypePath(jsPath, collapseIndex = true) {
1244
1447
  if (jsPath.endsWith(".js")) return `${jsPath.slice(0, -3)}.d.ts`;
1245
1448
  return `${jsPath}.d.ts`;
1246
1449
  }
1247
- function transformPackageBin(bin, processTSExports = true) {
1248
- if ("string" == typeof bin) return transformExportPath(bin, processTSExports);
1450
+ function transformPackageBin(bin, _processTSExports = true) {
1451
+ if ("string" == typeof bin) {
1452
+ if (bin.endsWith(".ts") || bin.endsWith(".tsx")) return "./bin/cli.js";
1453
+ return bin;
1454
+ }
1249
1455
  if (bin && "object" == typeof bin) {
1250
1456
  const transformed = {};
1251
- for (const [command, path] of Object.entries(bin))if (void 0 !== path) transformed[command] = transformExportPath(path, processTSExports);
1457
+ for (const [command, path] of Object.entries(bin))if (void 0 !== path) if (path.endsWith(".ts") || path.endsWith(".tsx")) transformed[command] = `./bin/${command}.js`;
1458
+ else transformed[command] = path;
1252
1459
  return transformed;
1253
1460
  }
1254
1461
  return bin;
@@ -1514,4 +1721,4 @@ const PackageJsonTransformPlugin = (options = {})=>{
1514
1721
  });
1515
1722
  }
1516
1723
  }
1517
- export { AutoEntryPlugin, DtsPlugin, EntryExtractor, FilesArrayPlugin, NodeLibraryBuilder, PackageJsonTransformPlugin, PackageJsonTransformer, PnpmCatalog, collectDtsFiles, ensureTempDeclarationDir, findTsConfig, generateTsgoArgs, getTsgoBinPath, getUnscopedPackageName, stripSourceMapComment };
1724
+ 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.1",
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",
@@ -24,16 +24,14 @@
24
24
  "types": "./index.d.ts",
25
25
  "import": "./index.js"
26
26
  },
27
- "./tsconfig/root.json": "./public/tsconfig/root.json",
28
- "./tsconfig/node/ecma/lib.json": "./public/tsconfig/node/ecma/lib.json",
29
- "./tsconfig/node/ecma/lib-compat.json": "./public/tsconfig/node/ecma/lib-compat.json",
30
- "./tsconfig/node/ecma/bundle.json": "./public/tsconfig/node/ecma/bundle.json",
31
- "./tsconfig/node/ecma/bundleless.json": "./public/tsconfig/node/ecma/bundleless.json"
27
+ "./tsconfig/root.json": "./tsconfig/root.json",
28
+ "./tsconfig/ecma/lib.json": "./tsconfig/ecma/lib.json"
32
29
  },
33
30
  "dependencies": {
31
+ "@microsoft/tsdoc": "^0.16.0",
32
+ "@microsoft/tsdoc-config": "^0.18.0",
34
33
  "@pnpm/exportable-manifest": "^1000.3.0",
35
34
  "glob": "^13.0.0",
36
- "markdownlint-cli2": "^0.20.0",
37
35
  "picocolors": "^1.1.1",
38
36
  "sort-package-json": "^3.6.0",
39
37
  "tmp": "^0.2.5",
@@ -57,20 +55,6 @@
57
55
  "optional": false
58
56
  }
59
57
  },
60
- "devEngines": {
61
- "packageManager": {
62
- "name": "pnpm",
63
- "version": "10.28.0",
64
- "onFail": "ignore"
65
- },
66
- "runtime": [
67
- {
68
- "name": "node",
69
- "version": "24.11.0",
70
- "onFail": "ignore"
71
- }
72
- ]
73
- },
74
58
  "files": [
75
59
  "!rslib-builder.api.json",
76
60
  "LICENSE",
@@ -79,7 +63,8 @@
79
63
  "index.js",
80
64
  "package.json",
81
65
  "rslib-runtime.js",
82
- "tsconfig/node/ecma/lib.json",
83
- "tsconfig/root.json"
66
+ "tsconfig/ecma/lib.json",
67
+ "tsconfig/root.json",
68
+ "tsdoc-metadata.json"
84
69
  ]
85
70
  }
@@ -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
+ }
File without changes