@savvy-web/bundler 0.6.1 → 0.8.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 (5) hide show
  1. package/README.md +20 -13
  2. package/config.js +4 -1
  3. package/index.d.ts +9 -5
  4. package/package.json +24 -3
  5. package/run.js +109 -121
package/README.md CHANGED
@@ -49,7 +49,7 @@ npm run build:prod
49
49
  # writes dist/prod/npm/pkg — the tarball root, with a resolved manifest and built code
50
50
  ```
51
51
 
52
- `--target dev` writes `dist/dev/pkg`, the local-link target with `catalog:`/`workspace:` specifiers preserved. `--target prod` writes `dist/prod/npm/pkg` with those specifiers resolved to concrete ranges, ready to publish. Two further targets, `--target meta` and `--target exe`, are covered below.
52
+ `--target dev` writes `dist/dev/pkg`, the local-link target with `catalog:`/`workspace:` specifiers preserved. `--target prod` writes `dist/prod/npm/pkg` with those specifiers resolved to concrete ranges, ready to publish and emits the API Extractor api-model alongside it. A third target, `--target exe`, compiles SEA binaries and is covered below.
53
53
 
54
54
  Every build emits per-module JavaScript alongside a single rolled-up, self-contained `.d.ts` per public entry. Each entry's declaration file pulls in every re-exported type, so a consumer that infers a type from your public API never has to reach into a deep sibling module that no export subpath addresses.
55
55
 
@@ -87,22 +87,19 @@ With no `targets` map the build falls back to the single-`npm` group above.
87
87
 
88
88
  ## API Extractor meta
89
89
 
90
- The bundler generates an [API Extractor](https://api-extractor.com/) api-model from a package's type declarations. Two behaviors come online:
90
+ `savvy build --target prod` generates an [API Extractor](https://api-extractor.com/) api-model from each prod group's resolved `.d.ts`. For every group it writes the bundle (`<unscoped>.api.json`, `tsdoc-metadata.json` and a resolved `tsconfig.json`) into `dist/prod/<group>/meta` as a release asset alongside `pkg/`, and copies the canonical group's bundle into any `localPaths` directories. Because it reads the prod build, the meta `package.json` carries concrete dependency versions rather than `catalog:`/`workspace:` specifiers.
91
91
 
92
- - `savvy build --target meta` runs API Extractor over the dev build's `.d.ts` — no tsdown build, so it depends only on a prior `--target dev`. It writes the api-model (`<unscoped>.api.json`, `tsdoc-metadata.json` and a resolved `tsconfig.json`) into each `localPaths` directory.
93
- - `savvy build --target prod` additionally emits the same bundle into `dist/prod/npm/meta` as a release asset alongside `pkg/`.
92
+ The `meta` field on `defineBuild` is tri-state:
94
93
 
95
- The `meta` field on `defineBuild` is tri-state and controls these:
96
-
97
- - **Omitted** (or `undefined`) generation runs with default options. `--target meta` works with no configuration and `--target prod` emits the meta asset. This is the default; you do not need a `meta` field to use `--target meta`.
98
- - **An object** — override the defaults: `localPaths` (directories the api-model is copied into on `--target meta`) and `tsdoc` (warning suppression and custom tags).
99
- - **`false`** — opt out entirely; both `--target meta` and the prod meta asset become no-ops.
94
+ - **Omitted** (or `undefined`) — generation runs with default options. This is the default; you do not need a `meta` field for `--target prod` to emit the api-model.
95
+ - **An object** — override the defaults: `localPaths` (directories the canonical api-model is copied into), `tsdoc` (warning suppression and custom tags) and `optimistic` (see below).
96
+ - **`false`**opt out entirely; the prod meta asset becomes a no-op.
100
97
 
101
98
  ```ts
102
99
  const config = defineBuild({
103
100
  format: ["esm"],
104
101
  meta: {
105
- // directories the generated api-model is copied into on `--target meta`
102
+ // directories the generated api-model is copied into
106
103
  localPaths: ["../mcp/models/@savvy-web/bundler"],
107
104
  tsdoc: {
108
105
  suppressWarnings: [{ messageId: "ae-undocumented" }],
@@ -115,6 +112,16 @@ const config = defineBuild({
115
112
  // const config = defineBuild({ meta: false });
116
113
  ```
117
114
 
115
+ `optimistic` (`"auto"`, the default, or a boolean) forward-looks the meta bundle's own `version` and its workspace-sibling dependency versions to their next release version from pending changesets. `"auto"` is off under CI (`CI` or `GITHUB_ACTIONS` set) and on locally, so a locally generated bundle matches what the CI release build would emit. Set it to `true` or `false` to pin the behavior:
116
+
117
+ ```ts
118
+ const config = defineBuild({
119
+ meta: { optimistic: false }, // always use the current package.json versions
120
+ });
121
+ ```
122
+
123
+ `--target meta` is deprecated: it warns and no-ops. Generate the api-model with `--target prod` instead.
124
+
118
125
  ## Executable binaries
119
126
 
120
127
  Set the optional `exe` field to compile a single-executable application (SEA) from a bin entry, via [`@tsdown/exe`](https://www.npmjs.com/package/@tsdown/exe):
@@ -268,7 +275,7 @@ const config = defineBuild({
268
275
  ## Features
269
276
 
270
277
  - **One self-executing config** — `savvy.build.ts` exports a `defineBuild` object for tooling to introspect and runs the build when invoked directly. No factory-notation config file.
271
- - **Four build targets** — `dev` for local linking, `npm` for a resolved publishable manifest, `meta` for an API Extractor api-model and `exe` for SEA binaries, on disjoint `dist/dev` and `dist/prod` output paths for clean caching.
278
+ - **Build targets** — `dev` for local linking, `prod` for a resolved publishable manifest (which also emits an API Extractor api-model) and `exe` for SEA binaries, on disjoint `dist/dev` and `dist/prod` output paths for clean caching.
272
279
  - **Bundled declarations** — per-module JavaScript with a single rolled-up `.d.ts` per public entry, so re-exported types stay reachable through your published export subpaths.
273
280
  - **Shared tsconfig base** — extend `@savvy-web/bundler/ecma.json` for the ESNext/NodeNext/strict settings the build expects.
274
281
  - **Manifest resolution** — `catalog:` and `workspace:` specifiers are resolved against the workspace for the published target, and preserved for the linked dev target.
@@ -290,12 +297,12 @@ const config = defineBuild({
290
297
  ## API
291
298
 
292
299
  - `defineBuild(input)` — normalizes a build config (`externals`, `bundle`, `bundleNodeModules`, `bundledPackages`, `dtsExternals`, `minify`, `devManifest`, `transform`, `output`, `meta`, `jsx`, `exe`, `format`, `overrides`, `looseFiles`, `define`), applying defaults. The `format` field controls the output module formats forwarded to tsdown (esm-only by default; add `"cjs"` for a dual-format esm+cjs build). `minify` defaults to false, `transform` defaults to a manifest stripper, and `overrides` pins a subset of entries to their own format and bundling. Pure; it does not run the build.
293
- - `runBuild(config, options)` — the orchestrator. Parses `--target`/`--watch` from `options.argv`, reads `package.json` at `options.cwd`, derives entries, drives the build for the selected target and renders a report. Every IO dependency on `options` is injectable for tests.
300
+ - `runBuild(config, options)` — the orchestrator. Parses `--target`/`--watch`/`--verbose` from `options.argv`, reads `package.json` at `options.cwd`, derives entries, drives the build for the selected target and renders a report. `--verbose` expands the report to a per-file table; the report is quiet by default. Every IO dependency on `options` is injectable for tests.
294
301
  - `parseArgs(argv)` — the argument parser behind `runBuild`, exported for embedding.
295
302
 
296
303
  ## Turbo tasks
297
304
 
298
- `pnpm turbo run build:meta` regenerates api-models into the `localPaths` configured in each package's `savvy.build.ts`, reading the dev build's `dist/dev/pkg` dts; it depends on `build:dev` and is intentionally uncached because it writes outside the package's own cache scope.
305
+ `pnpm turbo run build:prod` produces the publishable output and the api-model bundle in one pass, writing the canonical group's api-model into the `localPaths` configured in each package's `savvy.build.ts`. The standalone `build:meta` task is deprecated its `--target meta` now warns and no-ops.
299
306
 
300
307
  ## License
301
308
 
package/config.js CHANGED
@@ -27,16 +27,19 @@ function parseArgs(argv) {
27
27
  let target = "dev";
28
28
  let watch = false;
29
29
  let noExe = false;
30
+ let verbose = false;
30
31
  for (let i = 0; i < argv.length; i++) if (argv[i] === "--target") {
31
32
  const v = argv[i + 1];
32
33
  if (v === "dev" || v === "prod" || v === "meta" || v === "exe") target = v;
33
34
  i++;
34
35
  } else if (argv[i] === "--watch") watch = true;
35
36
  else if (argv[i] === "--no-exe") noExe = true;
37
+ else if (argv[i] === "--verbose") verbose = true;
36
38
  return {
37
39
  target,
38
40
  watch,
39
- noExe
41
+ noExe,
42
+ verbose
40
43
  };
41
44
  }
42
45
 
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BuildFormat, BuildPlatform, BuildTargetGroupsOptions, CssOptions, ExeConfig, GenerateMetaOptions, Json, JsxConfig, LooseFiles, MetaOptions, MetaResult, PublishTargets, RenderedOutput, RunExeBuildOptions, TargetGroupRef, TargetResolution, TsconfigJsx, defaultManifestTransform } from "@savvy-web/tsdown-plugins";
1
+ import { BuildFormat, BuildPlatform, BuildTargetGroupsOptions, CssOptions, ExeConfig, GenerateMetaOptions, Json, JsxConfig, LooseFiles, MetaOptions, MetaResult, NextVersions, PublishTargets, RenderedOutput, RunExeBuildOptions, TargetGroupRef, TargetResolution, TsconfigJsx, defaultManifestTransform } from "@savvy-web/tsdown-plugins";
2
2
 
3
3
  //#region src/config.d.ts
4
4
  interface BuildEntryOverride {
@@ -86,10 +86,11 @@ interface BuildConfigInput {
86
86
  }) => Json;
87
87
  readonly output?: OutputConfig;
88
88
  /**
89
- * API-model (meta) generation. Tri-state: omit it (or `undefined`) to generate with
90
- * DEFAULT options `savvy build --target meta` always works and `--target prod` emits the
91
- * meta release asset. Pass an object to override the defaults (`localPaths`, `tsdoc`). Pass
92
- * `false` to opt OUT entirely — both `--target meta` and the prod meta asset become no-ops.
89
+ * API-model (meta) generation. Tri-state: omit it (or `undefined`) to generate with DEFAULT
90
+ * options; `--target prod` emits the meta release asset for every prod group and copies the
91
+ * canonical group's bundle into `localPaths`. Pass an object to override defaults (`localPaths`,
92
+ * `tsdoc`, `optimistic`). Pass `false` to opt OUT (the prod meta asset becomes a no-op).
93
+ * NOTE: `--target meta` is deprecated and now a no-op; meta is a function of `--target prod`.
93
94
  */
94
95
  readonly meta?: MetaOptions | false;
95
96
  readonly jsx?: JsxConfig | undefined;
@@ -175,6 +176,7 @@ interface ParsedArgs {
175
176
  readonly watch: boolean;
176
177
  /** Skip the SEA compile step of a dev/prod build (the manifest is still programmed). Used by `prepare`. */
177
178
  readonly noExe: boolean;
179
+ readonly verbose: boolean;
178
180
  }
179
181
  declare function parseArgs(argv: ReadonlyArray<string>): ParsedArgs;
180
182
  //#endregion
@@ -209,6 +211,8 @@ interface RunOptions {
209
211
  os: ReadonlyArray<string>;
210
212
  cpu: ReadonlyArray<string>;
211
213
  }) | undefined;
214
+ /** Injectable for tests: resolves next release versions for the optimistic meta rewrite. */
215
+ readonly resolveNextVersions?: ((cwd: string) => Promise<NextVersions>) | undefined;
212
216
  }
213
217
  /** Run a build from a normalized config. Pure orchestration; all IO injectable. */
214
218
  declare function runBuild(config: BuildConfig, options: RunOptions): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/bundler",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "Zero-config tsdown-based bundler for Silk Suite TypeScript packages",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/bundler",
@@ -29,11 +29,32 @@
29
29
  "./package.json": "./package.json"
30
30
  },
31
31
  "dependencies": {
32
- "@savvy-web/tsdown-plugins": "0.6.0",
32
+ "@savvy-web/tsdown-plugins": "0.8.0",
33
33
  "@tsdown/exe": "^0.22.1",
34
+ "effect": "^3.21.3",
34
35
  "tsdown": "^0.22.3"
35
36
  },
36
37
  "peerDependencies": {
37
- "effect": ">=3.21.0"
38
+ "@types/node": "^25.9.0",
39
+ "@types/react": "^19.2.0",
40
+ "@types/react-dom": "^19.2.0",
41
+ "@typescript/native-preview": "^7.0.0-dev.20260513.1",
42
+ "react": "^19.2.0",
43
+ "react-dom": "^19.2.0",
44
+ "typescript": "^6.0.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "@types/react": {
48
+ "optional": true
49
+ },
50
+ "@types/react-dom": {
51
+ "optional": true
52
+ },
53
+ "react": {
54
+ "optional": true
55
+ },
56
+ "react-dom": {
57
+ "optional": true
58
+ }
38
59
  }
39
60
  }
package/run.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parseArgs } from "./config.js";
2
- import { ConfigValidator, ConfigValidatorLive, ReportPipelineLive, buildEmittedManifest, buildTargetGroups, computeExeFileName, createEntryName, generateMeta, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveTargets, runExeBuild, writeResolvedTsconfig, writeTargetsBinding } from "@savvy-web/tsdown-plugins";
2
+ import { BuildCollector, ConfigValidator, ConfigValidatorLive, ReportPipelineLive, buildEmittedManifest, buildTargetGroups, computeExeFileName, createEntryName, generateMeta, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveNextVersions, resolveTargets, rewriteMetaVersions, runExeBuild, writeResolvedTsconfig, writeTargetsBinding } from "@savvy-web/tsdown-plugins";
3
3
  import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { Effect } from "effect";
@@ -74,12 +74,13 @@ function applySubdirMetaEntries(overrides, dtsBasenames, exportPaths) {
74
74
  }
75
75
  /** Run a build from a normalized config. Pure orchestration; all IO injectable. */
76
76
  async function runBuild(config, options) {
77
- const { target, noExe } = parseArgs(options.argv);
77
+ const { target, noExe, verbose } = parseArgs(options.argv);
78
78
  const build = options.buildTargetGroups ?? buildTargetGroups;
79
79
  const cwd = options.cwd;
80
80
  const pkg = readPackageJson(cwd);
81
81
  const version = options.readVersion ? options.readVersion() : pkg.version ?? "0.0.0";
82
82
  const packageName = options.readPackageName ? options.readPackageName() : pkg.name ?? "unknown";
83
+ const collector = new BuildCollector();
83
84
  const jsx = resolveJsxConfig((options.readTsconfigJsx ?? (() => readTsconfigJsx(cwd)))(), config.jsx);
84
85
  const tsconfigPath = (options.writeTsconfig ?? ((c) => writeResolvedTsconfig({
85
86
  cwd: c,
@@ -128,34 +129,10 @@ async function runBuild(config, options) {
128
129
  const hasJsEntries = Object.keys(entries).length > 0;
129
130
  validateSubdirOverrides(config.overrides, entries, packageName);
130
131
  if (target === "meta") {
131
- if (config.meta === false) {
132
- (options.writeOutput ?? ((o) => process.stdout.write(`${o.content}\n`)))({
133
- target: "stdout",
134
- contentType: "text/plain",
135
- content: `meta: generation disabled (meta: false) for ${packageName}`
136
- });
137
- return;
138
- }
139
- const norm = normalizeMetaOptions(config.meta ?? {});
140
- const dtsBasenames = {};
141
- for (const name of Object.keys(entries)) dtsBasenames[name] = name;
142
- const exportPaths = deriveExportPaths(entries, exportsMap);
143
- applySubdirMetaEntries(config.overrides, dtsBasenames, exportPaths);
144
- await runGenerateMeta({
145
- cwd,
146
- packageName,
147
- tsconfigPath,
148
- dtsDir: join(cwd, "dist", "dev", "pkg"),
149
- entries: dtsBasenames,
150
- exportPaths,
151
- outMetaDir: join(cwd, "dist", "dev", "meta"),
152
- localPaths: norm.localPaths,
153
- tsdoc: norm.tsdoc
154
- });
155
132
  (options.writeOutput ?? ((o) => process.stdout.write(`${o.content}\n`)))({
156
133
  target: "stdout",
157
134
  contentType: "text/plain",
158
- content: `meta: wrote api-model for ${packageName} to ${norm.localPaths.length} localPath(s)`
135
+ content: `meta: --target meta is deprecated and now a no-op; meta is emitted by --target prod (${packageName}).`
159
136
  });
160
137
  return;
161
138
  }
@@ -164,7 +141,10 @@ async function runBuild(config, options) {
164
141
  await (options.runExeBuild ?? runExeBuild)({
165
142
  cwd,
166
143
  outDir: join(cwd, "dist", "dev", "pkg", "bin"),
167
- specs: exeSpecs
144
+ specs: exeSpecs,
145
+ collector,
146
+ groupId: "dev",
147
+ verbose
168
148
  });
169
149
  (options.writeOutput ?? ((o) => process.stdout.write(`${o.content}\n`)))({
170
150
  target: "stdout",
@@ -221,7 +201,6 @@ async function runBuild(config, options) {
221
201
  dualExports = dualExportKeys;
222
202
  }
223
203
  const looseFiles = config.looseFiles !== void 0 ? normalizeLooseFiles(config.looseFiles) : void 0;
224
- const startMs = Date.now();
225
204
  const { groups, resolution } = target === "dev" ? {
226
205
  groups: [{
227
206
  id: "dev",
@@ -229,104 +208,113 @@ async function runBuild(config, options) {
229
208
  }],
230
209
  resolution: void 0
231
210
  } : deriveProdGroups(publishTargets, packageName);
232
- if (hasJsEntries || config.exe === void 0) await build({
233
- cwd,
234
- version,
235
- entry: config.overrides !== void 0 ? baseEntries : entries,
236
- tsconfigPath,
237
- groups,
238
- devManifest: config.devManifest,
239
- externals: config.externals,
240
- ...config.bundledPackages !== void 0 ? { bundledPackages: config.bundledPackages } : {},
241
- ...config.dtsExternals !== void 0 ? { dtsExternals: config.dtsExternals } : {},
242
- ...config.bundleNodeModules !== void 0 ? { bundleNodeModules: config.bundleNodeModules } : {},
243
- ...config.bundle !== void 0 ? { bundle: config.bundle } : {},
244
- ...config.minify !== void 0 ? { minify: config.minify } : {},
245
- ...config.transform !== void 0 ? { transform: config.transform } : {},
246
- ...jsx !== void 0 ? { jsx } : {},
247
- ...config.format !== void 0 ? { format: config.format } : {},
248
- ...config.define !== void 0 ? { define: config.define } : {},
249
- ...overridePartitions.length > 0 ? { overrides: overridePartitions } : {},
250
- ...dualExports !== void 0 ? { dualExports } : {},
251
- ...subdirExports.size > 0 ? { subdirExports } : {},
252
- ...looseFiles !== void 0 ? { looseFiles } : {},
253
- ...exeRewrite !== void 0 ? { exeRewrite } : {}
254
- });
255
- if (target === "prod" && resolution !== void 0) writeBinding(cwd, resolution);
256
- if (target === "prod" && config.meta !== false && (config.exe === void 0 || hasJsEntries)) {
257
- const metaGroupId = (groups.find((g) => g.name === packageName) ?? groups[0])?.id ?? "npm";
258
- const norm = normalizeMetaOptions(config.meta ?? {});
259
- const dtsBasenames = {};
260
- for (const name of Object.keys(entries)) dtsBasenames[name] = name;
261
- const exportPaths = deriveExportPaths(entries, exportsMap);
262
- applySubdirMetaEntries(config.overrides, dtsBasenames, exportPaths);
263
- await runGenerateMeta({
211
+ const explicitFormat = config.output?.format;
212
+ const writeOutput = options.writeOutput ?? ((o) => process.stdout.write(`${o.content}\n`));
213
+ const renderAndWrite = async () => {
214
+ const rendered = await Effect.runPromise(renderReport(collector.snapshot(packageName), {
215
+ ...explicitFormat !== void 0 ? { explicitFormat } : {},
216
+ verbose,
217
+ noColor: process.env.NO_COLOR !== void 0 || !process.stdout.isTTY
218
+ }).pipe(Effect.provide(ReportPipelineLive)));
219
+ for (const output of rendered) writeOutput(output);
220
+ };
221
+ try {
222
+ if (hasJsEntries || config.exe === void 0) await build({
264
223
  cwd,
265
- packageName,
224
+ version,
225
+ entry: config.overrides !== void 0 ? baseEntries : entries,
266
226
  tsconfigPath,
267
- dtsDir: join(cwd, "dist", "prod", metaGroupId, "pkg"),
268
- entries: dtsBasenames,
269
- exportPaths,
270
- outMetaDir: join(cwd, "dist", "prod", metaGroupId, "meta"),
271
- localPaths: [],
272
- tsdoc: norm.tsdoc
227
+ groups,
228
+ devManifest: config.devManifest,
229
+ externals: config.externals,
230
+ ...config.bundledPackages !== void 0 ? { bundledPackages: config.bundledPackages } : {},
231
+ ...config.dtsExternals !== void 0 ? { dtsExternals: config.dtsExternals } : {},
232
+ ...config.bundleNodeModules !== void 0 ? { bundleNodeModules: config.bundleNodeModules } : {},
233
+ ...config.bundle !== void 0 ? { bundle: config.bundle } : {},
234
+ ...config.minify !== void 0 ? { minify: config.minify } : {},
235
+ ...config.transform !== void 0 ? { transform: config.transform } : {},
236
+ ...jsx !== void 0 ? { jsx } : {},
237
+ ...config.format !== void 0 ? { format: config.format } : {},
238
+ ...config.define !== void 0 ? { define: config.define } : {},
239
+ ...overridePartitions.length > 0 ? { overrides: overridePartitions } : {},
240
+ ...dualExports !== void 0 ? { dualExports } : {},
241
+ ...subdirExports.size > 0 ? { subdirExports } : {},
242
+ ...looseFiles !== void 0 ? { looseFiles } : {},
243
+ ...exeRewrite !== void 0 ? { exeRewrite } : {},
244
+ collector,
245
+ verbose
273
246
  });
274
- }
275
- if (target === "prod") for (const g of groups) removeDeclarationMaps(join(cwd, "dist", "prod", g.id, "pkg"));
276
- if (config.exe !== void 0 && exeSpec !== void 0 && exeRewrite !== void 0 && exeFileName !== void 0) {
277
- const runExe = options.runExeBuild ?? runExeBuild;
278
- const groupOutDir = (g) => target === "dev" ? join(cwd, "dist", "dev", "pkg") : join(cwd, "dist", "prod", g.id, "pkg");
279
- for (const g of groups) {
280
- const outDir = groupOutDir(g);
281
- if (!hasJsEntries) {
282
- const manifest = await buildEmittedManifest({
283
- pkg,
284
- targetGroup: {
285
- id: g.id,
286
- name: g.name,
287
- isProd: target === "prod"
288
- },
289
- devManifest: config.devManifest,
290
- ...config.transform !== void 0 ? { transform: config.transform } : {},
291
- exeRewrite
292
- });
293
- mkdirSync(outDir, { recursive: true });
294
- writeFileSync(join(outDir, "package.json"), `${JSON.stringify(manifest, null, " ")}\n`);
295
- for (const file of ["LICENSE", "README.md"]) try {
296
- copyFileSync(join(cwd, file), join(outDir, file));
297
- } catch {}
298
- }
299
- if (!noExe) {
300
- const binDir = join(outDir, "bin");
301
- await runExe({
302
- cwd,
303
- outDir: binDir,
304
- specs: exeSpecs
305
- });
306
- if (options.runExeBuild === void 0 && !existsSync(join(binDir, exeFileName))) throw new Error(`exe: expected compiled binary at ${join(binDir, exeFileName)} but it is missing — tsdown's emitted name may have drifted from computeExeFileName`);
247
+ if (target === "prod" && resolution !== void 0) writeBinding(cwd, resolution);
248
+ if (target === "prod" && config.meta !== false && (config.exe === void 0 || hasJsEntries)) {
249
+ const norm = normalizeMetaOptions(config.meta ?? {});
250
+ const canonicalId = (groups.find((g) => g.name === packageName) ?? groups[0])?.id ?? "npm";
251
+ const dtsBasenames = {};
252
+ for (const name of Object.keys(entries)) if (!name.startsWith("bin/")) dtsBasenames[name] = name;
253
+ const exportPaths = deriveExportPaths(entries, exportsMap);
254
+ applySubdirMetaEntries(config.overrides, dtsBasenames, exportPaths);
255
+ const resolveNext = options.resolveNextVersions ?? resolveNextVersions;
256
+ const nextVersions = norm.optimistic ? await resolveNext(cwd) : void 0;
257
+ const manifestTransform = nextVersions ? (m) => rewriteMetaVersions(m, nextVersions.versions, packageName) : void 0;
258
+ for (const g of groups) await runGenerateMeta({
259
+ cwd,
260
+ packageName,
261
+ tsconfigPath,
262
+ dtsDir: join(cwd, "dist", "prod", g.id, "pkg"),
263
+ entries: dtsBasenames,
264
+ exportPaths,
265
+ outMetaDir: join(cwd, "dist", "prod", g.id, "meta"),
266
+ localPaths: g.id === canonicalId ? norm.localPaths : [],
267
+ tsdoc: norm.tsdoc,
268
+ ...manifestTransform !== void 0 ? { manifestTransform } : {},
269
+ onMessage: (e) => {
270
+ if (e.level === "error") collector.recordError(g.id, e);
271
+ else collector.recordWarning(g.id, e);
272
+ }
273
+ });
274
+ }
275
+ if (target === "prod") for (const g of groups) removeDeclarationMaps(join(cwd, "dist", "prod", g.id, "pkg"));
276
+ if (config.exe !== void 0 && exeSpec !== void 0 && exeRewrite !== void 0 && exeFileName !== void 0) {
277
+ const runExe = options.runExeBuild ?? runExeBuild;
278
+ const groupOutDir = (g) => target === "dev" ? join(cwd, "dist", "dev", "pkg") : join(cwd, "dist", "prod", g.id, "pkg");
279
+ for (const g of groups) {
280
+ const outDir = groupOutDir(g);
281
+ if (!hasJsEntries) {
282
+ const manifest = await buildEmittedManifest({
283
+ pkg,
284
+ targetGroup: {
285
+ id: g.id,
286
+ name: g.name,
287
+ isProd: target === "prod"
288
+ },
289
+ devManifest: config.devManifest,
290
+ ...config.transform !== void 0 ? { transform: config.transform } : {},
291
+ exeRewrite
292
+ });
293
+ mkdirSync(outDir, { recursive: true });
294
+ writeFileSync(join(outDir, "package.json"), `${JSON.stringify(manifest, null, " ")}\n`);
295
+ for (const file of ["LICENSE", "README.md"]) try {
296
+ copyFileSync(join(cwd, file), join(outDir, file));
297
+ } catch {}
298
+ }
299
+ if (!noExe) {
300
+ const binDir = join(outDir, "bin");
301
+ await runExe({
302
+ cwd,
303
+ outDir: binDir,
304
+ specs: exeSpecs,
305
+ collector,
306
+ groupId: g.id,
307
+ verbose
308
+ });
309
+ if (options.runExeBuild === void 0 && !existsSync(join(binDir, exeFileName))) throw new Error(`exe: expected compiled binary at ${join(binDir, exeFileName)} but it is missing — tsdown's emitted name may have drifted from computeExeFileName`);
310
+ }
307
311
  }
308
312
  }
313
+ } catch (err) {
314
+ await renderAndWrite();
315
+ throw err;
309
316
  }
310
- const totalMs = Date.now() - startMs;
311
- const reportEntries = Object.keys(entries);
312
- const report = {
313
- package: packageName,
314
- targetGroups: groups.map((g) => ({
315
- id: g.id,
316
- entries: reportEntries,
317
- emittedFiles: [],
318
- timings: { totalMs },
319
- warnings: [],
320
- errors: []
321
- }))
322
- };
323
- const explicitFormat = config.output?.format;
324
- const rendered = await Effect.runPromise(renderReport([report], {
325
- ...explicitFormat !== void 0 ? { explicitFormat } : {},
326
- noColor: process.env.NO_COLOR !== void 0 || !process.stdout.isTTY
327
- }).pipe(Effect.provide(ReportPipelineLive)));
328
- const writeOutput = options.writeOutput ?? ((o) => process.stdout.write(`${o.content}\n`));
329
- for (const output of rendered) writeOutput(output);
317
+ await renderAndWrite();
330
318
  }
331
319
 
332
320
  //#endregion