@savvy-web/rslib-builder 0.1.1 → 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.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,101 @@ 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
+ }
490
+ function getUnscopedPackageName(name) {
491
+ return name.startsWith("@") ? name.split("/")[1] ?? name : name;
492
+ }
397
493
  function getTsgoBinPath() {
398
494
  const cwd = process.cwd();
399
495
  const localTsgoBin = (0, external_node_path_.join)(cwd, "node_modules", ".bin", "tsgo");
@@ -445,9 +541,25 @@ async function bundleDtsFiles(options) {
445
541
  const { cwd, tempDtsDir, tempOutputDir, tsconfigPath, bundledPackages, entryPoints, banner, footer, apiModel } = options;
446
542
  const bundledFiles = new Map();
447
543
  let apiModelPath;
544
+ let tsdocMetadataPath;
448
545
  const apiModelEnabled = true === apiModel || "object" == typeof apiModel && (void 0 === apiModel.enabled || true === apiModel.enabled);
449
- const apiModelFilename = "object" == typeof apiModel && apiModel.filename ? apiModel.filename : "api.model.json";
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";
450
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
+ }
451
563
  const { Extractor, ExtractorConfig } = await import("@microsoft/api-extractor");
452
564
  for (const [entryName, sourcePath] of entryPoints){
453
565
  const normalizedSourcePath = sourcePath.replace(/^\.\//, "");
@@ -469,6 +581,8 @@ async function bundleDtsFiles(options) {
469
581
  const tempBundledPath = (0, external_node_path_.join)(tempOutputDir, outputFileName);
470
582
  const generateApiModel = apiModelEnabled && "index" === entryName;
471
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;
472
586
  const extractorConfig = ExtractorConfig.prepare({
473
587
  configObject: {
474
588
  projectFolder: cwd,
@@ -484,11 +598,17 @@ async function bundleDtsFiles(options) {
484
598
  enabled: true,
485
599
  apiJsonFilePath: tempApiModelPath
486
600
  } : void 0,
601
+ tsdocMetadata: generateTsdocMetadata ? {
602
+ enabled: true,
603
+ tsdocMetadataFilePath: tempTsdocMetadataPath
604
+ } : void 0,
487
605
  bundledPackages: bundledPackages
488
606
  },
489
607
  packageJsonFullPath: (0, external_node_path_.join)(cwd, "package.json"),
490
- configObjectFullPath: void 0
608
+ configObjectFullPath: void 0,
609
+ tsdocConfigFile: tsdocConfigFile
491
610
  });
611
+ const collectedTsdocWarnings = [];
492
612
  const extractorResult = Extractor.invoke(extractorConfig, {
493
613
  localBuild: true,
494
614
  showVerboseMessages: false,
@@ -497,11 +617,35 @@ async function bundleDtsFiles(options) {
497
617
  message.logLevel = "none";
498
618
  return;
499
619
  }
500
- 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
+ }
501
635
  }
502
636
  });
503
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
+ }
504
647
  if (generateApiModel && tempApiModelPath) apiModelPath = tempApiModelPath;
648
+ if (generateTsdocMetadata && tempTsdocMetadataPath) tsdocMetadataPath = tempTsdocMetadataPath;
505
649
  if (banner || footer) {
506
650
  let content = await promises_readFile(tempBundledPath, "utf-8");
507
651
  if (banner) content = `${banner}\n${content}`;
@@ -510,9 +654,16 @@ async function bundleDtsFiles(options) {
510
654
  }
511
655
  bundledFiles.set(entryName, tempBundledPath);
512
656
  }
657
+ let persistedTsdocConfigPath;
658
+ if (tsdocConfigPath) if (shouldPersist) persistedTsdocConfigPath = tsdocConfigPath;
659
+ else try {
660
+ await unlink(tsdocConfigPath);
661
+ } catch {}
513
662
  return {
514
663
  bundledFiles,
515
- apiModelPath
664
+ apiModelPath,
665
+ tsdocMetadataPath,
666
+ tsdocConfigPath: persistedTsdocConfigPath
516
667
  };
517
668
  }
518
669
  function stripSourceMapComment(content) {
@@ -719,7 +870,7 @@ function runTsgo(options) {
719
870
  await mkdir(tempBundledDir, {
720
871
  recursive: true
721
872
  });
722
- const { bundledFiles, apiModelPath } = await bundleDtsFiles({
873
+ const { bundledFiles, apiModelPath, tsdocMetadataPath, tsdocConfigPath } = await bundleDtsFiles({
723
874
  cwd,
724
875
  tempDtsDir,
725
876
  tempOutputDir: tempBundledDir,
@@ -742,20 +893,13 @@ function runTsgo(options) {
742
893
  }
743
894
  core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted ${emittedCount} bundled declaration file${1 === emittedCount ? "" : "s"} through asset pipeline`);
744
895
  if (apiModelPath) {
745
- const apiModelFilename = "object" == typeof options.apiModel && options.apiModel.filename ? options.apiModel.filename : "api.model.json";
896
+ const defaultApiModelFilename = packageJson.name ? `${getUnscopedPackageName(packageJson.name)}.api.json` : "api.json";
897
+ const apiModelFilename = "object" == typeof options.apiModel && options.apiModel.filename ? options.apiModel.filename : defaultApiModelFilename;
746
898
  const apiModelContent = await promises_readFile(apiModelPath, "utf-8");
747
899
  const apiModelSource = new context.sources.OriginalSource(apiModelContent, apiModelFilename);
748
900
  context.compilation.emitAsset(apiModelFilename, apiModelSource);
749
- if (filesArray) filesArray.add(apiModelFilename);
750
- core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted API model: ${apiModelFilename}`);
751
- const shouldAddNpmIgnore = true === options.apiModel || "object" == typeof options.apiModel && false !== options.apiModel.npmIgnore;
752
- if (shouldAddNpmIgnore) {
753
- const npmIgnoreContent = `# Exclude API model from npm publish (used by internal tooling)\n${apiModelFilename}\n`;
754
- const npmIgnoreSource = new context.sources.OriginalSource(npmIgnoreContent, ".npmignore");
755
- context.compilation.emitAsset(".npmignore", npmIgnoreSource);
756
- if (filesArray) filesArray.add(".npmignore");
757
- core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted .npmignore to exclude ${apiModelFilename}`);
758
- }
901
+ if (filesArray) filesArray.add(`!${apiModelFilename}`);
902
+ core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted API model: ${apiModelFilename} (excluded from npm publish)`);
759
903
  const isCI = "true" === process.env.GITHUB_ACTIONS || "true" === process.env.CI;
760
904
  const localPaths = "object" == typeof options.apiModel ? options.apiModel.localPaths : void 0;
761
905
  if (localPaths && localPaths.length > 0 && !isCI) for (const localPath of localPaths){
@@ -770,6 +914,13 @@ function runTsgo(options) {
770
914
  });
771
915
  const apiModelDestPath = (0, external_node_path_.join)(resolvedPath, apiModelFilename);
772
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
+ }
773
924
  const packageJsonAsset = context.compilation.assets["package.json"];
774
925
  if (packageJsonAsset) {
775
926
  const rawContent = "function" == typeof packageJsonAsset.source ? packageJsonAsset.source() : packageJsonAsset.source;
@@ -777,9 +928,26 @@ function runTsgo(options) {
777
928
  const packageJsonDestPath = (0, external_node_path_.join)(resolvedPath, "package.json");
778
929
  await writeFile(packageJsonDestPath, packageJsonContent, "utf-8");
779
930
  }
780
- 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}`);
781
933
  }
782
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
+ }
783
951
  for (const [entryName] of bundledFiles){
784
952
  const bundledFileName = `${entryName}.d.ts`;
785
953
  const mapFileName = `${bundledFileName}.map`;
@@ -1518,4 +1686,4 @@ const PackageJsonTransformPlugin = (options = {})=>{
1518
1686
  });
1519
1687
  }
1520
1688
  }
1521
- export { AutoEntryPlugin, DtsPlugin, EntryExtractor, FilesArrayPlugin, NodeLibraryBuilder, PackageJsonTransformPlugin, PackageJsonTransformer, PnpmCatalog, collectDtsFiles, ensureTempDeclarationDir, findTsConfig, generateTsgoArgs, getTsgoBinPath, 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.1",
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",
@@ -72,15 +74,15 @@
72
74
  ]
73
75
  },
74
76
  "files": [
75
- ".npmignore",
77
+ "!rslib-builder.api.json",
76
78
  "LICENSE",
77
79
  "README.md",
78
- "api.model.json",
79
80
  "index.d.ts",
80
81
  "index.js",
81
82
  "package.json",
82
83
  "rslib-runtime.js",
83
84
  "tsconfig/node/ecma/lib.json",
84
- "tsconfig/root.json"
85
+ "tsconfig/root.json",
86
+ "tsdoc-metadata.json"
85
87
  ]
86
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
+ }
package/.npmignore DELETED
@@ -1,2 +0,0 @@
1
- # Exclude API model from npm publish (used by internal tooling)
2
- api.model.json