@savvy-web/bundler 0.8.0 → 0.9.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
@@ -89,6 +89,8 @@ With no `targets` map the build falls back to the single-`npm` group above.
89
89
 
90
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
+ API Extractor's analyzer messages — forgotten exports, missing release tags and TSDoc issues — surface in the build log rather than being dropped. A forgotten export (a type reachable from your public API but not itself exported) fails the build under CI (`CI` or `GITHUB_ACTIONS` set); locally it stays a warning so an incremental build is not blocked. Listing the message in `tsdoc.suppressWarnings` suppresses both the local warning and the CI failure, and the build log accounts for what it suppressed.
93
+
92
94
  The `meta` field on `defineBuild` is tri-state:
93
95
 
94
96
  - **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.
package/config.js CHANGED
@@ -1,7 +1,11 @@
1
1
  import { defaultManifestTransform } from "@savvy-web/tsdown-plugins";
2
2
 
3
3
  //#region src/config.ts
4
- /** Normalize + validate a defineBuild config. Pure when imported; self-runs when entry (see run.ts). */
4
+ /**
5
+ * Normalize + validate a defineBuild config. Pure when imported; self-runs when entry (see run.ts).
6
+ *
7
+ * @public
8
+ */
5
9
  function defineBuild(input = {}) {
6
10
  return {
7
11
  formats: input.formats ?? ["esm"],
@@ -23,6 +27,7 @@ function defineBuild(input = {}) {
23
27
  define: input.define
24
28
  };
25
29
  }
30
+ /** Parse the build CLI argv into the normalized target/flags shape. @public */
26
31
  function parseArgs(argv) {
27
32
  let target = "dev";
28
33
  let watch = false;
package/index.d.ts CHANGED
@@ -1,6 +1,7 @@
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";
1
+ import { BuildFormat, BuildPlatform, BuildReport, 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
+ /** @public */
4
5
  interface BuildEntryOverride {
5
6
  /** Export paths to pin to this partition, e.g. "./changesets/markdownlint" (or "." for root). */
6
7
  readonly entries: ReadonlyArray<string>;
@@ -21,6 +22,7 @@ interface BuildEntryOverride {
21
22
  */
22
23
  readonly outSubdir?: string | undefined;
23
24
  }
25
+ /** @public */
24
26
  interface OutputConfig {
25
27
  readonly console?: {
26
28
  readonly human?: boolean;
@@ -29,6 +31,7 @@ interface OutputConfig {
29
31
  };
30
32
  readonly format?: "terminal" | "json" | "markdown" | "ci-annotations" | "silent";
31
33
  }
34
+ /** @public */
32
35
  interface BuildConfigInput {
33
36
  readonly formats?: ReadonlyArray<"esm">;
34
37
  readonly externals?: ReadonlyArray<string>;
@@ -50,8 +53,8 @@ interface BuildConfigInput {
50
53
  /**
51
54
  * Force-bundle node_modules (and workspace) JS dependencies that are not
52
55
  * externalized into the package output, restoring the self-contained bundle
53
- * the rslib builder produced. Threads tsdown `deps.skipNodeModulesBundle:
54
- * false` into BOTH the JS output and the bundled declarations: the dts posture
56
+ * the rslib builder produced. Threads tsdown `deps.skipNodeModulesBundle: false`
57
+ * into BOTH the JS output and the bundled declarations: the dts posture
55
58
  * tracks the JS posture, so node_modules types are inlined into the `.d.ts`
56
59
  * and the published package needs no extra declared deps for them. Defaults to false.
57
60
  */
@@ -75,7 +78,7 @@ interface BuildConfigInput {
75
78
  readonly devManifest?: "preserve" | "resolve";
76
79
  /**
77
80
  * Final mutation of the emitted package.json, run after the declarative
78
- * `publishConfig.targets` rename. Defaults to {@link defaultManifestTransform},
81
+ * `publishConfig.targets` rename. Defaults to `defaultManifestTransform`,
79
82
  * which strips build/dev-only fields (devDependencies, scripts, publishConfig,
80
83
  * etc.). Supplying your own REPLACES that default — import and call
81
84
  * `defaultManifestTransform` from it if you still want the stripping.
@@ -123,6 +126,7 @@ interface BuildConfigInput {
123
126
  */
124
127
  readonly define?: Record<string, string> | undefined;
125
128
  }
129
+ /** @public */
126
130
  interface BuildConfig {
127
131
  readonly formats: ReadonlyArray<"esm">;
128
132
  readonly externals: ReadonlyArray<string>;
@@ -169,8 +173,13 @@ interface BuildConfig {
169
173
  /** Compile-time global replacements forwarded to the build `define` (merged with the auto-version). */
170
174
  readonly define?: Record<string, string> | undefined;
171
175
  }
172
- /** Normalize + validate a defineBuild config. Pure when imported; self-runs when entry (see run.ts). */
176
+ /**
177
+ * Normalize + validate a defineBuild config. Pure when imported; self-runs when entry (see run.ts).
178
+ *
179
+ * @public
180
+ */
173
181
  declare function defineBuild(input?: BuildConfigInput): BuildConfig;
182
+ /** @public */
174
183
  interface ParsedArgs {
175
184
  readonly target: "dev" | "prod" | "meta" | "exe";
176
185
  readonly watch: boolean;
@@ -178,9 +187,11 @@ interface ParsedArgs {
178
187
  readonly noExe: boolean;
179
188
  readonly verbose: boolean;
180
189
  }
190
+ /** Parse the build CLI argv into the normalized target/flags shape. @public */
181
191
  declare function parseArgs(argv: ReadonlyArray<string>): ParsedArgs;
182
192
  //#endregion
183
193
  //#region src/run.d.ts
194
+ /** @public */
184
195
  interface RunOptions {
185
196
  readonly cwd: string;
186
197
  readonly argv: ReadonlyArray<string>;
@@ -213,8 +224,15 @@ interface RunOptions {
213
224
  }) | undefined;
214
225
  /** Injectable for tests: resolves next release versions for the optimistic meta rewrite. */
215
226
  readonly resolveNextVersions?: ((cwd: string) => Promise<NextVersions>) | undefined;
227
+ /** Injectable issues-artifact writer (defaults to writeIssuesArtifact). */
228
+ readonly writeIssues?: (opts: {
229
+ cwd: string;
230
+ target: "dev" | "prod";
231
+ reports: ReadonlyArray<BuildReport>;
232
+ now?: () => Date;
233
+ }) => string | undefined;
216
234
  }
217
- /** Run a build from a normalized config. Pure orchestration; all IO injectable. */
235
+ /** Run a build from a normalized config. Pure orchestration; all IO injectable. @public */
218
236
  declare function runBuild(config: BuildConfig, options: RunOptions): Promise<void>;
219
237
  //#endregion
220
238
  export { type BuildConfig, type BuildConfigInput, type BuildEntryOverride, type OutputConfig, type ParsedArgs, type RunOptions, defaultManifestTransform, defineBuild, parseArgs, runBuild };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/bundler",
3
- "version": "0.8.0",
3
+ "version": "0.9.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,16 +29,16 @@
29
29
  "./package.json": "./package.json"
30
30
  },
31
31
  "dependencies": {
32
- "@savvy-web/tsdown-plugins": "0.8.0",
32
+ "@savvy-web/tsdown-plugins": "0.9.0",
33
33
  "@tsdown/exe": "^0.22.1",
34
- "effect": "^3.21.3",
34
+ "effect": "^3.21.4",
35
35
  "tsdown": "^0.22.3"
36
36
  },
37
37
  "peerDependencies": {
38
- "@types/node": "^25.9.0",
38
+ "@types/node": "^26.0.0",
39
39
  "@types/react": "^19.2.0",
40
40
  "@types/react-dom": "^19.2.0",
41
- "@typescript/native-preview": "^7.0.0-dev.20260513.1",
41
+ "@typescript/native-preview": "^7.0.0-dev.20260612.1",
42
42
  "react": "^19.2.0",
43
43
  "react-dom": "^19.2.0",
44
44
  "typescript": "^6.0.0"
package/run.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parseArgs } from "./config.js";
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";
2
+ import { BuildCollector, ConfigValidator, ConfigValidatorLive, ReportPipelineLive, buildEmittedManifest, buildTargetGroups, computeExeFileName, createEntryName, deriveExportPaths, normalizeExeOptions, normalizeLooseFiles, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveTargets, runExeBuild, runMetaPass, writeIssuesArtifact, 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";
@@ -27,22 +27,14 @@ function deriveProdGroups(targets, baseName) {
27
27
  resolution
28
28
  };
29
29
  }
30
- /** Map entry names to export paths using the package exports map. index maps to ".". */
31
- function deriveExportPaths(entries, exportsMap) {
32
- const out = {};
33
- const sourceToKey = /* @__PURE__ */ new Map();
34
- if (exportsMap) for (const [key, src] of Object.entries(exportsMap)) sourceToKey.set(src, key);
35
- for (const [entryName, src] of Object.entries(entries)) out[entryName] = sourceToKey.get(src) ?? (entryName === "index" ? "." : `./${entryName}`);
36
- return out;
37
- }
38
30
  /**
39
31
  * Fast-fail validation for `outSubdir` overrides, run on EVERY target path. The dev/prod override-partition
40
32
  * loop already validates these (and all other overrides) before building, but `--target meta` returns early
41
- * (before that loop) and remaps meta dts basenames via `applySubdirMetaEntries` — which assumes validated
42
- * input. Without this guard, a malformed `outSubdir` override (more than one entry, a non-canonical export
43
- * path, or an export path that is not a real build entry) would silently remap a wrong/nonexistent key on the
44
- * meta path. Mirrors the override loop's conditions and messages verbatim so every target path fast-fails
45
- * identically. No-op when there are no overrides or none set `outSubdir`.
33
+ * (before that loop) and remaps meta dts basenames (logic now in @savvy-web/tsdown-plugins) — which assumes
34
+ * validated input. Without this guard, a malformed `outSubdir` override (more than one entry, a non-canonical
35
+ * export path, or an export path that is not a real build entry) would silently remap a wrong/nonexistent key
36
+ * on the meta path. Mirrors the override loop's conditions and messages verbatim so every target path
37
+ * fast-fails identically. No-op when there are no overrides or none set `outSubdir`.
46
38
  */
47
39
  function validateSubdirOverrides(overrides, entries, packageName) {
48
40
  if (overrides === void 0) return;
@@ -55,24 +47,7 @@ function validateSubdirOverrides(overrides, entries, packageName) {
55
47
  if (entries[flatName] === void 0) throw new Error(`overrides: export path "${exportPath}" (entry "${flatName}") is not a build entry of ${packageName}`);
56
48
  }
57
49
  }
58
- /**
59
- * For each `outSubdir` override, point its meta entry at the isolated sub-package barrel: the dts lives
60
- * at `<subdir>/index.d.ts` (not `<flatName>.d.ts`). Keyed by the stable flattened entry name so it
61
- * overwrites the default `dtsBasenames[flatName] = flatName` set from the full entry map. No-op when no
62
- * override sets `outSubdir`.
63
- */
64
- function applySubdirMetaEntries(overrides, dtsBasenames, exportPaths) {
65
- if (overrides === void 0) return;
66
- for (const ov of overrides) {
67
- if (ov.outSubdir === void 0) continue;
68
- const exportPath = ov.entries[0];
69
- if (exportPath === void 0) continue;
70
- const flatName = createEntryName(exportPath, false);
71
- dtsBasenames[flatName] = `${ov.outSubdir}/index`;
72
- exportPaths[flatName] = exportPath;
73
- }
74
- }
75
- /** Run a build from a normalized config. Pure orchestration; all IO injectable. */
50
+ /** Run a build from a normalized config. Pure orchestration; all IO injectable. @public */
76
51
  async function runBuild(config, options) {
77
52
  const { target, noExe, verbose } = parseArgs(options.argv);
78
53
  const build = options.buildTargetGroups ?? buildTargetGroups;
@@ -91,7 +66,6 @@ async function runBuild(config, options) {
91
66
  ...jsx?.runtime === "classic" ? { jsx: "react" } : {}
92
67
  })))(cwd);
93
68
  const exportsMap = options.readExports ? options.readExports() : pkg.exports;
94
- const runGenerateMeta = options.generateMeta ?? generateMeta;
95
69
  const publishTargets = (options.readPublishTargets ?? (() => {
96
70
  const declared = pkg.publishConfig?.targets;
97
71
  return declared !== void 0 && !Array.isArray(declared) && typeof declared === "object" ? declared : void 0;
@@ -218,6 +192,16 @@ async function runBuild(config, options) {
218
192
  }).pipe(Effect.provide(ReportPipelineLive)));
219
193
  for (const output of rendered) writeOutput(output);
220
194
  };
195
+ const writeIssuesBestEffort = () => {
196
+ if (target !== "dev" && target !== "prod") return;
197
+ try {
198
+ (options.writeIssues ?? writeIssuesArtifact)({
199
+ cwd,
200
+ target,
201
+ reports: collector.snapshot(packageName)
202
+ });
203
+ } catch {}
204
+ };
221
205
  try {
222
206
  if (hasJsEntries || config.exe === void 0) await build({
223
207
  cwd,
@@ -246,30 +230,20 @@ async function runBuild(config, options) {
246
230
  });
247
231
  if (target === "prod" && resolution !== void 0) writeBinding(cwd, resolution);
248
232
  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({
233
+ const ci = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
234
+ await runMetaPass({
259
235
  cwd,
260
236
  packageName,
261
237
  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
- }
238
+ groups,
239
+ entries,
240
+ exportsMap,
241
+ overrides: config.overrides,
242
+ meta: config.meta ?? {},
243
+ collector,
244
+ ci,
245
+ ...options.generateMeta !== void 0 ? { generateMeta: options.generateMeta } : {},
246
+ ...options.resolveNextVersions !== void 0 ? { resolveNextVersions: options.resolveNextVersions } : {}
273
247
  });
274
248
  }
275
249
  if (target === "prod") for (const g of groups) removeDeclarationMaps(join(cwd, "dist", "prod", g.id, "pkg"));
@@ -312,9 +286,11 @@ async function runBuild(config, options) {
312
286
  }
313
287
  } catch (err) {
314
288
  await renderAndWrite();
289
+ writeIssuesBestEffort();
315
290
  throw err;
316
291
  }
317
292
  await renderAndWrite();
293
+ writeIssuesBestEffort();
318
294
  }
319
295
 
320
296
  //#endregion
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.58.9"
9
+ }
10
+ ]
11
+ }