@savvy-web/rslib-builder 0.12.2 → 0.13.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.
Files changed (4) hide show
  1. package/README.md +23 -13
  2. package/index.d.ts +48 -2
  3. package/index.js +261 -76
  4. package/package.json +3 -3
package/README.md CHANGED
@@ -10,23 +10,33 @@ resolution automatically.
10
10
 
11
11
  ## Features
12
12
 
13
- - **Zero-Config Entry Detection** - Auto-discovers entry points from package.json
14
- exports, no manual configuration needed
15
- - **10-100x Faster Types** - Uses tsgo (native TypeScript compiler) with API
16
- Extractor for bundled, clean public API declarations
17
- - **Production-Ready Transforms** - Converts `.ts` exports to `.js`, resolves
18
- PNPM `catalog:` and `workspace:` references, generates files array
19
- - **Bundled or Bundleless** - Choose single-file bundles per entry or
20
- bundleless mode that preserves your source file structure
21
- - **Multi-Target Builds** - Separate dev (with source maps) and npm (optimized)
22
- outputs from a single configuration
23
- - **TSDoc Validation** - Pre-build documentation validation with automatic
24
- public API discovery
13
+ - **Zero-Config Entry Detection** - Auto-discovers
14
+ entry points from package.json exports
15
+ - **10-100x Faster Types** - Uses tsgo (native
16
+ TypeScript compiler) with API Extractor for
17
+ bundled, clean public API declarations
18
+ - **Production-Ready Transforms** - Converts `.ts`
19
+ exports to `.js`, resolves PNPM `catalog:` and
20
+ `workspace:` references, generates files array
21
+ - **Bundled or Bundleless** - Choose single-file
22
+ bundles per entry or bundleless mode that
23
+ preserves your source file structure
24
+ - **Multi-Target Builds** - Separate dev (with
25
+ source maps) and npm (optimized) outputs from a
26
+ single configuration
27
+ - **Flexible Formats** - ESM, CJS, or dual format
28
+ output with per-entry format overrides
29
+ - **TSDoc Validation** - Pre-build documentation
30
+ validation with automatic public API discovery
25
31
 
26
32
  ## Installation
27
33
 
28
34
  ```bash
29
- npm install --save-dev @savvy-web/rslib-builder @rslib/core @microsoft/api-extractor @typescript/native-preview
35
+ npm install --save-dev \
36
+ @savvy-web/rslib-builder \
37
+ @rslib/core \
38
+ @microsoft/api-extractor \
39
+ @typescript/native-preview
30
40
  ```
31
41
 
32
42
  ## Quick Start
package/index.d.ts CHANGED
@@ -1238,9 +1238,42 @@ export declare interface NodeLibraryBuilderOptions {
1238
1238
  * - `"esm"` → `"type": "module"`
1239
1239
  * - `"cjs"` → `"type": "commonjs"`
1240
1240
  *
1241
+ * When an array is provided, the package is built in both formats.
1242
+ * The first format in the array is the primary format (determines `type` field).
1243
+ * Each format outputs to its own subdirectory (`dist/{target}/esm/`, `dist/{target}/cjs/`).
1244
+ *
1241
1245
  * @defaultValue `"esm"`
1246
+ *
1247
+ * @example
1248
+ * Dual format output:
1249
+ * ```typescript
1250
+ * NodeLibraryBuilder.create({
1251
+ * format: ['esm', 'cjs'],
1252
+ * })
1253
+ * ```
1242
1254
  */
1243
- format?: LibraryFormat;
1255
+ format?: LibraryFormat | LibraryFormat[];
1256
+ /**
1257
+ * Per-entry format overrides.
1258
+ * Maps export paths (matching package.json exports keys like `"./markdownlint"`)
1259
+ * to a specific format. Entries not listed inherit the top-level `format`.
1260
+ *
1261
+ * @remarks
1262
+ * When both `entryFormats` and array `format` are used, `entryFormats` takes precedence.
1263
+ * An entry with a specific format override will only be built in that format,
1264
+ * even if the global format is dual.
1265
+ *
1266
+ * @example
1267
+ * ```typescript
1268
+ * NodeLibraryBuilder.create({
1269
+ * format: 'esm',
1270
+ * entryFormats: {
1271
+ * './markdownlint': 'cjs',
1272
+ * },
1273
+ * })
1274
+ * ```
1275
+ */
1276
+ entryFormats?: Record<string, LibraryFormat>;
1244
1277
  /**
1245
1278
  * Additional entry points bundled with custom output names.
1246
1279
  * These entries bypass type generation and package.json exports
@@ -2081,6 +2114,18 @@ export declare interface PackageJsonTransformPluginOptions {
2081
2114
  * ```
2082
2115
  */
2083
2116
  transform?: (pkg: PackageJson) => PackageJson;
2117
+ /**
2118
+ * Per-entry format overrides for export conditions.
2119
+ * Maps export paths to their format (e.g., `{ "./markdownlint": "cjs" }`).
2120
+ * Entries not listed inherit the top-level `format`.
2121
+ */
2122
+ entryFormats?: Record<string, LibraryFormat>;
2123
+ /**
2124
+ * Whether the build uses dual format (both ESM and CJS).
2125
+ * When true, exports get both `import` and `require` conditions
2126
+ * with format directory prefixes.
2127
+ */
2128
+ dualFormat?: boolean;
2084
2129
  }
2085
2130
 
2086
2131
  /**
@@ -3124,7 +3169,8 @@ export declare class TsconfigResolver {
3124
3169
  source: string;
3125
3170
  /**
3126
3171
  * Output format for this entry.
3127
- * If not specified, inherits from top-level `format` option.
3172
+ * If not specified, inherits from the primary format
3173
+ * (first element when `format` is an array, or the single format value).
3128
3174
  */
3129
3175
  format?: LibraryFormat;
3130
3176
  }
package/index.js CHANGED
@@ -465,28 +465,24 @@ const AutoEntryPlugin = (options)=>{
465
465
  tracedEntries[relPath] = `./${relPath}`;
466
466
  }
467
467
  log.global.info(`bundleless: traced ${Object.keys(tracedEntries).length} files from ${entrySourcePaths.length} entries`);
468
- environments.forEach(([_env, lib])=>{
469
- lib.source = {
470
- ...lib.source,
471
- entry: tracedEntries
472
- };
473
- });
474
- } else environments.forEach(([_env, lib])=>{
475
- lib.source = {
468
+ for (const [, lib] of environments)lib.source = {
476
469
  ...lib.source,
477
- entry: {
478
- ...lib.source?.entry,
479
- ...entries
480
- }
470
+ entry: tracedEntries
481
471
  };
482
- });
472
+ } else for (const [, lib] of environments)lib.source = {
473
+ ...lib.source,
474
+ entry: {
475
+ ...lib.source?.entry,
476
+ ...entries
477
+ }
478
+ };
483
479
  const state = buildStateMap.get(api);
484
480
  if (state && !state.hasLoggedEntries) {
485
481
  state.hasLoggedEntries = true;
486
- environments.forEach(([env])=>{
482
+ for (const [env] of environments){
487
483
  const log = createEnvLogger(env);
488
484
  log.entries("auto-detected entries", entries);
489
- });
485
+ }
490
486
  }
491
487
  }
492
488
  } catch (error) {
@@ -977,8 +973,7 @@ class TsDocConfigBuilder {
977
973
  }));
978
974
  }
979
975
  static shouldPersist(persistConfig) {
980
- if (false === persistConfig) return false;
981
- return true;
976
+ return false !== persistConfig;
982
977
  }
983
978
  static getConfigPath(persistConfig, cwd) {
984
979
  if ("string" == typeof persistConfig) return isAbsolute(persistConfig) ? persistConfig : join(cwd, persistConfig);
@@ -1156,7 +1151,8 @@ async function bundleDtsFiles(options) {
1156
1151
  } catch {}
1157
1152
  }
1158
1153
  }
1159
- const outputFileName = `${entryName}.d.ts`;
1154
+ const dtsExtension = "cjs" === options.format ? ".d.cts" : ".d.ts";
1155
+ const outputFileName = `${entryName}${dtsExtension}`;
1160
1156
  const tempBundledPath = join(tempOutputDir, outputFileName);
1161
1157
  const isMainEntry = "index" === entryName || 1 === entryPoints.size;
1162
1158
  const generateApiModel = apiModelEnabled;
@@ -1293,7 +1289,7 @@ async function bundleDtsFiles(options) {
1293
1289
  };
1294
1290
  }
1295
1291
  function stripSourceMapComment(content) {
1296
- return content.replace(/\/\/# sourceMappingURL=\S+\.d\.ts\.map\s*$/gm, "").trim();
1292
+ return content.replace(/\/\/# sourceMappingURL=\S+\.d\.c?ts\.map\s*$/gm, "").trim();
1297
1293
  }
1298
1294
  function mergeApiModels(options) {
1299
1295
  const { perEntryModels, packageName, exportPaths } = options;
@@ -1573,6 +1569,9 @@ function runTsgo(options) {
1573
1569
  },
1574
1570
  ...void 0 !== options.apiModel && {
1575
1571
  apiModel: options.apiModel
1572
+ },
1573
+ ...options.format && {
1574
+ format: options.format
1576
1575
  }
1577
1576
  });
1578
1577
  let apiModelPath;
@@ -1593,10 +1592,11 @@ function runTsgo(options) {
1593
1592
  await writeFile(mergedPath, JSON.stringify(mergedModel, null, 2), "utf-8");
1594
1593
  apiModelPath = mergedPath;
1595
1594
  }
1595
+ const bundledDtsExtension = "cjs" === options.format ? ".d.cts" : ".d.ts";
1596
1596
  if (options.bundle) {
1597
1597
  let emittedCount = 0;
1598
1598
  for (const [entryName, tempBundledPath] of bundledFiles){
1599
- const bundledFileName = `${entryName}.d.ts`;
1599
+ const bundledFileName = `${entryName}${bundledDtsExtension}`;
1600
1600
  let content = await readFile(tempBundledPath, "utf-8");
1601
1601
  content = stripSourceMapComment(content);
1602
1602
  const source = new context.sources.OriginalSource(content, bundledFileName);
@@ -1653,7 +1653,7 @@ function runTsgo(options) {
1653
1653
  core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted resolved tsconfig: tsconfig.json (excluded from npm publish)`);
1654
1654
  }
1655
1655
  if (options.bundle) for (const [entryName] of bundledFiles){
1656
- const bundledFileName = `${entryName}.d.ts`;
1656
+ const bundledFileName = `${entryName}${bundledDtsExtension}`;
1657
1657
  const mapFileName = `${bundledFileName}.map`;
1658
1658
  for (const file of dtsFiles){
1659
1659
  if (file.relativePath.endsWith(".d.ts.map")) continue;
@@ -1679,7 +1679,7 @@ function runTsgo(options) {
1679
1679
  stage: "summarize"
1680
1680
  }, async (compiler)=>{
1681
1681
  const assetsToDelete = [];
1682
- for(const assetName in compiler.compilation.assets)if (assetName.endsWith(".d.ts")) {
1682
+ for(const assetName in compiler.compilation.assets)if (assetName.endsWith(".d.ts") || assetName.endsWith(".d.cts")) {
1683
1683
  const asset = compiler.compilation.assets[assetName];
1684
1684
  const content = asset.source().toString();
1685
1685
  const strippedContent = stripSourceMapComment(content);
@@ -1687,7 +1687,7 @@ function runTsgo(options) {
1687
1687
  const source = new compiler.sources.OriginalSource(strippedContent, assetName);
1688
1688
  compiler.compilation.assets[assetName] = source;
1689
1689
  }
1690
- } else if (assetName.endsWith(".d.ts.map")) assetsToDelete.push(assetName);
1690
+ } else if (assetName.endsWith(".d.ts.map") || assetName.endsWith(".d.cts.map")) assetsToDelete.push(assetName);
1691
1691
  for (const assetName of assetsToDelete)delete compiler.compilation.assets[assetName];
1692
1692
  });
1693
1693
  api.onCloseBuild(async ()=>{
@@ -2118,7 +2118,7 @@ function createTypePath(jsPath, collapseIndex = true) {
2118
2118
  if (jsPath.endsWith(".js")) return `${jsPath.slice(0, -3)}.d.ts`;
2119
2119
  return `${jsPath}.d.ts`;
2120
2120
  }
2121
- function transformPackageBin(bin, _processTSExports = true) {
2121
+ function transformPackageBin(bin) {
2122
2122
  if ("string" == typeof bin) {
2123
2123
  if (bin.endsWith(".ts") || bin.endsWith(".tsx")) return "./bin/cli.js";
2124
2124
  return bin;
@@ -2184,7 +2184,7 @@ function applyRslibTransformations(packageJson, originalPackageJson, processTSEx
2184
2184
  };
2185
2185
  if (processedManifest.exports) processedManifest.exports = transformPackageExports(processedManifest.exports, processTSExports, void 0, entrypoints, exportToOutputMap, bundle ?? false);
2186
2186
  if (processedManifest.bin) {
2187
- const transformedBin = transformPackageBin(processedManifest.bin, processTSExports);
2187
+ const transformedBin = transformPackageBin(processedManifest.bin);
2188
2188
  if (transformedBin) processedManifest.bin = transformedBin;
2189
2189
  }
2190
2190
  if (originalPackageJson.typesVersions) {
@@ -2207,15 +2207,51 @@ async function applyPnpmTransformations(packageJson, dir = process.cwd(), catalo
2207
2207
  const workspaceCatalog = catalog ?? createWorkspaceCatalog();
2208
2208
  return workspaceCatalog.resolvePackageJson(packageJson, dir);
2209
2209
  }
2210
- async function buildPackageJson(packageJson, isProduction = false, processTSExports = true, entrypoints, exportToOutputMap, bundle, transform) {
2210
+ async function buildPackageJson(packageJson, isProduction = false, processTSExports = true, entrypoints, exportToOutputMap, bundle, transform, formatConditions) {
2211
2211
  let result;
2212
2212
  if (isProduction) {
2213
2213
  const pnpmTransformed = await applyPnpmTransformations(packageJson);
2214
2214
  result = applyRslibTransformations(pnpmTransformed, packageJson, processTSExports, entrypoints, exportToOutputMap, bundle);
2215
2215
  } else result = applyRslibTransformations(packageJson, packageJson, processTSExports, entrypoints, exportToOutputMap, bundle);
2216
+ if (formatConditions && result.exports) result.exports = applyFormatConditions(result.exports, formatConditions);
2216
2217
  if (transform) result = transform(result);
2217
2218
  return result;
2218
2219
  }
2220
+ function toCjsPath(jsPath) {
2221
+ if (jsPath.endsWith(".js")) return `${jsPath.slice(0, -3)}.cjs`;
2222
+ return jsPath;
2223
+ }
2224
+ function toCtsTypePath(dtsPath) {
2225
+ if (dtsPath.endsWith(".d.ts")) return `${dtsPath.slice(0, -5)}.d.cts`;
2226
+ return dtsPath;
2227
+ }
2228
+ function addFormatDirPrefix(path, format) {
2229
+ if (path.startsWith("./")) return `./${format}/${path.slice(2)}`;
2230
+ return `./${format}/${path}`;
2231
+ }
2232
+ function applyFormatConditions(exports, options) {
2233
+ if (!exports || "object" != typeof exports || Array.isArray(exports)) return exports;
2234
+ const { format = "esm", entryFormats, dualFormat } = options;
2235
+ const result = {};
2236
+ for (const [key, value] of Object.entries(exports))if (value && "object" == typeof value && !Array.isArray(value)) {
2237
+ const condObj = value;
2238
+ if ("string" == typeof condObj.types && "string" == typeof condObj.import) {
2239
+ const entryFormat = entryFormats?.[key] ?? format;
2240
+ const hasExplicitOverride = void 0 !== entryFormats && key in entryFormats;
2241
+ if (dualFormat && !hasExplicitOverride) result[key] = {
2242
+ types: addFormatDirPrefix(condObj.types, "esm"),
2243
+ import: addFormatDirPrefix(condObj.import, "esm"),
2244
+ require: addFormatDirPrefix(toCjsPath(condObj.import), "cjs")
2245
+ };
2246
+ else if ("cjs" === entryFormat) result[key] = {
2247
+ types: toCtsTypePath(condObj.types),
2248
+ require: toCjsPath(condObj.import)
2249
+ };
2250
+ else result[key] = value;
2251
+ } else result[key] = value;
2252
+ } else result[key] = value;
2253
+ return result;
2254
+ }
2219
2255
  const PackageJsonTransformPlugin = (options = {})=>({
2220
2256
  name: "package-json-processor",
2221
2257
  setup (api) {
@@ -2243,7 +2279,19 @@ const PackageJsonTransformPlugin = (options = {})=>({
2243
2279
  const isProduction = "dev" !== envId;
2244
2280
  const entrypoints = api.useExposed("entrypoints");
2245
2281
  const exportToOutputMap = api.useExposed("exportToOutputMap");
2246
- const processedPackageJson = await buildPackageJson(packageJson.data, isProduction, options.processTSExports, entrypoints, exportToOutputMap, options.bundle, options.transform);
2282
+ let formatConditions;
2283
+ if (options.entryFormats || options.dualFormat) formatConditions = {
2284
+ ...options.format && {
2285
+ format: options.format
2286
+ },
2287
+ ...options.entryFormats && {
2288
+ entryFormats: options.entryFormats
2289
+ },
2290
+ ...options.dualFormat && {
2291
+ dualFormat: options.dualFormat
2292
+ }
2293
+ };
2294
+ const processedPackageJson = await buildPackageJson(packageJson.data, isProduction, options.processTSExports, entrypoints, exportToOutputMap, options.bundle, options.transform, formatConditions);
2247
2295
  packageJson.data = processedPackageJson;
2248
2296
  if (options.forcePrivate) packageJson.data.private = true;
2249
2297
  if (options.format) packageJson.data.type = "esm" === options.format ? "module" : "commonjs";
@@ -2251,7 +2299,7 @@ const PackageJsonTransformPlugin = (options = {})=>({
2251
2299
  if (useRollupTypes && packageJson.data.exports && "object" == typeof packageJson.data.exports) {
2252
2300
  const exports = packageJson.data.exports;
2253
2301
  delete exports["./api-extractor"];
2254
- for (const [, value] of Object.entries(exports))if (value && "object" == typeof value && "types" in value) value.types = "./index.d.ts";
2302
+ for (const value of Object.values(exports))if (value && "object" == typeof value && "types" in value) value.types = "./index.d.ts";
2255
2303
  }
2256
2304
  packageJson.update();
2257
2305
  });
@@ -2684,6 +2732,9 @@ const VirtualEntryPlugin = (options)=>{
2684
2732
  },
2685
2733
  ...void 0 !== options.virtualEntries && {
2686
2734
  virtualEntries: options.virtualEntries
2735
+ },
2736
+ ...void 0 !== options.entryFormats && {
2737
+ entryFormats: options.entryFormats
2687
2738
  }
2688
2739
  };
2689
2740
  return merged;
@@ -2698,6 +2749,7 @@ const VirtualEntryPlugin = (options)=>{
2698
2749
  }
2699
2750
  static async createSingleTarget(target, opts) {
2700
2751
  const options = NodeLibraryBuilder.mergeOptions(opts);
2752
+ const bundle = options.bundle ?? true;
2701
2753
  const VERSION = await packageJsonVersion();
2702
2754
  const plugins = [];
2703
2755
  const apiModelConfig = "object" == typeof options.apiModel ? options.apiModel : {};
@@ -2714,7 +2766,7 @@ const VirtualEntryPlugin = (options)=>{
2714
2766
  };
2715
2767
  plugins.push(TsDocLintPlugin({
2716
2768
  ...lintOptions,
2717
- ...false === options.bundle && {
2769
+ ...!bundle && {
2718
2770
  perEntry: true
2719
2771
  }
2720
2772
  }));
@@ -2723,7 +2775,7 @@ const VirtualEntryPlugin = (options)=>{
2723
2775
  ...null != options.exportsAsIndexes && {
2724
2776
  exportsAsIndexes: options.exportsAsIndexes
2725
2777
  },
2726
- ...false === options.bundle && {
2778
+ ...!bundle && {
2727
2779
  bundleless: true
2728
2780
  }
2729
2781
  }));
@@ -2732,27 +2784,31 @@ const VirtualEntryPlugin = (options)=>{
2732
2784
  target,
2733
2785
  pkg
2734
2786
  }) : void 0;
2735
- const libraryFormat = options.format ?? "esm";
2736
- const collapseIndex = (options.bundle ?? true) || !(options.exportsAsIndexes ?? false);
2737
- plugins.push(PackageJsonTransformPlugin({
2738
- forcePrivate: "dev" === target,
2739
- bundle: collapseIndex,
2740
- target,
2741
- format: libraryFormat,
2742
- ...transformFn && {
2743
- transform: transformFn
2744
- }
2745
- }));
2746
- plugins.push(FilesArrayPlugin({
2747
- target,
2748
- ...options.transformFiles && {
2749
- transformFiles: options.transformFiles
2750
- }
2751
- }));
2752
- plugins.push(...options.plugins);
2753
- const outputDir = `dist/${target}`;
2787
+ const formatOption = options.format ?? "esm";
2788
+ const formats = Array.isArray(formatOption) ? formatOption : [
2789
+ formatOption
2790
+ ];
2791
+ const primaryFormat = formats[0] ?? "esm";
2792
+ const isDualFormat = formats.length > 1;
2793
+ const entryFormats = options.entryFormats;
2794
+ const hasFormatOverrides = void 0 !== entryFormats && Object.keys(entryFormats).length > 0;
2795
+ const collapseIndex = bundle || !(options.exportsAsIndexes ?? false);
2796
+ const baseOutputDir = `dist/${target}`;
2797
+ const outputDir = isDualFormat ? `${baseOutputDir}/${primaryFormat}` : baseOutputDir;
2798
+ const apiModelForTarget = "npm" === target ? options.apiModel : void 0;
2799
+ const sourceMap = "dev" === target;
2800
+ const externalsConfig = options.externals && options.externals.length > 0 ? {
2801
+ externals: options.externals
2802
+ } : {};
2803
+ const bundlelessOutput = bundle ? {} : {
2804
+ legalComments: "inline"
2805
+ };
2806
+ const sourceDefine = {
2807
+ "process.env.__PACKAGE_VERSION__": JSON.stringify(VERSION),
2808
+ ...options.define
2809
+ };
2754
2810
  let entry = options.entry;
2755
- if (false === options.bundle && !entry) {
2811
+ if (!bundle && !entry) {
2756
2812
  const cwd = process.cwd();
2757
2813
  const packageJsonPath = join(cwd, "package.json");
2758
2814
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
@@ -2768,49 +2824,65 @@ const VirtualEntryPlugin = (options)=>{
2768
2824
  }
2769
2825
  entry = tracedEntries;
2770
2826
  }
2771
- const apiModelForTarget = "npm" === target ? options.apiModel : void 0;
2827
+ plugins.push(PackageJsonTransformPlugin({
2828
+ forcePrivate: "dev" === target,
2829
+ bundle: collapseIndex,
2830
+ target,
2831
+ format: primaryFormat,
2832
+ ...transformFn && {
2833
+ transform: transformFn
2834
+ },
2835
+ ...hasFormatOverrides && {
2836
+ entryFormats
2837
+ },
2838
+ ...isDualFormat && {
2839
+ dualFormat: true
2840
+ }
2841
+ }));
2842
+ plugins.push(FilesArrayPlugin({
2843
+ target,
2844
+ ...options.transformFiles && {
2845
+ transformFiles: options.transformFiles
2846
+ }
2847
+ }));
2848
+ plugins.push(...options.plugins);
2772
2849
  plugins.push(DtsPlugin({
2773
2850
  ...options.tsconfigPath && {
2774
2851
  tsconfigPath: options.tsconfigPath
2775
2852
  },
2776
2853
  abortOnError: true,
2777
- bundle: options.bundle ?? true,
2854
+ bundle,
2778
2855
  ...options.dtsBundledPackages && {
2779
2856
  bundledPackages: options.dtsBundledPackages
2780
2857
  },
2781
2858
  buildTarget: target,
2782
- format: libraryFormat,
2859
+ format: primaryFormat,
2783
2860
  ...void 0 !== apiModelForTarget && {
2784
2861
  apiModel: apiModelForTarget
2785
2862
  }
2786
2863
  }));
2787
2864
  const lib = {
2788
- id: target,
2789
- outBase: false === options.bundle ? "src" : outputDir,
2865
+ id: isDualFormat ? `${target}-${primaryFormat}` : target,
2866
+ outBase: bundle ? outputDir : "src",
2790
2867
  output: {
2791
2868
  target: "node",
2792
2869
  module: true,
2793
2870
  cleanDistPath: true,
2794
- sourceMap: "dev" === target,
2795
- // Prevent @preserve comments from generating .LICENSE.txt files
2796
- ...false === options.bundle && {
2797
- legalComments: "inline"
2798
- },
2871
+ sourceMap,
2872
+ ...bundlelessOutput,
2799
2873
  distPath: {
2800
2874
  root: outputDir
2801
2875
  },
2802
2876
  copy: {
2803
2877
  patterns: options.copyPatterns
2804
2878
  },
2805
- ...options.externals && options.externals.length > 0 && {
2806
- externals: options.externals
2807
- }
2879
+ ...externalsConfig
2808
2880
  },
2809
- format: libraryFormat,
2881
+ format: primaryFormat,
2810
2882
  experiments: {
2811
- advancedEsm: "esm" === libraryFormat
2883
+ advancedEsm: "esm" === primaryFormat
2812
2884
  },
2813
- bundle: options.bundle ?? true,
2885
+ bundle,
2814
2886
  plugins,
2815
2887
  source: {
2816
2888
  ...options.tsconfigPath && {
@@ -2819,10 +2891,7 @@ const VirtualEntryPlugin = (options)=>{
2819
2891
  ...entry && {
2820
2892
  entry
2821
2893
  },
2822
- define: {
2823
- "process.env.__PACKAGE_VERSION__": JSON.stringify(VERSION),
2824
- ...options.define
2825
- }
2894
+ define: sourceDefine
2826
2895
  }
2827
2896
  };
2828
2897
  const hasRegularEntries = void 0 !== options.entry || NodeLibraryBuilder.packageHasExports();
@@ -2830,11 +2899,129 @@ const VirtualEntryPlugin = (options)=>{
2830
2899
  const hasVirtualEntries = Object.keys(virtualEntries).length > 0;
2831
2900
  if (!hasRegularEntries && !hasVirtualEntries) throw new Error("No entry points configured. Provide package.json exports, explicit entry option, or virtualEntries.");
2832
2901
  const libConfigs = [];
2833
- if (hasRegularEntries) libConfigs.push(lib);
2902
+ if (hasRegularEntries) {
2903
+ libConfigs.push(lib);
2904
+ if (isDualFormat) for (const secondaryFormat of formats.slice(1)){
2905
+ const secondaryOutputDir = `${baseOutputDir}/${secondaryFormat}`;
2906
+ const secondaryPlugins = [
2907
+ FilesArrayPlugin({
2908
+ target
2909
+ }),
2910
+ DtsPlugin({
2911
+ ...options.tsconfigPath && {
2912
+ tsconfigPath: options.tsconfigPath
2913
+ },
2914
+ abortOnError: true,
2915
+ bundle,
2916
+ ...options.dtsBundledPackages && {
2917
+ bundledPackages: options.dtsBundledPackages
2918
+ },
2919
+ buildTarget: target,
2920
+ format: secondaryFormat
2921
+ })
2922
+ ];
2923
+ const secondaryLib = {
2924
+ id: `${target}-${secondaryFormat}`,
2925
+ outBase: bundle ? secondaryOutputDir : "src",
2926
+ output: {
2927
+ target: "node",
2928
+ cleanDistPath: false,
2929
+ sourceMap,
2930
+ ...bundlelessOutput,
2931
+ distPath: {
2932
+ root: secondaryOutputDir
2933
+ },
2934
+ ...externalsConfig
2935
+ },
2936
+ format: secondaryFormat,
2937
+ experiments: {
2938
+ advancedEsm: "esm" === secondaryFormat
2939
+ },
2940
+ bundle,
2941
+ plugins: secondaryPlugins,
2942
+ source: {
2943
+ ...options.tsconfigPath && {
2944
+ tsconfigPath: options.tsconfigPath
2945
+ },
2946
+ ...entry && {
2947
+ entry
2948
+ },
2949
+ define: sourceDefine
2950
+ }
2951
+ };
2952
+ libConfigs.push(secondaryLib);
2953
+ }
2954
+ if (hasFormatOverrides && !isDualFormat) {
2955
+ const cwd = process.cwd();
2956
+ const packageJsonPath = join(cwd, "package.json");
2957
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2958
+ const { entries: extractedEntries, exportPaths } = new EntryExtractor().extract(packageJson);
2959
+ const overridesByFormat = new Map();
2960
+ for (const [entryName, sourcePath] of Object.entries(extractedEntries)){
2961
+ const exportPath = exportPaths[entryName];
2962
+ if (exportPath && entryFormats?.[exportPath] && entryFormats[exportPath] !== primaryFormat) {
2963
+ const overrideFormat = entryFormats[exportPath];
2964
+ let formatEntries = overridesByFormat.get(overrideFormat);
2965
+ if (!formatEntries) {
2966
+ formatEntries = {};
2967
+ overridesByFormat.set(overrideFormat, formatEntries);
2968
+ }
2969
+ formatEntries[entryName] = sourcePath;
2970
+ }
2971
+ }
2972
+ for (const [overrideFormat, overrideEntries] of overridesByFormat){
2973
+ const overridePlugins = [
2974
+ FilesArrayPlugin({
2975
+ target
2976
+ }),
2977
+ DtsPlugin({
2978
+ ...options.tsconfigPath && {
2979
+ tsconfigPath: options.tsconfigPath
2980
+ },
2981
+ abortOnError: true,
2982
+ bundle,
2983
+ ...options.dtsBundledPackages && {
2984
+ bundledPackages: options.dtsBundledPackages
2985
+ },
2986
+ buildTarget: target,
2987
+ format: overrideFormat
2988
+ })
2989
+ ];
2990
+ const overrideLib = {
2991
+ id: `${target}-${overrideFormat}`,
2992
+ outBase: bundle ? baseOutputDir : "src",
2993
+ output: {
2994
+ target: "node",
2995
+ cleanDistPath: false,
2996
+ sourceMap,
2997
+ ...bundlelessOutput,
2998
+ distPath: {
2999
+ root: baseOutputDir
3000
+ },
3001
+ ...externalsConfig
3002
+ },
3003
+ format: overrideFormat,
3004
+ experiments: {
3005
+ advancedEsm: "esm" === overrideFormat
3006
+ },
3007
+ bundle,
3008
+ plugins: overridePlugins,
3009
+ source: {
3010
+ entry: overrideEntries,
3011
+ ...options.tsconfigPath && {
3012
+ tsconfigPath: options.tsconfigPath
3013
+ },
3014
+ define: sourceDefine
3015
+ }
3016
+ };
3017
+ libConfigs.push(overrideLib);
3018
+ }
3019
+ }
3020
+ }
2834
3021
  if (hasVirtualEntries) {
2835
3022
  const virtualByFormat = new Map();
2836
3023
  for (const [outputName, config] of Object.entries(virtualEntries)){
2837
- const entryFormat = config.format ?? libraryFormat;
3024
+ const entryFormat = config.format ?? primaryFormat;
2838
3025
  let formatMap = virtualByFormat.get(entryFormat);
2839
3026
  if (!formatMap) {
2840
3027
  formatMap = new Map();
@@ -2855,11 +3042,9 @@ const VirtualEntryPlugin = (options)=>{
2855
3042
  cleanDistPath: false,
2856
3043
  sourceMap: false,
2857
3044
  distPath: {
2858
- root: outputDir
3045
+ root: baseOutputDir
2859
3046
  },
2860
- ...options.externals && options.externals.length > 0 && {
2861
- externals: options.externals
2862
- }
3047
+ ...externalsConfig
2863
3048
  },
2864
3049
  source: {
2865
3050
  entry: entryMap
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/rslib-builder",
3
- "version": "0.12.2",
3
+ "version": "0.13.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
  "keywords": [
@@ -63,8 +63,8 @@
63
63
  "peerDependencies": {
64
64
  "@microsoft/api-extractor": "^7.56.3",
65
65
  "@rslib/core": "^0.19.5",
66
- "@types/node": "^25.2.2",
67
- "@typescript/native-preview": "^7.0.0-dev.20260209.1",
66
+ "@types/node": "^25.2.3",
67
+ "@typescript/native-preview": "7.0.0-dev.20260210.1",
68
68
  "typescript": "^5.9.3"
69
69
  },
70
70
  "peerDependenciesMeta": {