dtsroll 1.4.1 → 1.6.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,145 @@
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
36
-
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)
40
-
41
- 2. Pass in the entry declaration file to _dtsroll_
42
-
43
- ```sh
44
- dtsroll --dry-run dist/index.d.ts
45
- ```
35
+ ```ts
36
+ // dist/index.d.ts (after dtsroll)
37
+ type SomeType = {
38
+ value: string
39
+ }
46
40
 
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`
41
+ export declare function myUtility(): SomeType
42
+ ```
52
43
 
53
- 3. If the changes look good, remove the `--dry-run` flag:
44
+ ### Features
54
45
 
55
- ```sh
56
- dtsroll dist/index.d.ts
57
- ```
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.
58
50
 
59
- ### Recommended setup
51
+ <p align="center">
52
+ <img width="600" src="./.github/screenshot.webp">
53
+ </p>
60
54
 
61
- Running `dtsroll` without specifying input files will auto-detect them from `package.json`.
55
+ ## Install
62
56
 
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
- }
57
+ ```bash
58
+ npm install --save-dev dtsroll
75
59
  ```
76
60
 
77
- ### Externalization
61
+ ## Quick start
78
62
 
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.
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
+ ```
80
79
 
81
- When there is no `package.json` file, you can specify packages to externalize with the `--external` flag.
80
+ ### 2. Build
82
81
 
83
- #### Handling `@types` packages
82
+ ```bash
83
+ npm run build
84
+ ```
84
85
 
85
- Some packages need separate `@types/*` packages for type definitions. In this setup, typically:
86
+ That's it.
86
87
 
87
- - The main package is in `dependencies`.
88
- - The corresponding `@types/*` package is in `devDependencies`.
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
+ > ```
89
95
 
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`.
96
+ ## Behavior
91
97
 
92
- To fix this, _dtsroll_ will display a warning suggesting you move the `@types/*` package out of `devDependencies`.
98
+ ### Automatic configuration
93
99
 
94
- ## CLI
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.
95
101
 
96
- ### Usage
97
- ```sh
98
- dtsroll [-flags] [...entry dts files]
99
- ```
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 |
100
108
 
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.
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.
102
110
 
103
- ### --help, -h
104
- Display usage instructions.
111
+ ### Manual configuration
105
112
 
106
- ### --dry-run, -d
107
- Simulate the bundling process without modifying the disk and logs what would happen.
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.
108
114
 
109
- ### --external, -e
110
- If there is no `package.json` file, you can specify package names to exclude from the bundle.
115
+ ### Subpath imports
111
116
 
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.
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.
114
119
 
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).
120
+ ## Usage
117
121
 
118
- ## Node.js API
119
- ```ts
120
- import { dtsroll } from 'dtsroll'
122
+ _dtsroll_ can be used in several ways.
121
123
 
122
- await dtsroll(options as {
124
+ ### CLI usage
123
125
 
124
- /**
125
- * CWD to find the package.json in
126
- * @default process.cwd()
127
- */
128
- cwd?: string
129
-
130
- /**
131
- * Defaults to auto-detecting d.ts files from package.json
132
- */
133
- inputs?: string[]
126
+ ```bash
127
+ dtsroll [flags] [...entry .d.ts files]
128
+ ```
134
129
 
135
- /**
136
- * Only used if there's no package.json
137
- * Defaults to auto-detecting dependencies from package.json
138
- */
139
- external?: string[]
130
+ If no entry files are provided, _dtsroll_ reads `package.json` to determine them.
140
131
 
141
- conditions?: string[]
132
+ #### Flags
142
133
 
143
- dryRun?: boolean
144
- })
145
- ```
134
+ | Flag | Alias | Description |
135
+ | -------------- | ----- | ------------------------------------------------- |
136
+ | `--dry-run` | `-d` | Show what would be bundled without writing files |
137
+ | `--conditions` | `-C` | Resolution conditions for [subpath exports](https://nodejs.org/api/packages.html#subpath-exports) (e.g. `production`) |
138
+ | `--external` | `-e` | *(Only when no `package.json`)* Packages to externalize |
146
139
 
147
- ## Vite plugin
140
+ ### Vite plugin
148
141
 
149
- Use it in conjunction with a plugin that generates the initial declaration files like `vite-plugin-dts`.
142
+ If you use `vite-plugin-dts`, _dtsroll_ will automatically bundle the emitted types immediately after generation:
150
143
 
151
144
  ```ts
152
145
  import { defineConfig } from 'vitest/config'
@@ -154,16 +147,24 @@ import dts from 'vite-plugin-dts'
154
147
  import { dtsroll } from 'dtsroll/vite'
155
148
 
156
149
  export default defineConfig({
157
- // ...
158
150
  plugins: [
159
- // ...
160
151
  dts(),
161
152
  dtsroll()
162
153
  ]
163
154
  })
164
155
  ```
165
156
 
157
+ ### Node API
158
+
159
+ ```ts
160
+ import { dtsroll } from 'dtsroll'
161
+
162
+ await dtsroll({
163
+ cwd: process.cwd(),
164
+ dryRun: false
165
+ })
166
+ ```
167
+
166
168
  ## Related
167
169
 
168
- ### pkgroll
169
- For package bundling (along with dts bundling), check out [pkgroll](https://github.com/privatenumber/pkgroll).
170
+ - [pkgroll](https://github.com/privatenumber/pkgroll) — Zero-config JS + DTS bundler
package/dist/cli.mjs CHANGED
@@ -1,15 +1,17 @@
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-C9-38zEa.mjs';
3
+ import { b as bgYellow, a as black, d as dtsroll, l as logOutput, D as DtsrollBuildError } from './index-Du7Kzot4.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
+ import 'empathic/find';
10
+ import 'resolve-pkg-maps';
9
11
  import 'byte-size';
10
12
 
11
13
  var name = "dtsroll";
12
- var version = "1.4.1";
14
+ var version = "1.6.0";
13
15
  var description = "Bundle dts files";
14
16
 
15
17
  const argv = cli({
@@ -18,6 +20,7 @@ const argv = cli({
18
20
  help: {
19
21
  description
20
22
  },
23
+ strictFlags: true,
21
24
  parameters: ["[input files...]"],
22
25
  flags: {
23
26
  conditions: {
@@ -34,32 +37,46 @@ const argv = cli({
34
37
  type: [String],
35
38
  alias: "e",
36
39
  description: "Dependency to externalize"
40
+ },
41
+ sourcemap: {
42
+ type: Boolean,
43
+ alias: "s",
44
+ description: "Generate sourcemaps"
37
45
  }
38
- // sourcemap: {
39
- // type: Boolean,
40
- // description: 'Generate sourcemaps',
41
- // },
42
46
  }
43
47
  });
44
48
  const { flags } = argv;
45
- const dryMode = flags.dryRun;
46
- if (dryMode) {
49
+ if (flags.dryRun) {
47
50
  console.log(bgYellow(black(" Dry run - No files will be written ")));
48
51
  }
49
52
  dtsroll({
50
53
  inputs: argv._.inputFiles,
51
54
  external: flags.external,
52
55
  conditions: flags.conditions,
53
- dryRun: flags.dryRun
56
+ dryRun: flags.dryRun,
57
+ sourcemap: flags.sourcemap
54
58
  }).then(
55
59
  (output) => {
56
60
  if ("error" in output) {
57
61
  process.exitCode = 1;
58
62
  }
59
63
  logOutput(output);
60
- },
64
+ }
65
+ ).catch(
61
66
  (error) => {
62
- console.error("\nFailed to build:", error.message);
67
+ let errorMessage = "\nFailed to build";
68
+ if (error instanceof DtsrollBuildError) {
69
+ errorMessage += `
70
+ File: ${error.id}`;
71
+ if (error.importChain.length > 1) {
72
+ errorMessage += "\n\n Import chain:\n ";
73
+ errorMessage += error.importChain.join("\n \u2192 ");
74
+ }
75
+ }
76
+ errorMessage += `
77
+
78
+ ${error instanceof Error ? error.message : String(error)}`;
79
+ console.error(errorMessage);
63
80
  process.exitCode = 1;
64
81
  }
65
82
  );
@@ -3,86 +3,63 @@ import fs from 'node:fs/promises';
3
3
  import { rollup } from 'rollup';
4
4
  import { dts } from 'rollup-plugin-dts';
5
5
  import nodeResolve from '@rollup/plugin-node-resolve';
6
+ import { up } from 'empathic/find';
7
+ import { resolveImports } from 'resolve-pkg-maps';
6
8
  import byteSize from 'byte-size';
7
9
 
8
10
  let enabled = true;
9
- // Support both browser and node environments
10
- const globalVar = typeof self !== 'undefined'
11
- ? self
12
- : typeof window !== 'undefined'
13
- ? window
14
- : typeof global !== 'undefined'
15
- ? global
16
- : {};
17
- /**
18
- * Detect how much colors the current terminal supports
19
- */
20
- let supportLevel = 0 /* none */;
11
+ const globalVar = typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {};
12
+ let supportLevel = 0;
21
13
  if (globalVar.process && globalVar.process.env && globalVar.process.stdout) {
22
- const { FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM, COLORTERM } = globalVar.process.env;
23
- if (NODE_DISABLE_COLORS || NO_COLOR || FORCE_COLOR === '0') {
24
- enabled = false;
25
- }
26
- else if (FORCE_COLOR === '1' ||
27
- FORCE_COLOR === '2' ||
28
- FORCE_COLOR === '3') {
29
- enabled = true;
30
- }
31
- else if (TERM === 'dumb') {
32
- enabled = false;
33
- }
34
- else if ('CI' in globalVar.process.env &&
35
- [
36
- 'TRAVIS',
37
- 'CIRCLECI',
38
- 'APPVEYOR',
39
- 'GITLAB_CI',
40
- 'GITHUB_ACTIONS',
41
- 'BUILDKITE',
42
- 'DRONE',
43
- ].some(vendor => vendor in globalVar.process.env)) {
44
- enabled = true;
45
- }
46
- else {
47
- enabled = process.stdout.isTTY;
48
- }
49
- if (enabled) {
50
- // Windows supports 24bit True Colors since Windows 10 revision #14931,
51
- // see https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/
52
- if (process.platform === 'win32') {
53
- supportLevel = 3 /* trueColor */;
54
- }
55
- else {
56
- if (COLORTERM && (COLORTERM === 'truecolor' || COLORTERM === '24bit')) {
57
- supportLevel = 3 /* trueColor */;
58
- }
59
- else if (TERM && (TERM.endsWith('-256color') || TERM.endsWith('256'))) {
60
- supportLevel = 2 /* ansi256 */;
61
- }
62
- else {
63
- supportLevel = 1 /* ansi */;
64
- }
65
- }
14
+ const { FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM, COLORTERM } = globalVar.process.env;
15
+ if (NODE_DISABLE_COLORS || NO_COLOR || FORCE_COLOR === "0") {
16
+ enabled = false;
17
+ } else if (FORCE_COLOR === "1" || FORCE_COLOR === "2" || FORCE_COLOR === "3") {
18
+ enabled = true;
19
+ } else if (TERM === "dumb") {
20
+ enabled = false;
21
+ } else if ("CI" in globalVar.process.env && [
22
+ "TRAVIS",
23
+ "CIRCLECI",
24
+ "APPVEYOR",
25
+ "GITLAB_CI",
26
+ "GITHUB_ACTIONS",
27
+ "BUILDKITE",
28
+ "DRONE"
29
+ ].some((vendor) => vendor in globalVar.process.env)) {
30
+ enabled = true;
31
+ } else {
32
+ enabled = process.stdout.isTTY;
33
+ }
34
+ if (enabled) {
35
+ if (process.platform === "win32") {
36
+ supportLevel = 3;
37
+ } else {
38
+ if (COLORTERM && (COLORTERM === "truecolor" || COLORTERM === "24bit")) {
39
+ supportLevel = 3;
40
+ } else if (TERM && (TERM.endsWith("-256color") || TERM.endsWith("256"))) {
41
+ supportLevel = 2;
42
+ } else {
43
+ supportLevel = 1;
44
+ }
66
45
  }
46
+ }
67
47
  }
68
48
  let options = {
69
- enabled,
70
- supportLevel,
49
+ enabled,
50
+ supportLevel
71
51
  };
72
- function kolorist(start, end, level = 1 /* ansi */) {
73
- const open = `\x1b[${start}m`;
74
- const close = `\x1b[${end}m`;
75
- const regex = new RegExp(`\\x1b\\[${end}m`, 'g');
76
- return (str) => {
77
- return options.enabled && options.supportLevel >= level
78
- ? open + ('' + str).replace(regex, open) + close
79
- : '' + str;
80
- };
52
+ function kolorist(start, end, level = 1) {
53
+ const open = `\x1B[${start}m`;
54
+ const close = `\x1B[${end}m`;
55
+ const regex = new RegExp(`\\x1b\\[${end}m`, "g");
56
+ return (str) => {
57
+ return options.enabled && options.supportLevel >= level ? open + ("" + str).replace(regex, open) + close : "" + str;
58
+ };
81
59
  }
82
60
  const bold = kolorist(1, 22);
83
61
  const dim = kolorist(2, 22);
84
62
  const underline = kolorist(4, 24);
85
- // colors
86
63
  const black = kolorist(30, 39);
87
64
  const red = kolorist(31, 39);
88
65
  const green = kolorist(32, 39);
@@ -94,7 +71,13 @@ const bgYellow = kolorist(43, 49);
94
71
  const cwd = process.cwd();
95
72
 
96
73
  const isPath = (filePath) => filePath[0] === "." || path.isAbsolute(filePath);
97
- const normalizePath = (filepath) => filepath.replaceAll("\\", "/");
74
+ const normalizePath$1 = (filepath) => filepath.replaceAll("\\", "/");
75
+ const getDisplayPath = (fullPath) => {
76
+ const relativePath = path.relative(cwd, fullPath);
77
+ return normalizePath$1(
78
+ relativePath.length < fullPath.length ? relativePath : fullPath
79
+ );
80
+ };
98
81
 
99
82
  const warningSignUnicode = "\u26A0";
100
83
  const warningPrefix = yellow("Warning:");
@@ -106,10 +89,7 @@ const logOutput = (dtsOutput) => {
106
89
  \u{1F4E5} Entry points${isCliInput ? "" : " in package.json"}`));
107
90
  console.log(
108
91
  inputs.map(([inputFile, inputSource, error]) => {
109
- const relativeInputFile = path.relative(cwd, inputFile);
110
- const logPath2 = normalizePath(
111
- relativeInputFile.length < inputFile.length ? relativeInputFile : inputFile
112
- );
92
+ const logPath2 = getDisplayPath(inputFile);
113
93
  if (error) {
114
94
  return ` ${lightYellow(`${warningSignUnicode} ${logPath2} ${dim(error)}`)}`;
115
95
  }
@@ -129,10 +109,7 @@ const logOutput = (dtsOutput) => {
129
109
  size,
130
110
  externals
131
111
  } = dtsOutput;
132
- const outputDirectoryRelative = path.relative(cwd, outputDirectory);
133
- const logPath = `${normalizePath(
134
- outputDirectoryRelative.length < outputDirectory.length ? outputDirectoryRelative : outputDirectory
135
- )}/`;
112
+ const logPath = `${getDisplayPath(outputDirectory)}/`;
136
113
  const logChunk = ({
137
114
  file,
138
115
  indent,
@@ -146,10 +123,7 @@ const logOutput = (dtsOutput) => {
146
123
  ${moduleIds.sort().map((moduleId, index) => {
147
124
  const isLast = index === moduleIds.length - 1;
148
125
  const prefix = `${indent} ${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}`;
149
- const relativeModuleId = path.relative(cwd, moduleId);
150
- const logModuleId = normalizePath(
151
- relativeModuleId.length < moduleId.length ? relativeModuleId : moduleId
152
- );
126
+ const logModuleId = getDisplayPath(moduleId);
153
127
  const bareSpecifier = moduleToPackage[moduleId];
154
128
  if (bareSpecifier) {
155
129
  return `${prefix}${dim(`${magenta(bareSpecifier)} (${logModuleId})`)}`;
@@ -199,6 +173,17 @@ ${moduleIds.sort().map((moduleId, index) => {
199
173
  }
200
174
  };
201
175
 
176
+ class DtsrollBuildError extends Error {
177
+ id;
178
+ importChain;
179
+ constructor(message, id, importChain) {
180
+ super(message);
181
+ this.name = "DtsrollBuildError";
182
+ this.id = id;
183
+ this.importChain = importChain;
184
+ }
185
+ }
186
+
202
187
  const dtsExtensions = [".d.ts", ".d.cts", ".d.mts"];
203
188
  const isDts = (fileName) => dtsExtensions.some((extension) => fileName.endsWith(extension));
204
189
 
@@ -291,6 +276,7 @@ const getPackageName = (id) => {
291
276
  return id.slice(0, indexOfSlash);
292
277
  };
293
278
 
279
+ const normalizePath = (filePath) => filePath.replaceAll("\\", "/");
294
280
  const getAllFiles = async (directoryPath, dontShortenPath) => {
295
281
  const directoryFiles = await fs.readdir(directoryPath, { withFileTypes: true });
296
282
  const fileTree = await Promise.all(
@@ -298,9 +284,9 @@ const getAllFiles = async (directoryPath, dontShortenPath) => {
298
284
  const filePath = path.join(directoryPath, entry.name);
299
285
  if (entry.isDirectory()) {
300
286
  const files = await getAllFiles(filePath, true);
301
- return dontShortenPath ? files : files.map((file) => `./${path.relative(directoryPath, file)}`);
287
+ return dontShortenPath ? files : files.map((file) => `./${normalizePath(path.relative(directoryPath, file))}`);
302
288
  }
303
- return dontShortenPath ? filePath : `./${path.relative(directoryPath, filePath)}`;
289
+ return dontShortenPath ? filePath : `./${normalizePath(path.relative(directoryPath, filePath))}`;
304
290
  })
305
291
  );
306
292
  return fileTree.flat();
@@ -495,6 +481,39 @@ const createExternalizePlugin = (configuredExternals) => {
495
481
  };
496
482
  };
497
483
 
484
+ const createImportChainPlugin = () => {
485
+ const importerMap = /* @__PURE__ */ new Map();
486
+ const plugin = {
487
+ name: "import-chain-tracker",
488
+ buildStart: () => {
489
+ importerMap.clear();
490
+ },
491
+ async resolveId(source, importer) {
492
+ if (!importer) {
493
+ return null;
494
+ }
495
+ const resolved = await this.resolve(source, importer, { skipSelf: true });
496
+ if (resolved && !resolved.external && !importerMap.has(resolved.id)) {
497
+ importerMap.set(resolved.id, importer);
498
+ }
499
+ return null;
500
+ }
501
+ };
502
+ const getImportChain = (errorFileId) => {
503
+ const chain = [];
504
+ let current = errorFileId;
505
+ while (current) {
506
+ chain.unshift(current);
507
+ current = importerMap.get(current);
508
+ }
509
+ return chain;
510
+ };
511
+ return {
512
+ plugin,
513
+ getImportChain
514
+ };
515
+ };
516
+
498
517
  const nodeModules = `${path.sep}node_modules${path.sep}`;
499
518
  const removeBundledModulesPlugin = (outputDirectory, sizeRef) => {
500
519
  let deleteFiles = [];
@@ -512,14 +531,17 @@ const removeBundledModulesPlugin = (outputDirectory, sizeRef) => {
512
531
  async generateBundle(options, bundle) {
513
532
  const modules = Object.values(bundle);
514
533
  const bundledFiles = Array.from(new Set(modules.flatMap(({ moduleIds }) => moduleIds)));
515
- const fileSizes = bundledFiles.map((moduleId) => this.getModuleInfo(moduleId).meta);
516
- const totalSize = fileSizes.reduce((total, { size }) => total + size, 0);
534
+ let totalSize = 0;
535
+ for (const moduleId of bundledFiles) {
536
+ const moduleInfo = this.getModuleInfo(moduleId);
537
+ const size = moduleInfo?.meta?.size;
538
+ if (typeof size === "number") {
539
+ totalSize += size;
540
+ }
541
+ }
517
542
  sizeRef.value = totalSize;
518
543
  const outputFiles = new Set(modules.map(({ fileName }) => path.join(options.dir, fileName)));
519
- deleteFiles = bundledFiles.filter((moduleId) => (
520
- // To avoid deleting files from symlinked dependencies
521
- moduleId.startsWith(outputDirectory) && !moduleId.includes(nodeModules) && !outputFiles.has(moduleId)
522
- ));
544
+ deleteFiles = bundledFiles.filter((moduleId) => moduleId && moduleId.startsWith(outputDirectory) && !moduleId.includes(nodeModules) && !outputFiles.has(moduleId));
523
545
  },
524
546
  writeBundle: async () => {
525
547
  await Promise.all(
@@ -529,30 +551,85 @@ const removeBundledModulesPlugin = (outputDirectory, sizeRef) => {
529
551
  };
530
552
  };
531
553
 
554
+ const packageJsonCache = /* @__PURE__ */ new Map();
555
+ const findPackageJsonUp = async (cwd) => {
556
+ const packageJsonPath = up("package.json", { cwd });
557
+ if (!packageJsonPath) {
558
+ return void 0;
559
+ }
560
+ const packageRoot = path.dirname(packageJsonPath);
561
+ let packageJson = packageJsonCache.get(packageRoot);
562
+ if (!packageJson) {
563
+ try {
564
+ const content = await fs.readFile(packageJsonPath, "utf8");
565
+ packageJson = JSON.parse(content);
566
+ packageJsonCache.set(packageRoot, packageJson);
567
+ } catch {
568
+ return void 0;
569
+ }
570
+ }
571
+ if (packageJson.imports) {
572
+ return {
573
+ imports: packageJson.imports,
574
+ packageRoot
575
+ };
576
+ }
577
+ };
578
+ const resolveSubpathImportsPlugin = () => ({
579
+ name: "resolve-subpath-imports",
580
+ async resolveId(id, importer) {
581
+ if (id[0] !== "#" || !importer) {
582
+ return null;
583
+ }
584
+ const result = await findPackageJsonUp(path.dirname(importer));
585
+ if (!result) {
586
+ return null;
587
+ }
588
+ const { imports, packageRoot } = result;
589
+ let resolvedPaths;
590
+ try {
591
+ resolvedPaths = resolveImports(imports, id, ["types", "import"]);
592
+ } catch {
593
+ return null;
594
+ }
595
+ if (resolvedPaths.length === 0) {
596
+ return null;
597
+ }
598
+ return this.resolve(
599
+ path.join(packageRoot, resolvedPaths[0]),
600
+ importer,
601
+ { skipSelf: true }
602
+ );
603
+ }
604
+ });
605
+
532
606
  const createInputMap = (input, outputDirectory) => Object.fromEntries(
533
607
  input.map((inputFile) => [
534
608
  inputFile.slice(outputDirectory.length + 1),
535
609
  inputFile
536
610
  ])
537
611
  );
538
- const build = async (input, outputDirectory, externals, mode, conditions) => {
612
+ const build = async (input, outputDirectory, externals, mode, conditions, sourcemap) => {
539
613
  const {
540
614
  externalizePlugin,
541
615
  externalized,
542
616
  getPackageEntryPoint
543
617
  } = createExternalizePlugin(externals);
618
+ const { plugin: importChainPlugin, getImportChain } = createImportChainPlugin();
544
619
  const sizeRef = {};
545
620
  const rollupConfig = {
546
621
  input: createInputMap(input, outputDirectory),
547
622
  output: {
548
- // sourcemap: true,
623
+ sourcemap,
549
624
  dir: outputDirectory,
550
625
  entryFileNames: "[name]",
551
626
  chunkFileNames: "_dtsroll-chunks/[hash]-[name].ts"
552
627
  },
553
628
  plugins: [
629
+ importChainPlugin,
554
630
  externalizePlugin,
555
631
  removeBundledModulesPlugin(outputDirectory, sizeRef),
632
+ resolveSubpathImportsPlugin(),
556
633
  nodeResolve({
557
634
  extensions: [".ts", ...dtsExtensions],
558
635
  exportConditions: conditions
@@ -569,15 +646,26 @@ const build = async (input, outputDirectory, externals, mode, conditions) => {
569
646
  })
570
647
  ]
571
648
  };
572
- const rollupBuild = await rollup(rollupConfig);
573
- const built = await rollupBuild[mode](rollupConfig.output);
574
- await rollupBuild.close();
575
- return {
576
- built,
577
- externalized,
578
- getPackageEntryPoint,
579
- sourceSize: sizeRef.value
580
- };
649
+ try {
650
+ const rollupBuild = await rollup(rollupConfig);
651
+ const built = await rollupBuild[mode](rollupConfig.output);
652
+ await rollupBuild.close();
653
+ return {
654
+ built,
655
+ externalized,
656
+ getPackageEntryPoint,
657
+ sourceSize: sizeRef.value ?? 0
658
+ };
659
+ } catch (error) {
660
+ if (error instanceof Error && "id" in error && typeof error.id === "string") {
661
+ throw new DtsrollBuildError(
662
+ error.message,
663
+ error.id,
664
+ getImportChain(error.id)
665
+ );
666
+ }
667
+ throw error;
668
+ }
581
669
  };
582
670
 
583
671
  const dtsroll = async ({
@@ -585,7 +673,8 @@ const dtsroll = async ({
585
673
  inputs,
586
674
  external,
587
675
  conditions,
588
- dryRun
676
+ dryRun,
677
+ sourcemap
589
678
  } = {}) => {
590
679
  const pkgJson = await getPackageJson(cwd);
591
680
  const externals = pkgJson ? pkgJson.getExternals() : /* @__PURE__ */ new Map();
@@ -620,13 +709,15 @@ const dtsroll = async ({
620
709
  outputDirectory,
621
710
  externals,
622
711
  dryRun ? "generate" : "write",
623
- conditions
712
+ conditions,
713
+ sourcemap
624
714
  );
625
715
  let outputSize = 0;
626
716
  const outputEntries = [];
627
717
  const outputChunks = [];
628
718
  const moduleImports = /* @__PURE__ */ new Set();
629
- for (const file of built.output) {
719
+ const chunks = built.output.filter((file) => file.type === "chunk");
720
+ for (const file of chunks) {
630
721
  const size = Buffer.byteLength(file.code);
631
722
  outputSize += size;
632
723
  const moduleToPackage = Object.fromEntries(
@@ -671,4 +762,4 @@ const dtsroll = async ({
671
762
  };
672
763
  };
673
764
 
674
- export { black as a, bgYellow as b, dtsroll as d, logOutput as l };
765
+ 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,13 +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
- export { type DtsrollOutput, type Options, dtsroll };
76
+ export { dtsroll };
77
+ export type { DtsrollOutput, Options };
package/dist/index.mjs CHANGED
@@ -1,7 +1,10 @@
1
+ #!/usr/bin/env node
1
2
  import 'node:path';
2
- export { d as dtsroll } from './index-C9-38zEa.mjs';
3
+ export { d as dtsroll } from './index-Du7Kzot4.mjs';
3
4
  import 'node:fs/promises';
4
5
  import 'rollup';
5
6
  import 'rollup-plugin-dts';
6
7
  import '@rollup/plugin-node-resolve';
8
+ import 'empathic/find';
9
+ import 'resolve-pkg-maps';
7
10
  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,9 +1,12 @@
1
- import { d as dtsroll, l as logOutput } from './index-C9-38zEa.mjs';
1
+ #!/usr/bin/env node
2
+ import { d as dtsroll, l as logOutput } from './index-Du7Kzot4.mjs';
2
3
  import 'node:path';
3
4
  import 'node:fs/promises';
4
5
  import 'rollup';
5
6
  import 'rollup-plugin-dts';
6
7
  import '@rollup/plugin-node-resolve';
8
+ import 'empathic/find';
9
+ import 'resolve-pkg-maps';
7
10
  import 'byte-size';
8
11
 
9
12
  const dtsrollPlugin = (options) => {
@@ -25,14 +28,22 @@ const dtsrollPlugin = (options) => {
25
28
  if (built) {
26
29
  return;
27
30
  }
28
- const output = await dtsroll({
29
- cwd,
30
- ...options
31
- });
32
- built = true;
33
- if (!noLog) {
34
- logOutput(output);
35
- console.log();
31
+ try {
32
+ const output = await dtsroll({
33
+ cwd,
34
+ ...options
35
+ });
36
+ built = true;
37
+ if (!noLog) {
38
+ logOutput(output);
39
+ console.log();
40
+ }
41
+ } catch (error) {
42
+ built = true;
43
+ throw new Error(
44
+ `dtsroll failed: ${error instanceof Error ? error.message : String(error)}`,
45
+ { cause: error }
46
+ );
36
47
  }
37
48
  }
38
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dtsroll",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "description": "Bundle dts files",
5
5
  "keywords": [
6
6
  "bundle",
@@ -37,15 +37,17 @@
37
37
  "node": ">=18"
38
38
  },
39
39
  "dependencies": {
40
- "@rollup/plugin-node-resolve": "^16.0.0",
40
+ "@rollup/plugin-node-resolve": "^16.0.3",
41
41
  "byte-size": "^9.0.1",
42
- "cleye": "^1.3.2",
43
- "rollup": "^4.29.1",
44
- "rollup-plugin-dts": "6.1.1"
42
+ "cleye": "^2.2.1",
43
+ "empathic": "^2.0.0",
44
+ "resolve-pkg-maps": "^1.0.0",
45
+ "rollup": "^4.55.1",
46
+ "rollup-plugin-dts": "6.3.0"
45
47
  },
46
48
  "peerDependencies": {
47
49
  "typescript": "^4.5 || ^5.0",
48
- "vite": "5 || 6"
50
+ "vite": "5 || 6 || 7"
49
51
  },
50
52
  "peerDependenciesMeta": {
51
53
  "vite": {