@savvy-web/rslib-builder 0.3.0 → 0.5.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
@@ -3,13 +3,14 @@ import * as __rspack_external_node_path_c5b9b54f from "node:path";
3
3
  import { __webpack_require__ } from "./rslib-runtime.js";
4
4
  import { constants, existsSync, writeFileSync } from "node:fs";
5
5
  import { defineConfig } from "@rslib/core";
6
- import { access, copyFile, mkdir, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
6
+ import { access, copyFile, mkdir, readFile, readdir, rm, stat, unlink as promises_unlink, writeFile } from "node:fs/promises";
7
7
  import { logger as core_logger } from "@rsbuild/core";
8
8
  import picocolors from "picocolors";
9
9
  import { getWorkspaceRoot } from "workspace-tools";
10
10
  import { spawn } from "node:child_process";
11
11
  import { StandardTags, Standardization, TSDocTagSyntaxKind } from "@microsoft/tsdoc";
12
- import { createCompilerHost, findConfigFile, formatDiagnostic, parseJsonConfigFileContent, readConfigFile, sys } from "typescript";
12
+ import deep_equal from "deep-equal";
13
+ import typescript, { createCompilerHost, findConfigFile, formatDiagnostic, parseJsonConfigFileContent, readConfigFile, sys } from "typescript";
13
14
  import { createRequire } from "node:module";
14
15
  import { inspect } from "node:util";
15
16
  import sort_package_json from "sort-package-json";
@@ -264,7 +265,7 @@ const AutoEntryPlugin = (options)=>{
264
265
  }
265
266
  };
266
267
  };
267
- var lib_namespaceObject = JSON.parse('{"$schema":"https://json.schemastore.org/tsconfig.json","compilerOptions":{"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"declarationDir":"${configDir}/dist","declarationMap":false,"emitDeclarationOnly":false,"esModuleInterop":true,"explainFiles":false,"forceConsistentCasingInFileNames":true,"incremental":true,"isolatedDeclarations":true,"isolatedModules":true,"jsx":"preserve","lib":["esnext"],"module":"nodenext","moduleResolution":"nodenext","outDir":"${configDir}/dist","resolveJsonModule":true,"rootDir":"${configDir}","skipLibCheck":true,"sourceMap":false,"strict":true,"strictNullChecks":true,"target":"es2023","tsBuildInfoFile":"${configDir}/dist/.tsbuildinfo.lib","typeRoots":["${configDir}/node_modules/@types","${configDir}/types"],"verbatimModuleSyntax":true},"exclude":["${configDir}/node_modules","${configDir}/dist/**/*"],"include":["${configDir}/types/*.ts","${configDir}/package.json","${configDir}/*.ts","${configDir}/*.cts","${configDir}/*.mts","${configDir}/src/**/*.ts","${configDir}/src/**/*.tsx","${configDir}/src/**/*.cts","${configDir}/src/**/*.mts","${configDir}/lib/**/*.ts","${configDir}/lib/**/*.tsx","${configDir}/lib/**/*.cts","${configDir}/lib/**/*.mts","${configDir}/public/**/*.json"]}');
268
+ var lib_namespaceObject = JSON.parse('{"$schema":"https://json.schemastore.org/tsconfig.json","compilerOptions":{"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"declarationDir":"${configDir}/dist","declarationMap":false,"emitDeclarationOnly":false,"esModuleInterop":true,"explainFiles":false,"forceConsistentCasingInFileNames":true,"incremental":true,"isolatedDeclarations":false,"isolatedModules":true,"jsx":"preserve","lib":["esnext"],"module":"nodenext","moduleResolution":"nodenext","outDir":"${configDir}/dist","resolveJsonModule":true,"rootDir":"${configDir}","skipLibCheck":true,"sourceMap":false,"strict":true,"strictNullChecks":true,"target":"es2023","tsBuildInfoFile":"${configDir}/dist/.tsbuildinfo.lib","typeRoots":["${configDir}/node_modules/@types","${configDir}/types"],"verbatimModuleSyntax":true},"exclude":["${configDir}/node_modules","${configDir}/dist/**/*"],"include":["${configDir}/types/*.ts","${configDir}/package.json","${configDir}/*.ts","${configDir}/*.cts","${configDir}/*.mts","${configDir}/src/**/*.ts","${configDir}/src/**/*.tsx","${configDir}/src/**/*.cts","${configDir}/src/**/*.mts","${configDir}/lib/**/*.ts","${configDir}/lib/**/*.tsx","${configDir}/lib/**/*.cts","${configDir}/lib/**/*.mts","${configDir}/public/**/*.json"]}');
268
269
  const requireCJS = createRequire(import.meta.url);
269
270
  const jsonImports = new Map([
270
271
  [
@@ -446,7 +447,14 @@ class TsDocConfigBuilder {
446
447
  if (tagDefinitions.length > 0) tsdocConfig.tagDefinitions = tagDefinitions;
447
448
  if (Object.keys(supportForTags).length > 0) tsdocConfig.supportForTags = supportForTags;
448
449
  const configPath = (0, external_node_path_.join)(outputDir, "tsdoc.json");
449
- await writeFile(configPath, JSON.stringify(tsdocConfig, null, 2));
450
+ if (existsSync(configPath)) try {
451
+ const existingContent = await readFile(configPath, "utf-8");
452
+ const existingConfig = JSON.parse(existingContent);
453
+ if (deep_equal(existingConfig, tsdocConfig, {
454
+ strict: true
455
+ })) return configPath;
456
+ } catch {}
457
+ await writeFile(configPath, `${JSON.stringify(tsdocConfig, null, "\t")}\n`);
450
458
  return configPath;
451
459
  }
452
460
  static syntaxKindToString(kind) {
@@ -609,13 +617,22 @@ async function bundleDtsFiles(options) {
609
617
  });
610
618
  if (!extractorResult.succeeded) throw new Error(`API Extractor failed for entry "${entryName}"`);
611
619
  if (collectedTsdocWarnings.length > 0) {
620
+ const isThirdParty = (warning)=>warning.sourceFilePath?.includes("node_modules/") ?? false;
621
+ const firstPartyWarnings = collectedTsdocWarnings.filter((w)=>!isThirdParty(w));
622
+ const thirdPartyWarnings = collectedTsdocWarnings.filter(isThirdParty);
612
623
  const formatWarning = (warning)=>{
613
624
  const location = warning.sourceFilePath ? `${picocolors.cyan((0, external_node_path_.relative)(cwd, warning.sourceFilePath))}${warning.sourceFileLine ? `:${warning.sourceFileLine}` : ""}${warning.sourceFileColumn ? `:${warning.sourceFileColumn}` : ""}` : null;
614
625
  return location ? `${location}: ${picocolors.yellow(warning.text)}` : picocolors.yellow(warning.text);
615
626
  };
616
- const warningMessages = collectedTsdocWarnings.map(formatWarning).join("\n ");
617
- if ("fail" === tsdocWarnings) throw new Error(`TSDoc validation failed for entry "${entryName}":\n ${warningMessages}`);
618
- if ("log" === tsdocWarnings) core_logger.warn(`TSDoc warnings for entry "${entryName}":\n ${warningMessages}`);
627
+ if (thirdPartyWarnings.length > 0) {
628
+ const thirdPartyMessages = thirdPartyWarnings.map(formatWarning).join("\n ");
629
+ core_logger.warn(`TSDoc warnings from dependencies for entry "${entryName}" (cannot be fixed, bundled types may have documentation issues):\n ${thirdPartyMessages}`);
630
+ }
631
+ if (firstPartyWarnings.length > 0) {
632
+ const firstPartyMessages = firstPartyWarnings.map(formatWarning).join("\n ");
633
+ if ("fail" === tsdocWarnings) throw new Error(`TSDoc validation failed for entry "${entryName}":\n ${firstPartyMessages}`);
634
+ if ("log" === tsdocWarnings) core_logger.warn(`TSDoc warnings for entry "${entryName}":\n ${firstPartyMessages}`);
635
+ }
619
636
  }
620
637
  if (generateApiModel && tempApiModelPath) apiModelPath = tempApiModelPath;
621
638
  if (generateTsdocMetadata && tempTsdocMetadataPath) tsdocMetadataPath = tempTsdocMetadataPath;
@@ -630,7 +647,7 @@ async function bundleDtsFiles(options) {
630
647
  let persistedTsdocConfigPath;
631
648
  if (tsdocConfigPath) if (shouldPersist) persistedTsdocConfigPath = tsdocConfigPath;
632
649
  else try {
633
- await unlink(tsdocConfigPath);
650
+ await promises_unlink(tsdocConfigPath);
634
651
  } catch {}
635
652
  return {
636
653
  bundledFiles,
@@ -792,7 +809,7 @@ function runTsgo(options) {
792
809
  });
793
810
  await copyFile(file.path, newPath);
794
811
  log.global.info(`Renamed ${file.relativePath} -> ${originalDtsPath} (from temp api-extractor)`);
795
- await unlink(file.path);
812
+ await promises_unlink(file.path);
796
813
  }
797
814
  }
798
815
  allDtsFiles.length = 0;
@@ -1440,6 +1457,413 @@ const PackageJsonTransformPlugin = (options = {})=>{
1440
1457
  }
1441
1458
  };
1442
1459
  };
1460
+ class ImportGraph {
1461
+ options;
1462
+ sys;
1463
+ program = null;
1464
+ compilerOptions = null;
1465
+ moduleResolutionCache = null;
1466
+ constructor(options){
1467
+ this.options = options;
1468
+ this.sys = options.sys ?? typescript.sys;
1469
+ }
1470
+ traceFromEntries(entryPaths) {
1471
+ const errors = [];
1472
+ const visited = new Set();
1473
+ const entries = [];
1474
+ const initResult = this.initializeProgram();
1475
+ if (!initResult.success) return {
1476
+ files: [],
1477
+ entries: [],
1478
+ errors: [
1479
+ initResult.error
1480
+ ]
1481
+ };
1482
+ for (const entryPath of entryPaths){
1483
+ const absolutePath = this.resolveEntryPath(entryPath);
1484
+ if (!this.sys.fileExists(absolutePath)) {
1485
+ errors.push({
1486
+ type: "entry_not_found",
1487
+ message: `Entry file not found: ${entryPath}`,
1488
+ path: absolutePath
1489
+ });
1490
+ continue;
1491
+ }
1492
+ entries.push(absolutePath);
1493
+ this.traceImports(absolutePath, visited, errors);
1494
+ }
1495
+ const files = Array.from(visited).filter((file)=>this.isSourceFile(file));
1496
+ return {
1497
+ files: files.sort(),
1498
+ entries,
1499
+ errors
1500
+ };
1501
+ }
1502
+ traceFromPackageExports(packageJsonPath) {
1503
+ const absolutePath = this.resolveEntryPath(packageJsonPath);
1504
+ let packageJson;
1505
+ try {
1506
+ const content = this.sys.readFile(absolutePath);
1507
+ if (!content) return {
1508
+ files: [],
1509
+ entries: [],
1510
+ errors: [
1511
+ {
1512
+ type: "package_json_not_found",
1513
+ message: `Failed to read package.json: File not found at ${absolutePath}`,
1514
+ path: absolutePath
1515
+ }
1516
+ ]
1517
+ };
1518
+ packageJson = JSON.parse(content);
1519
+ } catch (error) {
1520
+ const message = error instanceof Error ? error.message : String(error);
1521
+ return {
1522
+ files: [],
1523
+ entries: [],
1524
+ errors: [
1525
+ {
1526
+ type: "package_json_parse_error",
1527
+ message: `Failed to parse package.json: ${message}`,
1528
+ path: absolutePath
1529
+ }
1530
+ ]
1531
+ };
1532
+ }
1533
+ const extractor = new EntryExtractor();
1534
+ const { entries } = extractor.extract(packageJson);
1535
+ const packageDir = (0, external_node_path_.dirname)(absolutePath);
1536
+ const entryPaths = Object.values(entries).map((p)=>(0, external_node_path_.resolve)(packageDir, p));
1537
+ return this.traceFromEntries(entryPaths);
1538
+ }
1539
+ initializeProgram() {
1540
+ if (this.program) return {
1541
+ success: true
1542
+ };
1543
+ const configPath = this.findTsConfig();
1544
+ if (!configPath) return {
1545
+ success: false,
1546
+ error: {
1547
+ type: "tsconfig_not_found",
1548
+ message: `No tsconfig.json found in ${this.options.rootDir}`,
1549
+ path: this.options.rootDir
1550
+ }
1551
+ };
1552
+ const configFile = typescript.readConfigFile(configPath, (path)=>this.sys.readFile(path));
1553
+ if (configFile.error) {
1554
+ const message = typescript.flattenDiagnosticMessageText(configFile.error.messageText, "\n");
1555
+ return {
1556
+ success: false,
1557
+ error: {
1558
+ type: "tsconfig_read_error",
1559
+ message: `Failed to read tsconfig.json: ${message}`,
1560
+ path: configPath
1561
+ }
1562
+ };
1563
+ }
1564
+ const parsed = typescript.parseJsonConfigFileContent(configFile.config, this.sys, (0, external_node_path_.dirname)(configPath));
1565
+ if (parsed.errors.length > 0) {
1566
+ const messages = parsed.errors.map((e)=>typescript.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
1567
+ return {
1568
+ success: false,
1569
+ error: {
1570
+ type: "tsconfig_parse_error",
1571
+ message: `Failed to parse tsconfig.json: ${messages}`,
1572
+ path: configPath
1573
+ }
1574
+ };
1575
+ }
1576
+ this.compilerOptions = parsed.options;
1577
+ this.moduleResolutionCache = typescript.createModuleResolutionCache(this.options.rootDir, (fileName)=>fileName.toLowerCase(), this.compilerOptions);
1578
+ const host = typescript.createCompilerHost(this.compilerOptions, true);
1579
+ host.getCurrentDirectory = ()=>this.options.rootDir;
1580
+ this.program = typescript.createProgram([], this.compilerOptions, host);
1581
+ return {
1582
+ success: true
1583
+ };
1584
+ }
1585
+ findTsConfig() {
1586
+ if (this.options.tsconfigPath) {
1587
+ const customPath = (0, external_node_path_.isAbsolute)(this.options.tsconfigPath) ? this.options.tsconfigPath : (0, external_node_path_.resolve)(this.options.rootDir, this.options.tsconfigPath);
1588
+ if (this.sys.fileExists(customPath)) return customPath;
1589
+ return null;
1590
+ }
1591
+ const configPath = typescript.findConfigFile(this.options.rootDir, (path)=>this.sys.fileExists(path));
1592
+ return configPath ?? null;
1593
+ }
1594
+ resolveEntryPath(entryPath) {
1595
+ if ((0, external_node_path_.isAbsolute)(entryPath)) return (0, external_node_path_.normalize)(entryPath);
1596
+ return (0, external_node_path_.normalize)((0, external_node_path_.resolve)(this.options.rootDir, entryPath));
1597
+ }
1598
+ traceImports(filePath, visited, errors) {
1599
+ const normalizedPath = (0, external_node_path_.normalize)(filePath);
1600
+ if (visited.has(normalizedPath)) return;
1601
+ if (this.isExternalModule(normalizedPath)) return;
1602
+ visited.add(normalizedPath);
1603
+ const content = this.sys.readFile(normalizedPath);
1604
+ if (!content) return void errors.push({
1605
+ type: "file_read_error",
1606
+ message: `Failed to read file: ${normalizedPath}`,
1607
+ path: normalizedPath
1608
+ });
1609
+ const sourceFile = typescript.createSourceFile(normalizedPath, content, typescript.ScriptTarget.Latest, true);
1610
+ const imports = this.extractImports(sourceFile);
1611
+ for (const importPath of imports){
1612
+ const resolved = this.resolveImport(importPath, normalizedPath);
1613
+ if (resolved) this.traceImports(resolved, visited, errors);
1614
+ }
1615
+ }
1616
+ extractImports(sourceFile) {
1617
+ const imports = [];
1618
+ const visit = (node)=>{
1619
+ if (typescript.isImportDeclaration(node)) {
1620
+ const specifier = node.moduleSpecifier;
1621
+ if (typescript.isStringLiteral(specifier)) imports.push(specifier.text);
1622
+ } else if (typescript.isExportDeclaration(node)) {
1623
+ const specifier = node.moduleSpecifier;
1624
+ if (specifier && typescript.isStringLiteral(specifier)) imports.push(specifier.text);
1625
+ } else if (typescript.isCallExpression(node)) {
1626
+ const expression = node.expression;
1627
+ if (expression.kind === typescript.SyntaxKind.ImportKeyword && node.arguments.length > 0) {
1628
+ const arg = node.arguments[0];
1629
+ if (arg && typescript.isStringLiteral(arg)) imports.push(arg.text);
1630
+ }
1631
+ }
1632
+ typescript.forEachChild(node, visit);
1633
+ };
1634
+ visit(sourceFile);
1635
+ return imports;
1636
+ }
1637
+ resolveImport(specifier, fromFile) {
1638
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
1639
+ if (!this.compilerOptions?.paths || !Object.keys(this.compilerOptions.paths).length) return null;
1640
+ }
1641
+ if (!this.compilerOptions || !this.moduleResolutionCache) return null;
1642
+ const resolved = typescript.resolveModuleName(specifier, fromFile, this.compilerOptions, this.sys, this.moduleResolutionCache);
1643
+ if (resolved.resolvedModule) {
1644
+ const resolvedPath = resolved.resolvedModule.resolvedFileName;
1645
+ if (resolved.resolvedModule.isExternalLibraryImport) return null;
1646
+ if (resolvedPath.endsWith(".d.ts")) {
1647
+ const sourcePath = resolvedPath.replace(/\.d\.ts$/, ".ts");
1648
+ if (this.sys.fileExists(sourcePath)) return sourcePath;
1649
+ return null;
1650
+ }
1651
+ return resolvedPath;
1652
+ }
1653
+ return null;
1654
+ }
1655
+ isExternalModule(filePath) {
1656
+ return filePath.includes("/node_modules/") || filePath.includes("\\node_modules\\");
1657
+ }
1658
+ isSourceFile(filePath) {
1659
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx")) return false;
1660
+ if (filePath.endsWith(".d.ts")) return false;
1661
+ if (filePath.includes(".test.") || filePath.includes(".spec.")) return false;
1662
+ if (filePath.includes("/__test__/") || filePath.includes("\\__test__\\")) return false;
1663
+ if (filePath.includes("/__tests__/") || filePath.includes("\\__tests__\\")) return false;
1664
+ const excludePatterns = this.options.excludePatterns ?? [];
1665
+ for (const pattern of excludePatterns)if (filePath.includes(pattern)) return false;
1666
+ return true;
1667
+ }
1668
+ static fromEntries(entryPaths, options) {
1669
+ const graph = new ImportGraph(options);
1670
+ return graph.traceFromEntries(entryPaths);
1671
+ }
1672
+ static fromPackageExports(packageJsonPath, options) {
1673
+ const graph = new ImportGraph(options);
1674
+ return graph.traceFromPackageExports(packageJsonPath);
1675
+ }
1676
+ }
1677
+ function formatLintResults(results, cwd) {
1678
+ if (0 === results.messages.length) return "";
1679
+ const lines = [];
1680
+ const messagesByFile = new Map();
1681
+ for (const msg of results.messages){
1682
+ const existing = messagesByFile.get(msg.filePath) ?? [];
1683
+ existing.push(msg);
1684
+ messagesByFile.set(msg.filePath, existing);
1685
+ }
1686
+ for (const [filePath, messages] of messagesByFile){
1687
+ lines.push(picocolors.underline(picocolors.cyan((0, external_node_path_.relative)(cwd, filePath))));
1688
+ for (const msg of messages){
1689
+ const location = picocolors.dim(`${msg.line}:${msg.column}`);
1690
+ const severityColor = 2 === msg.severity ? picocolors.red : picocolors.yellow;
1691
+ const severityLabel = 2 === msg.severity ? "error" : "warning";
1692
+ const rule = msg.ruleId ? picocolors.dim(`(${msg.ruleId})`) : "";
1693
+ lines.push(` ${location} ${severityColor(severityLabel)} ${msg.message} ${rule}`);
1694
+ }
1695
+ lines.push("");
1696
+ }
1697
+ const errorText = 1 === results.errorCount ? "error" : "errors";
1698
+ const warningText = 1 === results.warningCount ? "warning" : "warnings";
1699
+ const summary = results.errorCount > 0 ? picocolors.red(`${results.errorCount} ${errorText}`) : picocolors.yellow(`${results.warningCount} ${warningText}`);
1700
+ lines.push(summary);
1701
+ return lines.join("\n");
1702
+ }
1703
+ function discoverFilesToLint(options, cwd) {
1704
+ if (options.include && options.include.length > 0) return {
1705
+ files: options.include,
1706
+ errors: [],
1707
+ isGlobPattern: true
1708
+ };
1709
+ const graph = new ImportGraph({
1710
+ rootDir: cwd
1711
+ });
1712
+ const packageJsonPath = (0, external_node_path_.join)(cwd, "package.json");
1713
+ const result = graph.traceFromPackageExports(packageJsonPath);
1714
+ return {
1715
+ files: result.files,
1716
+ errors: result.errors,
1717
+ isGlobPattern: false
1718
+ };
1719
+ }
1720
+ async function runTsDocLint(options, cwd) {
1721
+ const tsdocOptions = options.tsdoc ?? {};
1722
+ const persistConfig = options.persistConfig;
1723
+ const shouldPersist = TsDocConfigBuilder.shouldPersist(persistConfig);
1724
+ const tsdocConfigOutputPath = TsDocConfigBuilder.getConfigPath(persistConfig, cwd);
1725
+ const tsdocConfigPath = await TsDocConfigBuilder.writeConfigFile(tsdocOptions, (0, external_node_path_.dirname)(tsdocConfigOutputPath));
1726
+ const eslintModule = await import("eslint");
1727
+ const tsParserModule = await import("@typescript-eslint/parser");
1728
+ const tsdocPluginModule = await import("eslint-plugin-tsdoc");
1729
+ const { ESLint } = eslintModule;
1730
+ const tsParser = tsParserModule.default ?? tsParserModule;
1731
+ const tsdocPlugin = tsdocPluginModule.default ?? tsdocPluginModule;
1732
+ const discovery = discoverFilesToLint(options, cwd);
1733
+ if (0 === discovery.files.length) return {
1734
+ results: {
1735
+ errorCount: 0,
1736
+ warningCount: 0,
1737
+ messages: []
1738
+ },
1739
+ tsdocConfigPath: shouldPersist ? tsdocConfigPath : void 0,
1740
+ discoveryErrors: discovery.errors
1741
+ };
1742
+ let eslintConfig;
1743
+ let filesToLint;
1744
+ if (discovery.isGlobPattern) {
1745
+ const includePatterns = discovery.files;
1746
+ filesToLint = includePatterns.filter((p)=>!p.startsWith("!"));
1747
+ eslintConfig = [
1748
+ {
1749
+ ignores: [
1750
+ "**/node_modules/**",
1751
+ "**/dist/**",
1752
+ "**/coverage/**"
1753
+ ]
1754
+ },
1755
+ {
1756
+ files: filesToLint,
1757
+ ignores: includePatterns.filter((p)=>p.startsWith("!")).map((p)=>p.slice(1)),
1758
+ languageOptions: {
1759
+ parser: tsParser
1760
+ },
1761
+ plugins: {
1762
+ tsdoc: tsdocPlugin
1763
+ },
1764
+ rules: {
1765
+ "tsdoc/syntax": "error"
1766
+ }
1767
+ }
1768
+ ];
1769
+ } else {
1770
+ filesToLint = discovery.files;
1771
+ eslintConfig = [
1772
+ {
1773
+ ignores: [
1774
+ "**/node_modules/**",
1775
+ "**/dist/**",
1776
+ "**/coverage/**"
1777
+ ]
1778
+ },
1779
+ {
1780
+ files: [
1781
+ "**/*.ts",
1782
+ "**/*.tsx"
1783
+ ],
1784
+ languageOptions: {
1785
+ parser: tsParser
1786
+ },
1787
+ plugins: {
1788
+ tsdoc: tsdocPlugin
1789
+ },
1790
+ rules: {
1791
+ "tsdoc/syntax": "error"
1792
+ }
1793
+ }
1794
+ ];
1795
+ }
1796
+ const eslint = new ESLint({
1797
+ cwd,
1798
+ overrideConfigFile: true,
1799
+ overrideConfig: eslintConfig
1800
+ });
1801
+ const eslintResults = await eslint.lintFiles(filesToLint);
1802
+ const messages = [];
1803
+ let errorCount = 0;
1804
+ let warningCount = 0;
1805
+ for (const result of eslintResults)for (const msg of result.messages){
1806
+ messages.push({
1807
+ filePath: result.filePath,
1808
+ line: msg.line,
1809
+ column: msg.column,
1810
+ message: msg.message,
1811
+ ruleId: msg.ruleId,
1812
+ severity: msg.severity
1813
+ });
1814
+ if (2 === msg.severity) errorCount++;
1815
+ else warningCount++;
1816
+ }
1817
+ return {
1818
+ results: {
1819
+ errorCount,
1820
+ warningCount,
1821
+ messages
1822
+ },
1823
+ tsdocConfigPath: shouldPersist ? tsdocConfigPath : void 0,
1824
+ discoveryErrors: discovery.errors.length > 0 ? discovery.errors : void 0
1825
+ };
1826
+ }
1827
+ async function cleanupTsDocConfig(configPath) {
1828
+ if (!configPath) return;
1829
+ try {
1830
+ const { unlink } = await import("node:fs/promises");
1831
+ await unlink(configPath);
1832
+ } catch {}
1833
+ }
1834
+ const TsDocLintPlugin = (options = {})=>{
1835
+ const { enabled = true } = options;
1836
+ let tempTsDocConfigPath;
1837
+ return {
1838
+ name: "tsdoc-lint-plugin",
1839
+ setup (api) {
1840
+ if (!enabled) return;
1841
+ api.onBeforeBuild(async ()=>{
1842
+ const cwd = api.context.rootPath;
1843
+ const isCI = TsDocConfigBuilder.isCI();
1844
+ const onError = options.onError ?? (isCI ? "throw" : "error");
1845
+ core_logger.info(`${picocolors.dim("[tsdoc-lint]")} Validating TSDoc comments...`);
1846
+ try {
1847
+ const { results, tsdocConfigPath, discoveryErrors } = await runTsDocLint(options, cwd);
1848
+ if (discoveryErrors && discoveryErrors.length > 0) for (const error of discoveryErrors)core_logger.warn(`${picocolors.dim("[tsdoc-lint]")} ${error.message}`);
1849
+ if (!TsDocConfigBuilder.shouldPersist(options.persistConfig)) tempTsDocConfigPath = tsdocConfigPath;
1850
+ if (0 === results.errorCount && 0 === results.warningCount) return void core_logger.info(`${picocolors.dim("[tsdoc-lint]")} ${picocolors.green("All TSDoc comments are valid")}`);
1851
+ const formatted = formatLintResults(results, cwd);
1852
+ if (results.errorCount > 0) if ("throw" === onError) throw new Error(`TSDoc validation failed:\n${formatted}`);
1853
+ else if ("error" === onError) core_logger.error(`${picocolors.dim("[tsdoc-lint]")} TSDoc validation errors:\n${formatted}`);
1854
+ else core_logger.warn(`${picocolors.dim("[tsdoc-lint]")} TSDoc validation warnings:\n${formatted}`);
1855
+ else if (results.warningCount > 0) core_logger.warn(`${picocolors.dim("[tsdoc-lint]")} TSDoc validation warnings:\n${formatted}`);
1856
+ } catch (error) {
1857
+ await cleanupTsDocConfig(tempTsDocConfigPath);
1858
+ throw error;
1859
+ }
1860
+ });
1861
+ api.onCloseBuild(async ()=>{
1862
+ await cleanupTsDocConfig(tempTsDocConfigPath);
1863
+ });
1864
+ }
1865
+ };
1866
+ };
1443
1867
  /* v8 ignore next -- @preserve */ class NodeLibraryBuilder {
1444
1868
  static DEFAULT_OPTIONS = {
1445
1869
  entry: void 0,
@@ -1453,7 +1877,8 @@ const PackageJsonTransformPlugin = (options = {})=>{
1453
1877
  tsconfigPath: void 0,
1454
1878
  externals: [],
1455
1879
  dtsBundledPackages: void 0,
1456
- transformFiles: void 0
1880
+ transformFiles: void 0,
1881
+ tsdocLint: void 0
1457
1882
  };
1458
1883
  static mergeOptions(options = {}) {
1459
1884
  const merged = {
@@ -1487,6 +1912,11 @@ const PackageJsonTransformPlugin = (options = {})=>{
1487
1912
  const options = NodeLibraryBuilder.mergeOptions(opts);
1488
1913
  const VERSION = await packageJsonVersion();
1489
1914
  const plugins = [];
1915
+ if (options.tsdocLint) {
1916
+ const lintOptions = true === options.tsdocLint ? {} : options.tsdocLint;
1917
+ if (!lintOptions.tsdoc && "object" == typeof options.apiModel && options.apiModel.tsdoc) lintOptions.tsdoc = options.apiModel.tsdoc;
1918
+ plugins.push(TsDocLintPlugin(lintOptions));
1919
+ }
1490
1920
  if ("dev" === target || "npm" === target) {
1491
1921
  if (!options.entry) plugins.push(AutoEntryPlugin({
1492
1922
  exportsAsIndexes: options.exportsAsIndexes
@@ -1565,4 +1995,4 @@ const PackageJsonTransformPlugin = (options = {})=>{
1565
1995
  });
1566
1996
  }
1567
1997
  }
1568
- export { AutoEntryPlugin, DtsPlugin, FilesArrayPlugin, NodeLibraryBuilder, PackageJsonTransformPlugin, TsDocConfigBuilder };
1998
+ export { AutoEntryPlugin, DtsPlugin, FilesArrayPlugin, ImportGraph, NodeLibraryBuilder, PackageJsonTransformPlugin, TsDocConfigBuilder, TsDocLintPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/rslib-builder",
3
- "version": "0.3.0",
3
+ "version": "0.5.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",
@@ -27,18 +27,22 @@
27
27
  "@microsoft/tsdoc": "^0.16.0",
28
28
  "@microsoft/tsdoc-config": "^0.18.0",
29
29
  "@pnpm/exportable-manifest": "^1000.3.1",
30
+ "@typescript-eslint/parser": "^8.53.1",
31
+ "deep-equal": "^2.2.3",
32
+ "eslint": "^9.39.2",
33
+ "eslint-plugin-tsdoc": "^0.5.0",
30
34
  "glob": "^13.0.0",
31
35
  "picocolors": "^1.1.1",
32
- "sort-package-json": "^3.6.0",
36
+ "sort-package-json": "^3.6.1",
33
37
  "tmp": "^0.2.5",
34
- "workspace-tools": "^0.40.3",
38
+ "workspace-tools": "^0.40.4",
35
39
  "yaml": "^2.8.2"
36
40
  },
37
41
  "peerDependencies": {
38
42
  "@microsoft/api-extractor": "^7.55.2",
39
- "@rslib/core": "^0.19.2",
40
- "@types/node": "^25.0.9",
41
- "@typescript/native-preview": "^7.0.0-dev.20260120.1",
43
+ "@rslib/core": "^0.19.3",
44
+ "@types/node": "^25.0.10",
45
+ "@typescript/native-preview": "^7.0.0-dev.20260124.1",
42
46
  "typescript": "^5.9.3"
43
47
  },
44
48
  "peerDependenciesMeta": {
@@ -48,11 +52,14 @@
48
52
  "@rslib/core": {
49
53
  "optional": false
50
54
  },
55
+ "@types/node": {
56
+ "optional": false
57
+ },
51
58
  "@typescript/native-preview": {
52
59
  "optional": false
53
60
  },
54
61
  "typescript": {
55
- "optional": false
62
+ "optional": true
56
63
  }
57
64
  },
58
65
  "files": [
@@ -11,7 +11,7 @@
11
11
  "explainFiles": false,
12
12
  "forceConsistentCasingInFileNames": true,
13
13
  "incremental": true,
14
- "isolatedDeclarations": true,
14
+ "isolatedDeclarations": false,
15
15
  "isolatedModules": true,
16
16
  "jsx": "preserve",
17
17
  "lib": ["esnext"],