@savvy-web/tsdown-plugins 1.0.1 → 1.1.1

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.
@@ -108,6 +108,7 @@ async function buildTargetGroups(options) {
108
108
  transform: options.transform,
109
109
  sourceDir: options.cwd,
110
110
  dual: options.dualExports ?? js.format.includes("cjs"),
111
+ emitDts: options.emitDts !== false,
111
112
  ...options.subdirExports !== void 0 ? { subdirExports: options.subdirExports } : {},
112
113
  ...options.exeRewrite !== void 0 ? { exeRewrite: options.exeRewrite } : {}
113
114
  }) : void 0;
@@ -141,7 +142,7 @@ async function buildTargetGroups(options) {
141
142
  ]
142
143
  }));
143
144
  const dtsNeverBundle = [...partExternals ?? [], ...partDtsExternals ?? []];
144
- if (Object.keys(dts.entry).length === 0) continue;
145
+ if (options.emitDts === false || Object.keys(dts.entry).length === 0) continue;
145
146
  const dtsDeps = dtsNeverBundle.length > 0 || partBundleNodeModules || dts.bundledPackages ? { deps: {
146
147
  ...dtsNeverBundle.length > 0 ? { neverBundle: dtsNeverBundle } : {},
147
148
  ...partBundleNodeModules ? {
package/index.d.ts CHANGED
@@ -68,9 +68,14 @@ interface ExeRewrite {
68
68
  *
69
69
  * Export keys in `subdirExports` are built into an isolated `<key>/index.*` subdir (e.g. an
70
70
  * RSPress `./runtime`), so their conditions gain an `/index` segment.
71
+ *
72
+ * `emitDts` gates the `types` condition on every GENERATED (non-ambient) TS export: pass `false`
73
+ * when the build's dts pass was skipped (issue #198) so the manifest does not point at a `.d.ts`
74
+ * that was never written. Defaults to `true`. Ambient `.d.ts` exports (hand-authored, copied
75
+ * verbatim regardless of the dts pass) are unaffected — their `types` condition is always kept.
71
76
  * @public
72
77
  */
73
- declare function transformExports(exports: unknown, dual?: DualExports, subdirExports?: ReadonlySet<string>): unknown;
78
+ declare function transformExports(exports: unknown, dual?: DualExports, subdirExports?: ReadonlySet<string>, emitDts?: boolean): unknown;
74
79
  /**
75
80
  * Rewrite bin: TS targets to bin/[command].js (string to bin/cli.js); strip leading ./ otherwise.
76
81
  *
@@ -93,6 +98,12 @@ interface TransformManifestOptions {
93
98
  readonly subdirExports?: ReadonlySet<string> | undefined;
94
99
  /** When set, rewrite exports/bin values equal to `source` to the SEA path and add it to `files`. */
95
100
  readonly exeRewrite?: ExeRewrite | undefined;
101
+ /**
102
+ * Whether the build's dts pass ran. `false` omits the `types` condition from generated (non-
103
+ * ambient) TS exports — the manifest must not point at a `.d.ts` that was never written
104
+ * (issue #198). Defaults to `true`.
105
+ */
106
+ readonly emitDts?: boolean | undefined;
96
107
  }
97
108
  /**
98
109
  * Apply the full standard manifest transform (excluding catalog resolution, done upstream).
@@ -124,6 +135,8 @@ interface BuildEmittedManifestOptions {
124
135
  readonly subdirExports?: ReadonlySet<string> | undefined;
125
136
  /** When set, rewrite exports/bin values equal to the exe source to the SEA path and add it to `files`. */
126
137
  readonly exeRewrite?: ExeRewrite | undefined;
138
+ /** Whether the dts pass ran; `false` omits `types` conditions from the emitted manifest (issue #198). Defaults to `true`. */
139
+ readonly emitDts?: boolean | undefined;
127
140
  }
128
141
  /**
129
142
  * Compute the final manifest bytes for a TargetGroup (catalog resolution + standard transforms).
@@ -147,6 +160,8 @@ interface EmitManifestOptions {
147
160
  readonly subdirExports?: ReadonlySet<string> | undefined;
148
161
  /** When set, rewrite exports/bin values equal to the exe source to the SEA path and add it to `files`. */
149
162
  readonly exeRewrite?: ExeRewrite | undefined;
163
+ /** Whether the dts pass ran; `false` omits `types` conditions from the emitted manifest (issue #198). Defaults to `true`. */
164
+ readonly emitDts?: boolean | undefined;
150
165
  }
151
166
  /**
152
167
  * Rolldown plugin: emit the transformed package.json + LICENSE/README into the output pkg/ root.
@@ -660,9 +675,16 @@ interface BuildTargetGroupsOptions {
660
675
  * Emit a per-module (`unbundle: true`) declaration tree into `dist/prod/<id>/declarations/` per
661
676
  * group, in addition to the bundled dts in `pkg/`. API Extractor's diagnostics-run input for the
662
677
  * meta pass. Prod-only; the bundler sets it for `--target prod`. Absent → no third pass
663
- * (byte-identical to the two-pass default). Not captured by the collector.
678
+ * (byte-identical to the two-pass default). Not captured by the collector. Has no effect when
679
+ * `emitDts` is `false` — the declarations pass exists only to feed the dts-derived meta pass.
664
680
  */
665
681
  readonly emitDeclarations?: boolean | undefined;
682
+ /**
683
+ * When `false`, skip BOTH the bundled per-entry dts pass (Pass 2) and the prod per-module
684
+ * declarations pass (Pass 3) — no TypeScript compiler load. The JS pass (Pass 1) and
685
+ * `copyPublicDir` still run. Defaults to `true` (current two/three-pass behavior).
686
+ */
687
+ readonly emitDts?: boolean | undefined;
666
688
  }
667
689
  /**
668
690
  * Run tsdown.build() per TargetGroup. Composable so the escape hatch gets multi-group too.
@@ -4,6 +4,24 @@ import { join } from "node:path";
4
4
  import { readFile } from "node:fs/promises";
5
5
 
6
6
  //#region src/manifest/emit-manifest.ts
7
+ const DEPENDENCY_FIELDS = [
8
+ "dependencies",
9
+ "devDependencies",
10
+ "peerDependencies",
11
+ "optionalDependencies"
12
+ ];
13
+ const isCatalogOrWorkspaceSpec = (spec) => typeof spec === "string" && (spec.startsWith("catalog:") || spec.startsWith("workspace:"));
14
+ /**
15
+ * Whether any of `pkg`'s four dependency fields carries at least one `catalog:`/`workspace:`
16
+ * specifier. `resolveManifest` returns a manifest with none of these unchanged, so callers can
17
+ * skip the CatalogResolver + pnpm-workspace + lockfile assembly entirely when this is false.
18
+ */
19
+ function manifestNeedsCatalogResolution(pkg) {
20
+ return DEPENDENCY_FIELDS.some((field) => {
21
+ const deps = pkg[field];
22
+ return typeof deps === "object" && deps !== null && Object.values(deps).some(isCatalogOrWorkspaceSpec);
23
+ });
24
+ }
7
25
  /**
8
26
  * Compute the final manifest bytes for a TargetGroup (catalog resolution + standard transforms).
9
27
  *
@@ -11,7 +29,7 @@ import { readFile } from "node:fs/promises";
11
29
  */
12
30
  async function buildEmittedManifest(options) {
13
31
  const { pkg, targetGroup, devManifest, transform } = options;
14
- const shouldResolve = targetGroup.isProd || devManifest === "resolve";
32
+ const shouldResolve = (targetGroup.isProd || devManifest === "resolve") && manifestNeedsCatalogResolution(pkg);
15
33
  let base = pkg;
16
34
  if (shouldResolve) base = await resolveManifest(pkg);
17
35
  base = {
@@ -25,7 +43,8 @@ async function buildEmittedManifest(options) {
25
43
  }) : void 0,
26
44
  dual: options.dual ?? false,
27
45
  subdirExports: options.subdirExports,
28
- exeRewrite: options.exeRewrite
46
+ exeRewrite: options.exeRewrite,
47
+ emitDts: options.emitDts ?? true
29
48
  });
30
49
  }
31
50
  /**
@@ -45,7 +64,8 @@ function emitManifest(options) {
45
64
  transform: options.transform,
46
65
  dual: options.dual,
47
66
  subdirExports: options.subdirExports,
48
- exeRewrite: options.exeRewrite
67
+ exeRewrite: options.exeRewrite,
68
+ emitDts: options.emitDts
49
69
  });
50
70
  this.emitFile({
51
71
  type: "asset",
@@ -65,4 +85,4 @@ function emitManifest(options) {
65
85
  }
66
86
 
67
87
  //#endregion
68
- export { buildEmittedManifest, emitManifest };
88
+ export { buildEmittedManifest, emitManifest, manifestNeedsCatalogResolution };
@@ -77,9 +77,14 @@ const toBuiltDts = (exportKey, subdirExports) => toBuiltJs(exportKey, subdirExpo
77
77
  const toBuiltCjs = (exportKey, subdirExports) => toBuiltJs(exportKey, subdirExports).replace(/\.js$/, ".cjs");
78
78
  const isDeclarationFile = (p) => p.endsWith(".d.ts") || p.endsWith(".d.cts") || p.endsWith(".d.mts");
79
79
  const isTs = (p) => !isDeclarationFile(p) && (p.endsWith(".ts") || p.endsWith(".tsx"));
80
- /** Build the conditions object for a TS export target (adds require when dual-format). */
81
- const tsConditions = (exportKey, dual, subdirExports) => ({
82
- types: toBuiltDts(exportKey, subdirExports),
80
+ /**
81
+ * Build the conditions object for a TS export target (adds require when dual-format). `emitDts`
82
+ * gates the `types` condition: when `false` (the build's dts pass was skipped, see issue #198),
83
+ * omitting `types` avoids pointing the published manifest at a declaration file that was never
84
+ * written. Defaults to `true` (current behavior, byte-identical).
85
+ */
86
+ const tsConditions = (exportKey, dual, subdirExports, emitDts = true) => ({
87
+ ...emitDts ? { types: toBuiltDts(exportKey, subdirExports) } : {},
83
88
  import: toBuiltJs(exportKey, subdirExports),
84
89
  ...dual ? { require: toBuiltCjs(exportKey, subdirExports) } : {}
85
90
  });
@@ -103,10 +108,15 @@ function stripPublicPrefix(value) {
103
108
  *
104
109
  * Export keys in `subdirExports` are built into an isolated `<key>/index.*` subdir (e.g. an
105
110
  * RSPress `./runtime`), so their conditions gain an `/index` segment.
111
+ *
112
+ * `emitDts` gates the `types` condition on every GENERATED (non-ambient) TS export: pass `false`
113
+ * when the build's dts pass was skipped (issue #198) so the manifest does not point at a `.d.ts`
114
+ * that was never written. Defaults to `true`. Ambient `.d.ts` exports (hand-authored, copied
115
+ * verbatim regardless of the dts pass) are unaffected — their `types` condition is always kept.
106
116
  * @public
107
117
  */
108
- function transformExports(exports, dual = false, subdirExports) {
109
- if (typeof exports === "string") return isTs(exports) ? tsConditions(".", isDualKey(dual, "."), subdirExports) : stripPublicPrefix(exports);
118
+ function transformExports(exports, dual = false, subdirExports, emitDts = true) {
119
+ if (typeof exports === "string") return isTs(exports) ? tsConditions(".", isDualKey(dual, "."), subdirExports, emitDts) : stripPublicPrefix(exports);
110
120
  if (exports && typeof exports === "object") {
111
121
  const out = {};
112
122
  for (const [key, value] of Object.entries(exports)) {
@@ -120,8 +130,8 @@ function transformExports(exports, dual = false, subdirExports) {
120
130
  out[key] = { types: `./${ambientOutName(key, cls.source)}` };
121
131
  continue;
122
132
  }
123
- if (typeof value === "string" && isTs(value)) out[key] = tsConditions(key, isDualKey(dual, key), subdirExports);
124
- else out[key] = transformExports(value, dual, subdirExports);
133
+ if (typeof value === "string" && isTs(value)) out[key] = tsConditions(key, isDualKey(dual, key), subdirExports, emitDts);
134
+ else out[key] = transformExports(value, dual, subdirExports, emitDts);
125
135
  }
126
136
  return out;
127
137
  }
@@ -184,7 +194,7 @@ function transformManifest(pkg, options = {}) {
184
194
  }
185
195
  if (result.exports !== void 0 && result.exports !== null) {
186
196
  const original = result.exports;
187
- const transformed = transformExports(original, options.dual ?? false, options.subdirExports);
197
+ const transformed = transformExports(original, options.dual ?? false, options.subdirExports, options.emitDts ?? true);
188
198
  const asMap = typeof original === "string" ? { ".": transformed } : { ...transformed };
189
199
  if (!("./package.json" in asMap)) asMap["./package.json"] = "./package.json";
190
200
  result.exports = asMap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/tsdown-plugins",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "private": false,
5
5
  "description": "Interface-only tsdown/rolldown plugin pack powering @savvy-web/bundler",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/tsdown-plugins",
@@ -35,7 +35,7 @@
35
35
  "@microsoft/tsdoc": "^0.16.0",
36
36
  "@microsoft/tsdoc-config": "^0.18.1",
37
37
  "deep-equal": "^2.2.3",
38
- "json-schema-effect": "^0.2.4",
38
+ "json-schema-effect": "^0.3.0",
39
39
  "picocolors": "^1.1.1",
40
40
  "sort-package-json": "^4.0.0",
41
41
  "std-env": "^4.1.0",