@savvy-web/bundler 0.6.0 → 0.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.
Files changed (4) hide show
  1. package/README.md +19 -12
  2. package/index.d.ts +8 -5
  3. package/package.json +3 -3
  4. package/run.js +12 -32
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.
@@ -295,7 +302,7 @@ const config = defineBuild({
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/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;
@@ -209,6 +210,8 @@ interface RunOptions {
209
210
  os: ReadonlyArray<string>;
210
211
  cpu: ReadonlyArray<string>;
211
212
  }) | undefined;
213
+ /** Injectable for tests: resolves next release versions for the optimistic meta rewrite. */
214
+ readonly resolveNextVersions?: ((cwd: string) => Promise<NextVersions>) | undefined;
212
215
  }
213
216
  /** Run a build from a normalized config. Pure orchestration; all IO injectable. */
214
217
  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.0",
3
+ "version": "0.7.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,9 +29,9 @@
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.7.0",
33
33
  "@tsdown/exe": "^0.22.1",
34
- "tsdown": "^0.22.2"
34
+ "tsdown": "^0.22.3"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "effect": ">=3.21.0"
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 { 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";
@@ -128,34 +128,10 @@ async function runBuild(config, options) {
128
128
  const hasJsEntries = Object.keys(entries).length > 0;
129
129
  validateSubdirOverrides(config.overrides, entries, packageName);
130
130
  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
131
  (options.writeOutput ?? ((o) => process.stdout.write(`${o.content}\n`)))({
156
132
  target: "stdout",
157
133
  contentType: "text/plain",
158
- content: `meta: wrote api-model for ${packageName} to ${norm.localPaths.length} localPath(s)`
134
+ content: `meta: --target meta is deprecated and now a no-op; meta is emitted by --target prod (${packageName}).`
159
135
  });
160
136
  return;
161
137
  }
@@ -254,22 +230,26 @@ async function runBuild(config, options) {
254
230
  });
255
231
  if (target === "prod" && resolution !== void 0) writeBinding(cwd, resolution);
256
232
  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
233
  const norm = normalizeMetaOptions(config.meta ?? {});
234
+ const canonicalId = (groups.find((g) => g.name === packageName) ?? groups[0])?.id ?? "npm";
259
235
  const dtsBasenames = {};
260
236
  for (const name of Object.keys(entries)) dtsBasenames[name] = name;
261
237
  const exportPaths = deriveExportPaths(entries, exportsMap);
262
238
  applySubdirMetaEntries(config.overrides, dtsBasenames, exportPaths);
263
- await runGenerateMeta({
239
+ const resolveNext = options.resolveNextVersions ?? resolveNextVersions;
240
+ const nextVersions = norm.optimistic ? await resolveNext(cwd) : void 0;
241
+ const manifestTransform = nextVersions ? (m) => rewriteMetaVersions(m, nextVersions.versions, packageName) : void 0;
242
+ for (const g of groups) await runGenerateMeta({
264
243
  cwd,
265
244
  packageName,
266
245
  tsconfigPath,
267
- dtsDir: join(cwd, "dist", "prod", metaGroupId, "pkg"),
246
+ dtsDir: join(cwd, "dist", "prod", g.id, "pkg"),
268
247
  entries: dtsBasenames,
269
248
  exportPaths,
270
- outMetaDir: join(cwd, "dist", "prod", metaGroupId, "meta"),
271
- localPaths: [],
272
- tsdoc: norm.tsdoc
249
+ outMetaDir: join(cwd, "dist", "prod", g.id, "meta"),
250
+ localPaths: g.id === canonicalId ? norm.localPaths : [],
251
+ tsdoc: norm.tsdoc,
252
+ ...manifestTransform !== void 0 ? { manifestTransform } : {}
273
253
  });
274
254
  }
275
255
  if (target === "prod") for (const g of groups) removeDeclarationMaps(join(cwd, "dist", "prod", g.id, "pkg"));