dtsroll 1.5.0 → 1.7.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/README.md CHANGED
@@ -1,152 +1,160 @@
1
1
  <p align="center">
2
- <img width="150" src="./.github/logo.webp">
2
+ <img width="200" src="./.github/logo.webp">
3
3
  </p>
4
+ <h2 align="center">dtsroll</h2>
4
5
 
5
- <h2 align="center">
6
- dtsroll
7
- </h2>
6
+ Are you publishing a TypeScript project where consumers encounter type-checking errors like:
8
7
 
9
- _dtsroll_ is a CLI tool for bundling TypeScript declaration (`.d.ts`) files.
8
+ ```
9
+ Cannot find module 'some-package' or its corresponding type declarations.
10
+ ```
10
11
 
11
- ### Why bundle `.d.ts` files?
12
+ When you compile with `tsc`, the emitted declaration files (`.d.ts` files) preserve imports exactly as written. So if your published types import from a `devDependency` or a private package (e.g. an internal monorepo package), those imports cannot be resolved by the consumer.
12
13
 
13
- - **Smaller distribution**
14
+ ```ts
15
+ // dist/index.d.ts (generated by tsc)
16
+ import type { SomeType } from 'my-private-dependency' // ❌ consumers can't resolve this
14
17
 
15
- Tree-shaking removes unused code, keeping only what's needed and reducing the output size.
18
+ export declare function myUtility(): SomeType
19
+ ```
16
20
 
17
- - **Bundle in private dependencies**
21
+ If you can't move the dependency to `dependencies`, or you just want its types pulled directly into your published declarations, _dtsroll_ is for you.
18
22
 
19
- Inline types from private dependencies (e.g., monorepo packages) that aren't accessible to consumers.
23
+ ## What is dtsroll?
20
24
 
21
- - **Improve TS performance**
25
+ _dtsroll_ is a TypeScript declaration (`.d.ts`) file bundler. It's zero-config and reads your `package.json` to determine how your types should be bundled.
22
26
 
23
- Flattens multiple files into one, reducing TypeScript's file resolution for type checking.
27
+ ### What dtsroll does
24
28
 
25
- <p align="center">
26
- <img width="600" src="./.github/screenshot.webp">
27
- </p>
29
+ _dtsroll_ runs *after your build when `.d.ts` files have been emitted*, and works in-place to bundle them to their entry points.
28
30
 
31
+ Since packages declared in `devDependencies` are not installed for the consumer, _dtsroll_ assumes any imports referencing them should be bundled, as they would otherwise be unresolvable.
29
32
 
30
- ## Install
31
- ```
32
- npm install --save-dev dtsroll
33
- ```
33
+ The result is a single, clean `.d.ts` output that works for consumers without extra installs.
34
34
 
35
- ## Quick start
35
+ ```ts
36
+ // dist/index.d.ts (after dtsroll)
37
+ type SomeType = {
38
+ value: string
39
+ }
36
40
 
37
- 1. Compile your TypeScript code with declaration (`.d.ts`) files
38
- - If using the TypeScript compiler (`tsc`), enable [`declaration`](https://www.typescriptlang.org/tsconfig/#declaration)
39
- - If using Vite, use a plugin like [vite-plugin-dts](https://www.npmjs.com/package/vite-plugin-dts)
41
+ export declare function myUtility(): SomeType
42
+ ```
40
43
 
41
- 2. Pass in the entry declaration file to _dtsroll_
44
+ ### Features
42
45
 
43
- ```sh
44
- dtsroll --dry-run dist/index.d.ts
45
- ```
46
+ - **Zero config** — Automatically finds entry points from `package.json`.
47
+ - **Fixes missing-type errors** — Inlines types from `devDependencies` so consumers don't need them installed.
48
+ - **Tree-shaken output** — Unused types are removed to keep files small.
49
+ - **In-place** — Designed to run directly on your `dist` folder after compilation.
46
50
 
47
- > [!CAUTION]
48
- > _dtsroll_ is designed to run on compiled output so it modifies files in-place.
49
- > - It will modify files you pass in, and files they import.
50
- > - Only pass in backed up or generated files
51
- > - Start with `--dry-run`
51
+ <p align="center">
52
+ <img width="600" src="./.github/screenshot.webp">
53
+ </p>
54
+
55
+ ## Install
52
56
 
53
- 3. If the changes look good, remove the `--dry-run` flag:
57
+ ```bash
58
+ npm install --save-dev dtsroll
59
+ ```
54
60
 
55
- ```sh
56
- dtsroll dist/index.d.ts
57
- ```
61
+ ## Quick start
58
62
 
59
- ### Recommended setup
63
+ ### 1. Configure `package.json`
64
+
65
+ Point your `types` or `exports` to the final `.d.ts` file you want to publish.
66
+
67
+ ```jsonc
68
+ {
69
+ "name": "my-package",
70
+ "exports": {
71
+ "types": "./dist/index.d.ts", // dtsroll targets this
72
+ "default": "./dist/index.js",
73
+ },
74
+ "scripts": {
75
+ "build": "tsc && dtsroll",
76
+ },
77
+ }
78
+ ```
60
79
 
61
- Running `dtsroll` without specifying input files will auto-detect them from `package.json`.
80
+ ### 2. Build
62
81
 
63
- Update your `package.json` to reference `.d.ts` files and include `dtsroll` in the build step:
64
- ```diff
65
- {
66
- "name": "my-package",
67
- "exports": {
68
- + "types": "./dist/index.d.ts",
69
- "default": "./dist/index.js"
70
- },
71
- "scripts": {
72
- + "build": "tsc && dtsroll"
73
- }
74
- }
82
+ ```bash
83
+ npm run build
75
84
  ```
76
85
 
77
- ### Externalization
86
+ That's it.
78
87
 
79
- By default, _dtsroll_ decides which dependencies to bundle or keep external by analyzing the `package.json` file. Packages in `devDependencies` are bundled, and packages in other dependency types are externalized.
88
+ > [!WARNING]
89
+ > _dtsroll_ modifies files in-place—bundled source files are removed and entry files are overwritten with bundled output.
90
+ > Use `--dry-run` first to see what it would change:
91
+ >
92
+ > ```bash
93
+ > dtsroll --dry-run
94
+ > ```
80
95
 
81
- When there is no `package.json` file, you can specify packages to externalize with the `--external` flag.
96
+ ## Behavior
82
97
 
83
- #### Handling `@types` packages
98
+ ### Automatic configuration
84
99
 
85
- Some packages need separate `@types/*` packages for type definitions. In this setup, typically:
100
+ By default, _dtsroll_ reads your `package.json` to determine which imports should be bundled and which should remain external. The recommended setup is to run _dtsroll_ without any configuration and let it infer the correct behavior based on your dependency declarations.
86
101
 
87
- - The main package is in `dependencies`.
88
- - The corresponding `@types/*` package is in `devDependencies`.
102
+ | Dependency type | Action | Reason |
103
+ | ---------------------- | ----------- | ----------------------------- |
104
+ | `devDependencies` | Bundle | Consumers don't install these |
105
+ | `dependencies` | Externalize | Consumers already have them |
106
+ | `optionalDependencies` | Externalize | Consumers already have them |
107
+ | `peerDependencies` | Externalize | Provided by the consumer |
89
108
 
90
- Because the main package is in `dependencies`, _dtsroll_ externalizes it. However, consumers of your package won’t get the type definitions for it because the `@types/*` package is only in `devDependencies`.
109
+ If you have a `@types/*` package in `devDependencies` but the corresponding runtime package in `dependencies`, _dtsroll_ will recommend moving the types package to `dependencies`, as this can otherwise result in missing types for consumers.
91
110
 
92
- To fix this, _dtsroll_ will display a warning suggesting you move the `@types/*` package out of `devDependencies`.
111
+ ### Manual configuration
93
112
 
94
- ## CLI
113
+ If your project doesn't have a `package.json` file, you can still manually specify the input files (which entry files to collapse the `imports` into), and which packages to externalize.
95
114
 
96
- ### Usage
97
- ```sh
98
- dtsroll [-flags] [...entry dts files]
99
- ```
115
+ ### Subpath imports
100
116
 
101
- The default usage is to run `dtsroll` without any flags/arguments in a directory containing a `package.json` file. This allows the configuration to be inferred automatically.
117
+ > [!WARNING]
118
+ > Currently, _dtsroll_ mistakenly resolves and bundles [subpath imports](https://nodejs.org/api/packages.html#subpath-imports). Subpath imports are intended to be dynamic aliases controlled by the consumer. In a future breaking release, _dtsroll_ will likely externalize them to preserve this behavior.
102
119
 
103
- ### --help, -h
104
- Display usage instructions.
120
+ ## Usage
105
121
 
106
- ### --dry-run, -d
107
- Simulate the bundling process without modifying the disk and logs what would happen.
122
+ _dtsroll_ can be used in several ways.
108
123
 
109
- ### --external, -e
110
- If there is no `package.json` file, you can specify package names to exclude from the bundle.
124
+ ### CLI usage
111
125
 
112
- > [!NOTE]
113
- > This flag can only be used when there is no `package.json`. It's better to define dependencies appropriately in `package.json` instead of using this flag.
126
+ ```bash
127
+ dtsroll [flags] [...entry .d.ts files]
128
+ ```
114
129
 
115
- ### --conditions, -C
116
- Provide resolution conditions to target specific entry points in dependencies, similar to Node’s [`--conditions`](https://nodejs.org/api/cli.html#-c-condition---conditionscondition).
130
+ If no entry files are provided, _dtsroll_ reads `package.json` to determine them.
117
131
 
118
- ## Node.js API
119
- ```ts
120
- import { dtsroll } from 'dtsroll'
132
+ #### Flags
121
133
 
122
- await dtsroll(options as {
134
+ | Flag | Alias | Description |
135
+ | -------------- | ----- | ------------------------------------------------- |
136
+ | `--dry-run` | `-d` | Show what would be bundled without writing files |
137
+ | `--sourcemap` | `-s` | Generate source maps (`.d.ts.map` files) |
138
+ | `--conditions` | `-C` | Resolution conditions for [subpath exports](https://nodejs.org/api/packages.html#subpath-exports) (e.g. `production`) |
139
+ | `--external` | `-e` | *(Only when no `package.json`)* Packages to externalize |
123
140
 
124
- /**
125
- * CWD to find the package.json in
126
- * @default process.cwd()
127
- */
128
- cwd?: string
141
+ #### Why use `--sourcemap`?
129
142
 
130
- /**
131
- * Defaults to auto-detecting d.ts files from package.json
132
- */
133
- inputs?: string[]
143
+ Without source maps, "Go to Definition" in VS Code lands you in bundled `.d.ts` files—often a flattened wall of generated types that's hard to navigate.
134
144
 
135
- /**
136
- * Only used if there's no package.json
137
- * Defaults to auto-detecting dependencies from package.json
138
- */
139
- external?: string[]
145
+ With `--sourcemap`, _dtsroll_ generates `.d.ts.map` files that map positions in the bundled output back to your original source files. This lets VS Code jump directly to the actual TypeScript implementation instead of the generated declarations.
140
146
 
141
- conditions?: string[]
147
+ This is especially useful for:
148
+ - **Monorepos** — Navigate seamlessly across packages to real source
149
+ - **Library authors** — Give consumers a better DX when exploring your types
150
+ - **Anyone debugging types** — Understand types at their origin, not the emitted output
142
151
 
143
- dryRun?: boolean
144
- })
145
- ```
152
+ > [!NOTE]
153
+ > For source navigation to work, the original `.ts` source files must be available (either shipped with your package or present locally). If they're not, VS Code falls back to the `.d.ts` file.
146
154
 
147
- ## Vite plugin
155
+ ### Vite plugin
148
156
 
149
- Use it in conjunction with a plugin that generates the initial declaration files like `vite-plugin-dts`.
157
+ If you use `vite-plugin-dts`, _dtsroll_ will automatically bundle the emitted types immediately after generation:
150
158
 
151
159
  ```ts
152
160
  import { defineConfig } from 'vitest/config'
@@ -154,16 +162,25 @@ import dts from 'vite-plugin-dts'
154
162
  import { dtsroll } from 'dtsroll/vite'
155
163
 
156
164
  export default defineConfig({
157
- // ...
158
165
  plugins: [
159
- // ...
160
166
  dts(),
161
167
  dtsroll()
162
168
  ]
163
169
  })
164
170
  ```
165
171
 
172
+ ### Node API
173
+
174
+ ```ts
175
+ import { dtsroll } from 'dtsroll'
176
+
177
+ await dtsroll({
178
+ cwd: process.cwd(),
179
+ dryRun: false,
180
+ sourcemap: true // generates .d.ts.map files
181
+ })
182
+ ```
183
+
166
184
  ## Related
167
185
 
168
- ### pkgroll
169
- For package bundling (along with dts bundling), check out [pkgroll](https://github.com/privatenumber/pkgroll).
186
+ - [pkgroll](https://github.com/privatenumber/pkgroll) — Zero-config JS + DTS bundler
package/dist/cli.mjs CHANGED
@@ -1,18 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { cli } from 'cleye';
3
- import { b as bgYellow, a as black, d as dtsroll, l as logOutput } from './index-DfxmCmBK.mjs';
3
+ import { b as bgYellow, a as black, d as dtsroll, l as logOutput, D as DtsrollBuildError } from './index-r9RZgCg7.mjs';
4
4
  import 'node:path';
5
5
  import 'node:fs/promises';
6
6
  import 'rollup';
7
7
  import 'rollup-plugin-dts';
8
8
  import '@rollup/plugin-node-resolve';
9
9
  import 'node:fs';
10
+ import 'convert-source-map';
10
11
  import 'empathic/find';
11
12
  import 'resolve-pkg-maps';
12
13
  import 'byte-size';
13
14
 
14
15
  var name = "dtsroll";
15
- var version = "1.5.0";
16
+ var version = "1.7.0";
16
17
  var description = "Bundle dts files";
17
18
 
18
19
  const argv = cli({
@@ -21,6 +22,7 @@ const argv = cli({
21
22
  help: {
22
23
  description
23
24
  },
25
+ strictFlags: true,
24
26
  parameters: ["[input files...]"],
25
27
  flags: {
26
28
  conditions: {
@@ -37,32 +39,46 @@ const argv = cli({
37
39
  type: [String],
38
40
  alias: "e",
39
41
  description: "Dependency to externalize"
42
+ },
43
+ sourcemap: {
44
+ type: Boolean,
45
+ alias: "s",
46
+ description: "Generate sourcemaps"
40
47
  }
41
- // sourcemap: {
42
- // type: Boolean,
43
- // description: 'Generate sourcemaps',
44
- // },
45
48
  }
46
49
  });
47
50
  const { flags } = argv;
48
- const dryMode = flags.dryRun;
49
- if (dryMode) {
51
+ if (flags.dryRun) {
50
52
  console.log(bgYellow(black(" Dry run - No files will be written ")));
51
53
  }
52
54
  dtsroll({
53
55
  inputs: argv._.inputFiles,
54
56
  external: flags.external,
55
57
  conditions: flags.conditions,
56
- dryRun: flags.dryRun
58
+ dryRun: flags.dryRun,
59
+ sourcemap: flags.sourcemap
57
60
  }).then(
58
61
  (output) => {
59
62
  if ("error" in output) {
60
63
  process.exitCode = 1;
61
64
  }
62
65
  logOutput(output);
63
- },
66
+ }
67
+ ).catch(
64
68
  (error) => {
65
- console.error("\nFailed to build:", error.message);
69
+ let errorMessage = "\nFailed to build";
70
+ if (error instanceof DtsrollBuildError) {
71
+ errorMessage += `
72
+ File: ${error.id}`;
73
+ if (error.importChain.length > 1) {
74
+ errorMessage += "\n\n Import chain:\n ";
75
+ errorMessage += error.importChain.join("\n \u2192 ");
76
+ }
77
+ }
78
+ errorMessage += `
79
+
80
+ ${error instanceof Error ? error.message : String(error)}`;
81
+ console.error(errorMessage);
66
82
  process.exitCode = 1;
67
83
  }
68
84
  );
@@ -4,6 +4,7 @@ import { rollup } from 'rollup';
4
4
  import { dts } from 'rollup-plugin-dts';
5
5
  import nodeResolve from '@rollup/plugin-node-resolve';
6
6
  import fs$1 from 'node:fs';
7
+ import convert from 'convert-source-map';
7
8
  import { up } from 'empathic/find';
8
9
  import { resolveImports } from 'resolve-pkg-maps';
9
10
  import byteSize from 'byte-size';
@@ -72,7 +73,13 @@ const bgYellow = kolorist(43, 49);
72
73
  const cwd = process.cwd();
73
74
 
74
75
  const isPath = (filePath) => filePath[0] === "." || path.isAbsolute(filePath);
75
- const normalizePath = (filepath) => filepath.replaceAll("\\", "/");
76
+ const normalizePath$1 = (filepath) => filepath.replaceAll("\\", "/");
77
+ const getDisplayPath = (fullPath) => {
78
+ const relativePath = path.relative(cwd, fullPath);
79
+ return normalizePath$1(
80
+ relativePath.length < fullPath.length ? relativePath : fullPath
81
+ );
82
+ };
76
83
 
77
84
  const warningSignUnicode = "\u26A0";
78
85
  const warningPrefix = yellow("Warning:");
@@ -84,10 +91,7 @@ const logOutput = (dtsOutput) => {
84
91
  \u{1F4E5} Entry points${isCliInput ? "" : " in package.json"}`));
85
92
  console.log(
86
93
  inputs.map(([inputFile, inputSource, error]) => {
87
- const relativeInputFile = path.relative(cwd, inputFile);
88
- const logPath2 = normalizePath(
89
- relativeInputFile.length < inputFile.length ? relativeInputFile : inputFile
90
- );
94
+ const logPath2 = getDisplayPath(inputFile);
91
95
  if (error) {
92
96
  return ` ${lightYellow(`${warningSignUnicode} ${logPath2} ${dim(error)}`)}`;
93
97
  }
@@ -107,10 +111,7 @@ const logOutput = (dtsOutput) => {
107
111
  size,
108
112
  externals
109
113
  } = dtsOutput;
110
- const outputDirectoryRelative = path.relative(cwd, outputDirectory);
111
- const logPath = `${normalizePath(
112
- outputDirectoryRelative.length < outputDirectory.length ? outputDirectoryRelative : outputDirectory
113
- )}/`;
114
+ const logPath = `${getDisplayPath(outputDirectory)}/`;
114
115
  const logChunk = ({
115
116
  file,
116
117
  indent,
@@ -124,10 +125,7 @@ const logOutput = (dtsOutput) => {
124
125
  ${moduleIds.sort().map((moduleId, index) => {
125
126
  const isLast = index === moduleIds.length - 1;
126
127
  const prefix = `${indent} ${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}`;
127
- const relativeModuleId = path.relative(cwd, moduleId);
128
- const logModuleId = normalizePath(
129
- relativeModuleId.length < moduleId.length ? relativeModuleId : moduleId
130
- );
128
+ const logModuleId = getDisplayPath(moduleId);
131
129
  const bareSpecifier = moduleToPackage[moduleId];
132
130
  if (bareSpecifier) {
133
131
  return `${prefix}${dim(`${magenta(bareSpecifier)} (${logModuleId})`)}`;
@@ -177,6 +175,17 @@ ${moduleIds.sort().map((moduleId, index) => {
177
175
  }
178
176
  };
179
177
 
178
+ class DtsrollBuildError extends Error {
179
+ id;
180
+ importChain;
181
+ constructor(message, id, importChain) {
182
+ super(message);
183
+ this.name = "DtsrollBuildError";
184
+ this.id = id;
185
+ this.importChain = importChain;
186
+ }
187
+ }
188
+
180
189
  const dtsExtensions = [".d.ts", ".d.cts", ".d.mts"];
181
190
  const isDts = (fileName) => dtsExtensions.some((extension) => fileName.endsWith(extension));
182
191
 
@@ -269,6 +278,7 @@ const getPackageName = (id) => {
269
278
  return id.slice(0, indexOfSlash);
270
279
  };
271
280
 
281
+ const normalizePath = (filePath) => filePath.replaceAll("\\", "/");
272
282
  const getAllFiles = async (directoryPath, dontShortenPath) => {
273
283
  const directoryFiles = await fs.readdir(directoryPath, { withFileTypes: true });
274
284
  const fileTree = await Promise.all(
@@ -276,9 +286,9 @@ const getAllFiles = async (directoryPath, dontShortenPath) => {
276
286
  const filePath = path.join(directoryPath, entry.name);
277
287
  if (entry.isDirectory()) {
278
288
  const files = await getAllFiles(filePath, true);
279
- return dontShortenPath ? files : files.map((file) => `./${path.relative(directoryPath, file)}`);
289
+ return dontShortenPath ? files : files.map((file) => `./${normalizePath(path.relative(directoryPath, file))}`);
280
290
  }
281
- return dontShortenPath ? filePath : `./${path.relative(directoryPath, filePath)}`;
291
+ return dontShortenPath ? filePath : `./${normalizePath(path.relative(directoryPath, filePath))}`;
282
292
  })
283
293
  );
284
294
  return fileTree.flat();
@@ -473,6 +483,129 @@ const createExternalizePlugin = (configuredExternals) => {
473
483
  };
474
484
  };
475
485
 
486
+ const createImportChainPlugin = () => {
487
+ const importerMap = /* @__PURE__ */ new Map();
488
+ const plugin = {
489
+ name: "import-chain-tracker",
490
+ buildStart: () => {
491
+ importerMap.clear();
492
+ },
493
+ async resolveId(source, importer) {
494
+ if (!importer) {
495
+ return null;
496
+ }
497
+ const resolved = await this.resolve(source, importer, { skipSelf: true });
498
+ if (resolved && !resolved.external && !importerMap.has(resolved.id)) {
499
+ importerMap.set(resolved.id, importer);
500
+ }
501
+ return null;
502
+ }
503
+ };
504
+ const getImportChain = (errorFileId) => {
505
+ const chain = [];
506
+ let current = errorFileId;
507
+ while (current) {
508
+ chain.unshift(current);
509
+ current = importerMap.get(current);
510
+ }
511
+ return chain;
512
+ };
513
+ return {
514
+ plugin,
515
+ getImportChain
516
+ };
517
+ };
518
+
519
+ const tryReadFile = async (filePath) => {
520
+ try {
521
+ return await fs$1.promises.readFile(filePath, "utf8");
522
+ } catch {
523
+ return null;
524
+ }
525
+ };
526
+ const loadSourceMap = async (codePath, code) => {
527
+ const adjacentMapPath = `${codePath}.map`;
528
+ const adjacentMapContent = await tryReadFile(adjacentMapPath);
529
+ if (adjacentMapContent) {
530
+ try {
531
+ const converter = convert.fromJSON(adjacentMapContent);
532
+ return {
533
+ map: converter.toObject(),
534
+ mapPath: adjacentMapPath
535
+ };
536
+ } catch {
537
+ }
538
+ }
539
+ try {
540
+ const inlineConverter = convert.fromSource(code);
541
+ if (inlineConverter) {
542
+ return {
543
+ map: inlineConverter.toObject(),
544
+ mapPath: codePath
545
+ };
546
+ }
547
+ } catch {
548
+ }
549
+ try {
550
+ const regex = new RegExp(convert.mapFileCommentRegex.source);
551
+ const commentMatch = regex.exec(code);
552
+ const referencedPath = commentMatch?.[1] ?? commentMatch?.[2];
553
+ if (!referencedPath) {
554
+ return;
555
+ }
556
+ const mapFilePath = path.join(path.dirname(codePath), referencedPath);
557
+ const mapContent = await tryReadFile(mapFilePath);
558
+ if (!mapContent) {
559
+ return;
560
+ }
561
+ const converter = convert.fromJSON(mapContent);
562
+ return {
563
+ map: converter.toObject(),
564
+ mapPath: mapFilePath
565
+ };
566
+ } catch {
567
+ }
568
+ };
569
+ const loadInputSourcemapsPlugin = () => ({
570
+ name: "load-input-sourcemaps",
571
+ async load(id) {
572
+ const isDts = dtsExtensions.some((extension) => id.endsWith(extension));
573
+ if (!isDts) {
574
+ return null;
575
+ }
576
+ const code = await tryReadFile(id);
577
+ if (!code) {
578
+ return null;
579
+ }
580
+ const result = await loadSourceMap(id, code);
581
+ if (!result) {
582
+ return { code };
583
+ }
584
+ const { map: inputMap, mapPath } = result;
585
+ const sourceRoot = path.resolve(path.dirname(mapPath), inputMap.sourceRoot ?? ".");
586
+ const sources = inputMap.sources.map(
587
+ (source) => path.isAbsolute(source) ? source : path.resolve(sourceRoot, source)
588
+ );
589
+ const sourcesContentRaw = await Promise.all(
590
+ sources.map(async (source, index) => inputMap.sourcesContent?.[index] ?? tryReadFile(source))
591
+ );
592
+ const sourcesContent = sourcesContentRaw.filter(
593
+ (content) => content !== null
594
+ );
595
+ return {
596
+ code,
597
+ map: {
598
+ version: inputMap.version,
599
+ names: inputMap.names,
600
+ sources,
601
+ mappings: inputMap.mappings,
602
+ ...sourcesContent.length > 0 ? { sourcesContent } : {},
603
+ ...inputMap.file ? { file: inputMap.file } : {}
604
+ }
605
+ };
606
+ }
607
+ });
608
+
476
609
  const nodeModules = `${path.sep}node_modules${path.sep}`;
477
610
  const removeBundledModulesPlugin = (outputDirectory, sizeRef) => {
478
611
  let deleteFiles = [];
@@ -490,14 +623,17 @@ const removeBundledModulesPlugin = (outputDirectory, sizeRef) => {
490
623
  async generateBundle(options, bundle) {
491
624
  const modules = Object.values(bundle);
492
625
  const bundledFiles = Array.from(new Set(modules.flatMap(({ moduleIds }) => moduleIds)));
493
- const fileSizes = bundledFiles.map((moduleId) => this.getModuleInfo(moduleId).meta);
494
- const totalSize = fileSizes.reduce((total, { size }) => total + size, 0);
626
+ let totalSize = 0;
627
+ for (const moduleId of bundledFiles) {
628
+ const moduleInfo = this.getModuleInfo(moduleId);
629
+ const size = moduleInfo?.meta?.size;
630
+ if (typeof size === "number") {
631
+ totalSize += size;
632
+ }
633
+ }
495
634
  sizeRef.value = totalSize;
496
635
  const outputFiles = new Set(modules.map(({ fileName }) => path.join(options.dir, fileName)));
497
- deleteFiles = bundledFiles.filter((moduleId) => (
498
- // To avoid deleting files from symlinked dependencies
499
- moduleId.startsWith(outputDirectory) && !moduleId.includes(nodeModules) && !outputFiles.has(moduleId)
500
- ));
636
+ deleteFiles = bundledFiles.filter((moduleId) => moduleId && moduleId.startsWith(outputDirectory) && !moduleId.includes(nodeModules) && !outputFiles.has(moduleId));
501
637
  },
502
638
  writeBundle: async () => {
503
639
  await Promise.all(
@@ -508,7 +644,7 @@ const removeBundledModulesPlugin = (outputDirectory, sizeRef) => {
508
644
  };
509
645
 
510
646
  const packageJsonCache = /* @__PURE__ */ new Map();
511
- const findPackageJsonUp = (cwd) => {
647
+ const findPackageJsonUp = async (cwd) => {
512
648
  const packageJsonPath = up("package.json", { cwd });
513
649
  if (!packageJsonPath) {
514
650
  return void 0;
@@ -517,7 +653,7 @@ const findPackageJsonUp = (cwd) => {
517
653
  let packageJson = packageJsonCache.get(packageRoot);
518
654
  if (!packageJson) {
519
655
  try {
520
- const content = fs$1.readFileSync(packageJsonPath, "utf8");
656
+ const content = await fs.readFile(packageJsonPath, "utf8");
521
657
  packageJson = JSON.parse(content);
522
658
  packageJsonCache.set(packageRoot, packageJson);
523
659
  } catch {
@@ -537,7 +673,7 @@ const resolveSubpathImportsPlugin = () => ({
537
673
  if (id[0] !== "#" || !importer) {
538
674
  return null;
539
675
  }
540
- const result = findPackageJsonUp(path.dirname(importer));
676
+ const result = await findPackageJsonUp(path.dirname(importer));
541
677
  if (!result) {
542
678
  return null;
543
679
  }
@@ -565,25 +701,29 @@ const createInputMap = (input, outputDirectory) => Object.fromEntries(
565
701
  inputFile
566
702
  ])
567
703
  );
568
- const build = async (input, outputDirectory, externals, mode, conditions) => {
704
+ const build = async (input, outputDirectory, externals, mode, conditions, sourcemap) => {
569
705
  const {
570
706
  externalizePlugin,
571
707
  externalized,
572
708
  getPackageEntryPoint
573
709
  } = createExternalizePlugin(externals);
710
+ const { plugin: importChainPlugin, getImportChain } = createImportChainPlugin();
574
711
  const sizeRef = {};
575
712
  const rollupConfig = {
576
713
  input: createInputMap(input, outputDirectory),
577
714
  output: {
578
- // sourcemap: true,
715
+ sourcemap,
579
716
  dir: outputDirectory,
580
717
  entryFileNames: "[name]",
581
718
  chunkFileNames: "_dtsroll-chunks/[hash]-[name].ts"
582
719
  },
583
720
  plugins: [
721
+ importChainPlugin,
584
722
  externalizePlugin,
585
723
  removeBundledModulesPlugin(outputDirectory, sizeRef),
586
724
  resolveSubpathImportsPlugin(),
725
+ // Load existing .d.ts.map files to chain sourcemaps back to original .ts sources
726
+ sourcemap && loadInputSourcemapsPlugin(),
587
727
  nodeResolve({
588
728
  extensions: [".ts", ...dtsExtensions],
589
729
  exportConditions: conditions
@@ -600,15 +740,26 @@ const build = async (input, outputDirectory, externals, mode, conditions) => {
600
740
  })
601
741
  ]
602
742
  };
603
- const rollupBuild = await rollup(rollupConfig);
604
- const built = await rollupBuild[mode](rollupConfig.output);
605
- await rollupBuild.close();
606
- return {
607
- built,
608
- externalized,
609
- getPackageEntryPoint,
610
- sourceSize: sizeRef.value
611
- };
743
+ try {
744
+ const rollupBuild = await rollup(rollupConfig);
745
+ const built = await rollupBuild[mode](rollupConfig.output);
746
+ await rollupBuild.close();
747
+ return {
748
+ built,
749
+ externalized,
750
+ getPackageEntryPoint,
751
+ sourceSize: sizeRef.value ?? 0
752
+ };
753
+ } catch (error) {
754
+ if (error instanceof Error && "id" in error && typeof error.id === "string") {
755
+ throw new DtsrollBuildError(
756
+ error.message,
757
+ error.id,
758
+ getImportChain(error.id)
759
+ );
760
+ }
761
+ throw error;
762
+ }
612
763
  };
613
764
 
614
765
  const dtsroll = async ({
@@ -616,7 +767,8 @@ const dtsroll = async ({
616
767
  inputs,
617
768
  external,
618
769
  conditions,
619
- dryRun
770
+ dryRun,
771
+ sourcemap
620
772
  } = {}) => {
621
773
  const pkgJson = await getPackageJson(cwd);
622
774
  const externals = pkgJson ? pkgJson.getExternals() : /* @__PURE__ */ new Map();
@@ -651,13 +803,15 @@ const dtsroll = async ({
651
803
  outputDirectory,
652
804
  externals,
653
805
  dryRun ? "generate" : "write",
654
- conditions
806
+ conditions,
807
+ sourcemap
655
808
  );
656
809
  let outputSize = 0;
657
810
  const outputEntries = [];
658
811
  const outputChunks = [];
659
812
  const moduleImports = /* @__PURE__ */ new Set();
660
- for (const file of built.output) {
813
+ const chunks = built.output.filter((file) => file.type === "chunk");
814
+ for (const file of chunks) {
661
815
  const size = Buffer.byteLength(file.code);
662
816
  outputSize += size;
663
817
  const moduleToPackage = Object.fromEntries(
@@ -702,4 +856,4 @@ const dtsroll = async ({
702
856
  };
703
857
  };
704
858
 
705
- export { black as a, bgYellow as b, dtsroll as d, logOutput as l };
859
+ export { DtsrollBuildError as D, black as a, bgYellow as b, dtsroll as d, logOutput as l };
package/dist/index.d.ts CHANGED
@@ -1,19 +1,36 @@
1
1
  import { OutputChunk } from 'rollup';
2
2
 
3
+ /**
4
+ * Extended output chunk with additional metadata.
5
+ */
3
6
  type Output = OutputChunk & {
7
+ /** Size of the output file in bytes. */
4
8
  size: number;
9
+ /** Map of module IDs to their package names. */
5
10
  moduleToPackage: Record<string, string | undefined>;
6
11
  };
12
+ /**
13
+ * List of externalized packages with metadata.
14
+ * Each entry is [packageName, reason, warning?].
15
+ */
7
16
  type Externals = [
8
17
  packageName: string,
9
18
  reason?: string,
10
19
  warning?: string
11
20
  ][];
21
+ /**
22
+ * Validated input file with source info and optional error.
23
+ * Tuple format: [inputPath, inputSource, error?].
24
+ */
12
25
  type ValidatedInput = [
13
26
  inputPath: string,
14
27
  inputSource: string | undefined,
15
28
  error?: string
16
29
  ];
30
+ /**
31
+ * Output from the dtsroll build process.
32
+ * Returns either an error state or successful build results.
33
+ */
17
34
  type DtsrollOutput = {
18
35
  inputs: ValidatedInput[];
19
36
  error: string;
@@ -31,14 +48,30 @@ type DtsrollOutput = {
31
48
  externals: Externals;
32
49
  };
33
50
 
51
+ /**
52
+ * Configuration options for dtsroll.
53
+ */
34
54
  type Options = {
55
+ /** Working directory. Defaults to process.cwd(). */
35
56
  cwd?: string;
57
+ /** Input .d.ts files to bundle. If not provided, auto-detects from package.json. */
36
58
  inputs?: string[];
59
+ /** Packages to externalize (only used when no package.json is present). */
37
60
  external?: string[];
61
+ /** Export conditions for module resolution. */
38
62
  conditions?: string[];
63
+ /** If true, generates output without writing files. */
39
64
  dryRun?: boolean;
65
+ /** If true, generates source maps (.d.ts.map files). */
66
+ sourcemap?: boolean;
40
67
  };
41
- declare const dtsroll: ({ cwd, inputs, external, conditions, dryRun, }?: Options) => Promise<DtsrollOutput>;
68
+ /**
69
+ * Bundle TypeScript declaration files using Rollup.
70
+ *
71
+ * @param options - Configuration options
72
+ * @returns Build output including bundled files, sizes, and externalized packages
73
+ */
74
+ declare const dtsroll: ({ cwd, inputs, external, conditions, dryRun, sourcemap, }?: Options) => Promise<DtsrollOutput>;
42
75
 
43
76
  export { dtsroll };
44
77
  export type { DtsrollOutput, Options };
package/dist/index.mjs CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import 'node:path';
3
- export { d as dtsroll } from './index-DfxmCmBK.mjs';
3
+ export { d as dtsroll } from './index-r9RZgCg7.mjs';
4
4
  import 'node:fs/promises';
5
5
  import 'rollup';
6
6
  import 'rollup-plugin-dts';
7
7
  import '@rollup/plugin-node-resolve';
8
8
  import 'node:fs';
9
+ import 'convert-source-map';
9
10
  import 'empathic/find';
10
11
  import 'resolve-pkg-maps';
11
12
  import 'byte-size';
package/dist/vite.d.ts CHANGED
@@ -2,6 +2,13 @@ import { Plugin } from 'vite';
2
2
  import { Options } from './index.js';
3
3
  import 'rollup';
4
4
 
5
+ /**
6
+ * Vite plugin for bundling TypeScript declaration files.
7
+ * Runs after vite-plugin-dts in the writeBundle hook.
8
+ *
9
+ * @param options - Configuration options (same as dtsroll function)
10
+ * @returns Vite plugin instance
11
+ */
5
12
  declare const dtsrollPlugin: (options?: Options) => Plugin;
6
13
 
7
14
  export { dtsrollPlugin as dtsroll };
package/dist/vite.mjs CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { d as dtsroll, l as logOutput } from './index-DfxmCmBK.mjs';
2
+ import { d as dtsroll, l as logOutput } from './index-r9RZgCg7.mjs';
3
3
  import 'node:path';
4
4
  import 'node:fs/promises';
5
5
  import 'rollup';
6
6
  import 'rollup-plugin-dts';
7
7
  import '@rollup/plugin-node-resolve';
8
8
  import 'node:fs';
9
+ import 'convert-source-map';
9
10
  import 'empathic/find';
10
11
  import 'resolve-pkg-maps';
11
12
  import 'byte-size';
@@ -29,14 +30,22 @@ const dtsrollPlugin = (options) => {
29
30
  if (built) {
30
31
  return;
31
32
  }
32
- const output = await dtsroll({
33
- cwd,
34
- ...options
35
- });
36
- built = true;
37
- if (!noLog) {
38
- logOutput(output);
39
- console.log();
33
+ try {
34
+ const output = await dtsroll({
35
+ cwd,
36
+ ...options
37
+ });
38
+ built = true;
39
+ if (!noLog) {
40
+ logOutput(output);
41
+ console.log();
42
+ }
43
+ } catch (error) {
44
+ built = true;
45
+ throw new Error(
46
+ `dtsroll failed: ${error instanceof Error ? error.message : String(error)}`,
47
+ { cause: error }
48
+ );
40
49
  }
41
50
  }
42
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dtsroll",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Bundle dts files",
5
5
  "keywords": [
6
6
  "bundle",
@@ -40,6 +40,7 @@
40
40
  "@rollup/plugin-node-resolve": "^16.0.3",
41
41
  "byte-size": "^9.0.1",
42
42
  "cleye": "^2.2.1",
43
+ "convert-source-map": "^2.0.0",
43
44
  "empathic": "^2.0.0",
44
45
  "resolve-pkg-maps": "^1.0.0",
45
46
  "rollup": "^4.55.1",