@savvy-web/tsdown-plugins 0.6.0 → 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.
@@ -1,4 +1,7 @@
1
1
  import { emitManifest } from "../manifest/emit-manifest.js";
2
+ import { buildMetricsPlugin } from "../report/metrics-plugin.js";
3
+ import { createTimer } from "../report/timer.js";
4
+ import { createTsdownLogger } from "../report/tsdown-logger.js";
2
5
  import { cjsDefaultInterop } from "./cjs-default-interop.js";
3
6
  import { nodeBuiltinDefaultInterop } from "./node-builtin-default-interop.js";
4
7
  import { syncPublicDir } from "./sync-public.js";
@@ -24,7 +27,23 @@ import { dirname, join } from "node:path";
24
27
  async function buildTargetGroups(options) {
25
28
  const build = options.build ?? (await import("tsdown")).build;
26
29
  const publicDir = join(options.cwd, "public");
30
+ const collector = options.collector;
31
+ const verbose = options.verbose ?? false;
32
+ const instrument = (groupId) => collector === void 0 ? {} : {
33
+ logLevel: "silent",
34
+ customLogger: createTsdownLogger(collector, groupId)
35
+ };
36
+ const metricsPlugins = (groupId, pass) => collector === void 0 ? [] : [buildMetricsPlugin(collector, groupId, pass, verbose)];
37
+ const timed = async (groupId, pass, run) => {
38
+ const timer = createTimer();
39
+ try {
40
+ await run();
41
+ } finally {
42
+ if (collector !== void 0) collector.recordPassTiming(groupId, pass, timer.elapsed());
43
+ }
44
+ };
27
45
  for (const group of options.groups) {
46
+ if (collector !== void 0) collector.registerGroup(group.id, Object.keys(options.entry));
28
47
  const partitions = [{
29
48
  entry: options.entry,
30
49
  ...options.format !== void 0 ? { format: options.format } : {},
@@ -81,7 +100,7 @@ async function buildTargetGroups(options) {
81
100
  ...options.subdirExports !== void 0 ? { subdirExports: options.subdirExports } : {},
82
101
  ...options.exeRewrite !== void 0 ? { exeRewrite: options.exeRewrite } : {}
83
102
  }) : void 0;
84
- await build({
103
+ await timed(group.id, "js", () => build({
85
104
  config: false,
86
105
  cwd: options.cwd,
87
106
  entry: jsEntry,
@@ -95,6 +114,7 @@ async function buildTargetGroups(options) {
95
114
  fixedExtension: js.fixedExtension,
96
115
  dts: js.dts,
97
116
  define: js.define,
117
+ ...instrument(group.id),
98
118
  ...part.css !== void 0 ? { css: part.css } : {},
99
119
  ...partExternals?.length || partBundleNodeModules || partBundle?.length ? { deps: {
100
120
  ...partExternals?.length ? { neverBundle: partExternals } : {},
@@ -106,12 +126,14 @@ async function buildTargetGroups(options) {
106
126
  plugins: [
107
127
  ...manifestPlugin ? [manifestPlugin] : [],
108
128
  ...js.format.includes("cjs") ? [nodeBuiltinDefaultInterop(), cjsDefaultInterop()] : [],
109
- ...options.extraPlugins ?? []
129
+ ...options.extraPlugins ?? [],
130
+ ...metricsPlugins(group.id, "js")
110
131
  ]
111
- });
132
+ }));
112
133
  if (isBase) syncPublicDir(publicDir, join(js.outDir, "public"));
113
134
  const dtsNeverBundle = [...partExternals ?? [], ...partDtsExternals ?? []];
114
- await build({
135
+ if (Object.keys(dts.entry).length === 0) continue;
136
+ await timed(group.id, "dts", () => build({
115
137
  config: false,
116
138
  cwd: options.cwd,
117
139
  entry: dts.entry,
@@ -124,6 +146,7 @@ async function buildTargetGroups(options) {
124
146
  fixedExtension: dts.fixedExtension,
125
147
  dts: dts.dts,
126
148
  define: dts.define,
149
+ ...instrument(group.id),
127
150
  ...dtsNeverBundle.length > 0 || partBundleNodeModules || dts.bundledPackages ? { deps: {
128
151
  ...dtsNeverBundle.length > 0 ? { neverBundle: dtsNeverBundle } : {},
129
152
  ...partBundleNodeModules ? {
@@ -135,8 +158,12 @@ async function buildTargetGroups(options) {
135
158
  } : {}
136
159
  } } : {},
137
160
  ...dts.jsx !== void 0 ? { inputOptions: { jsx: dts.jsx } } : {},
138
- plugins: [...dts.format.includes("cjs") ? [nodeBuiltinDefaultInterop(), cjsDefaultInterop()] : [], ...options.extraPlugins ?? []]
139
- });
161
+ plugins: [
162
+ ...dts.format.includes("cjs") ? [nodeBuiltinDefaultInterop(), cjsDefaultInterop()] : [],
163
+ ...options.extraPlugins ?? [],
164
+ ...metricsPlugins(group.id, "dts")
165
+ ]
166
+ }));
140
167
  }
141
168
  const looseOutDir = outDirFor(options.cwd, group.id);
142
169
  const isProdGroup = group.id !== "dev";
@@ -147,7 +174,7 @@ async function buildTargetGroups(options) {
147
174
  };
148
175
  for (const lf of options.looseFiles ?? []) {
149
176
  const hasCjs = lf.format === "cjs";
150
- await build({
177
+ await timed(group.id, "loose", () => build({
151
178
  config: false,
152
179
  cwd: options.cwd,
153
180
  entry: { [lf.entryName]: lf.source },
@@ -164,10 +191,15 @@ async function buildTargetGroups(options) {
164
191
  "process.env.__PACKAGE_VERSION__": JSON.stringify(options.version),
165
192
  ...options.define
166
193
  },
194
+ ...instrument(group.id),
167
195
  ...Object.keys(looseDeps).length > 0 ? { deps: looseDeps } : {},
168
196
  ...hasCjs ? { cjsDefault: true } : {},
169
- plugins: [...hasCjs ? [nodeBuiltinDefaultInterop(), cjsDefaultInterop()] : [], ...options.extraPlugins ?? []]
170
- });
197
+ plugins: [
198
+ ...hasCjs ? [nodeBuiltinDefaultInterop(), cjsDefaultInterop()] : [],
199
+ ...options.extraPlugins ?? [],
200
+ ...metricsPlugins(group.id, "loose")
201
+ ]
202
+ }));
171
203
  }
172
204
  }
173
205
  }
@@ -32,6 +32,7 @@ function deriveTargetGroupOptions(options) {
32
32
  function deriveDtsPassOptions(options) {
33
33
  const isProd = options.group !== "dev";
34
34
  const format = options.format ?? ["esm"];
35
+ const entry = Object.fromEntries(Object.entries(options.entry).filter(([name]) => !name.startsWith("bin/")));
35
36
  return {
36
37
  outDir: outDirFor(options.cwd, options.group),
37
38
  sourcemap: false,
@@ -40,7 +41,7 @@ function deriveDtsPassOptions(options) {
40
41
  clean: false,
41
42
  platform: "node",
42
43
  fixedExtension: false,
43
- entry: options.entry,
44
+ entry,
44
45
  dts: {
45
46
  tsconfig: options.tsconfigPath,
46
47
  emitDtsOnly: true
@@ -0,0 +1,42 @@
1
+ import getReleasePlan from "@changesets/get-release-plan";
2
+ import { getPackages } from "@manypkg/get-packages";
3
+
4
+ //#region src/changesets/next-versions.ts
5
+ /**
6
+ * Resolve the next release version of every workspace package from pending changesets.
7
+ *
8
+ * Walks up from `cwd` to the monorepo root via `@manypkg/get-packages`, seeds the map with
9
+ * each package's CURRENT version, then overlays `newVersion` for changeset-affected packages
10
+ * via `@changesets/get-release-plan`. Never rejects: any failure (not a workspace, missing
11
+ * `.changeset/config.json`, parse error) degrades to current versions (or an empty map).
12
+ */
13
+ async function resolveNextVersions(cwd) {
14
+ try {
15
+ const packages = await getPackages(cwd);
16
+ if (packages.tool === "root") return {
17
+ root: packages.root.dir,
18
+ versions: /* @__PURE__ */ new Map()
19
+ };
20
+ const versions = /* @__PURE__ */ new Map();
21
+ for (const p of packages.packages) {
22
+ const { name, version } = p.packageJson;
23
+ if (name && version) versions.set(name, version);
24
+ }
25
+ try {
26
+ const plan = await getReleasePlan(packages.root.dir);
27
+ for (const r of plan.releases) versions.set(r.name, r.newVersion);
28
+ } catch {}
29
+ return {
30
+ root: packages.root.dir,
31
+ versions
32
+ };
33
+ } catch {
34
+ return {
35
+ root: cwd,
36
+ versions: /* @__PURE__ */ new Map()
37
+ };
38
+ }
39
+ }
40
+
41
+ //#endregion
42
+ export { resolveNextVersions };
@@ -7,7 +7,7 @@ import { Effect, Layer } from "effect";
7
7
  import { existsSync, statSync } from "node:fs";
8
8
 
9
9
  //#region src/config-validation/ConfigValidatorLive.ts
10
- const VALID_SYNTAX_KINDS = new Set([
10
+ const VALID_SYNTAX_KINDS = /* @__PURE__ */ new Set([
11
11
  "block",
12
12
  "inline",
13
13
  "modifier"
package/exe/build.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { buildMetricsPlugin } from "../report/metrics-plugin.js";
2
+ import { createTimer } from "../report/timer.js";
3
+ import { createTsdownLogger } from "../report/tsdown-logger.js";
1
4
  import { join } from "node:path";
2
5
  import { mkdtempSync, rmSync } from "node:fs";
3
6
  import { tmpdir } from "node:os";
@@ -8,6 +11,14 @@ async function runExeBuild(options) {
8
11
  const build = options.build ?? (await import("tsdown")).build;
9
12
  for (const spec of options.specs) {
10
13
  const scratch = mkdtempSync(join(tmpdir(), "savvy-exe-"));
14
+ const collector = options.collector;
15
+ const groupId = options.groupId;
16
+ const instrument = collector !== void 0 && groupId !== void 0 ? {
17
+ logLevel: "silent",
18
+ customLogger: createTsdownLogger(collector, groupId)
19
+ } : {};
20
+ const plugins = collector !== void 0 && groupId !== void 0 ? [buildMetricsPlugin(collector, groupId, "exe", options.verbose ?? false)] : [];
21
+ const timer = createTimer();
11
22
  try {
12
23
  await build({
13
24
  cwd: options.cwd,
@@ -23,9 +34,12 @@ async function runExeBuild(options) {
23
34
  outDir: options.outDir,
24
35
  seaConfig: spec.seaConfig,
25
36
  targets: spec.targets
26
- }
37
+ },
38
+ ...instrument,
39
+ ...plugins.length > 0 ? { plugins } : {}
27
40
  });
28
41
  } finally {
42
+ if (collector !== void 0 && groupId !== void 0) collector.recordPassTiming(groupId, "exe", timer.elapsed());
29
43
  rmSync(scratch, {
30
44
  recursive: true,
31
45
  force: true
package/index.d.ts CHANGED
@@ -152,6 +152,148 @@ interface EmitManifestOptions {
152
152
  /** Rolldown plugin: emit the transformed package.json + LICENSE/README into the output pkg/ root. */
153
153
  declare function emitManifest(options: EmitManifestOptions): Plugin;
154
154
  //#endregion
155
+ //#region src/report/schema.d.ts
156
+ declare const ReportTimings_base: Schema.Class<ReportTimings, {
157
+ totalMs: typeof Schema.Number;
158
+ }, Schema.Struct.Encoded<{
159
+ totalMs: typeof Schema.Number;
160
+ }>, never, {
161
+ readonly totalMs: number;
162
+ }, {}, {}>;
163
+ declare class ReportTimings extends ReportTimings_base {}
164
+ declare const DiagnosticEntry_base: Schema.Class<DiagnosticEntry, {
165
+ source: Schema.Literal<["tsdown", "rolldown", "api-extractor"]>;
166
+ level: Schema.Literal<["warn", "error"]>;
167
+ text: typeof Schema.String;
168
+ file: Schema.optional<typeof Schema.String>;
169
+ line: Schema.optional<typeof Schema.Number>;
170
+ column: Schema.optional<typeof Schema.Number>;
171
+ }, Schema.Struct.Encoded<{
172
+ source: Schema.Literal<["tsdown", "rolldown", "api-extractor"]>;
173
+ level: Schema.Literal<["warn", "error"]>;
174
+ text: typeof Schema.String;
175
+ file: Schema.optional<typeof Schema.String>;
176
+ line: Schema.optional<typeof Schema.Number>;
177
+ column: Schema.optional<typeof Schema.Number>;
178
+ }>, never, {
179
+ readonly text: string;
180
+ } & {
181
+ readonly source: "tsdown" | "rolldown" | "api-extractor";
182
+ } & {
183
+ readonly level: "warn" | "error";
184
+ } & {
185
+ readonly file?: string | undefined;
186
+ } & {
187
+ readonly line?: number | undefined;
188
+ } & {
189
+ readonly column?: number | undefined;
190
+ }, {}, {}>;
191
+ /** A captured warning or error, from tsdown's logger, rolldown's onLog, or API Extractor. */
192
+ declare class DiagnosticEntry extends DiagnosticEntry_base {}
193
+ declare const EmittedFile_base: Schema.Class<EmittedFile, {
194
+ path: typeof Schema.String;
195
+ bytes: typeof Schema.Number;
196
+ gzip: Schema.optional<typeof Schema.Number>;
197
+ }, Schema.Struct.Encoded<{
198
+ path: typeof Schema.String;
199
+ bytes: typeof Schema.Number;
200
+ gzip: Schema.optional<typeof Schema.Number>;
201
+ }>, never, {
202
+ readonly path: string;
203
+ } & {
204
+ readonly bytes: number;
205
+ } & {
206
+ readonly gzip?: number | undefined;
207
+ }, {}, {}>;
208
+ /** One emitted output file with its in-memory byte size (gzip only when --verbose). */
209
+ declare class EmittedFile extends EmittedFile_base {}
210
+ declare const PassReport_base: Schema.Class<PassReport, {
211
+ id: Schema.Literal<["js", "dts", "loose", "exe", "meta"]>;
212
+ files: Schema.Array$<typeof EmittedFile>;
213
+ ms: typeof Schema.Number;
214
+ }, Schema.Struct.Encoded<{
215
+ id: Schema.Literal<["js", "dts", "loose", "exe", "meta"]>;
216
+ files: Schema.Array$<typeof EmittedFile>;
217
+ ms: typeof Schema.Number;
218
+ }>, never, {
219
+ readonly ms: number;
220
+ } & {
221
+ readonly id: "js" | "dts" | "loose" | "exe" | "meta";
222
+ } & {
223
+ readonly files: readonly EmittedFile[];
224
+ }, {}, {}>;
225
+ /** One build pass within a target group (js / dts / loose / exe / meta). */
226
+ declare class PassReport extends PassReport_base {}
227
+ declare const TargetGroupReport_base: Schema.Class<TargetGroupReport, {
228
+ id: typeof Schema.String;
229
+ entries: Schema.Array$<typeof Schema.String>;
230
+ passes: Schema.Array$<typeof PassReport>;
231
+ warnings: Schema.Array$<typeof DiagnosticEntry>;
232
+ errors: Schema.Array$<typeof DiagnosticEntry>;
233
+ timings: typeof ReportTimings;
234
+ }, Schema.Struct.Encoded<{
235
+ id: typeof Schema.String;
236
+ entries: Schema.Array$<typeof Schema.String>;
237
+ passes: Schema.Array$<typeof PassReport>;
238
+ warnings: Schema.Array$<typeof DiagnosticEntry>;
239
+ errors: Schema.Array$<typeof DiagnosticEntry>;
240
+ timings: typeof ReportTimings;
241
+ }>, never, {
242
+ readonly entries: readonly string[];
243
+ } & {
244
+ readonly id: string;
245
+ } & {
246
+ readonly timings: ReportTimings;
247
+ } & {
248
+ readonly passes: readonly PassReport[];
249
+ } & {
250
+ readonly warnings: readonly DiagnosticEntry[];
251
+ } & {
252
+ readonly errors: readonly DiagnosticEntry[];
253
+ }, {}, {}>;
254
+ declare class TargetGroupReport extends TargetGroupReport_base {}
255
+ declare const BuildReport_base: Schema.Class<BuildReport, {
256
+ package: typeof Schema.String;
257
+ targetGroups: Schema.Array$<typeof TargetGroupReport>;
258
+ }, Schema.Struct.Encoded<{
259
+ package: typeof Schema.String;
260
+ targetGroups: Schema.Array$<typeof TargetGroupReport>;
261
+ }>, never, {
262
+ readonly package: string;
263
+ } & {
264
+ readonly targetGroups: readonly TargetGroupReport[];
265
+ }, {}, {}>;
266
+ declare class BuildReport extends BuildReport_base {}
267
+ //#endregion
268
+ //#region src/report/collector.d.ts
269
+ type PassKind = PassReport["id"];
270
+ interface DiagnosticInput {
271
+ readonly source: DiagnosticEntry["source"];
272
+ readonly level: DiagnosticEntry["level"];
273
+ readonly text: string;
274
+ readonly file?: string;
275
+ readonly line?: number;
276
+ readonly column?: number;
277
+ }
278
+ /**
279
+ * Stateful build-event accumulator. The write surface is synchronous so it can be called directly
280
+ * from tsdown's customLogger and API Extractor's messageCallback (both invoked synchronously).
281
+ * `snapshot` builds the immutable BuildReport the Effect render pipeline consumes.
282
+ */
283
+ declare class BuildCollector {
284
+ private readonly groups;
285
+ private group;
286
+ private pass;
287
+ registerGroup(groupId: string, entries: ReadonlyArray<string>): void;
288
+ recordEmitted(groupId: string, pass: PassKind, file: EmittedFile): void;
289
+ recordPassTiming(groupId: string, pass: PassKind, ms: number): void;
290
+ recordWarning(groupId: string, entry: DiagnosticInput): void;
291
+ recordError(groupId: string, entry: DiagnosticInput): void;
292
+ snapshot(packageName: string): ReadonlyArray<BuildReport>;
293
+ }
294
+ declare const BuildCollectorTag_base: Context.TagClass<BuildCollectorTag, "@savvy-web/tsdown-plugins/BuildCollector", BuildCollector>;
295
+ declare class BuildCollectorTag extends BuildCollectorTag_base {}
296
+ //#endregion
155
297
  //#region src/build/target-groups.d.ts
156
298
  /** A build group id: "dev" or any prod byte-variant id (e.g. "npm", "github", a custom key). */
157
299
  type TargetGroupId = string;
@@ -437,6 +579,10 @@ interface BuildTargetGroupsOptions {
437
579
  readonly exeRewrite?: ExeRewrite | undefined;
438
580
  /** Injectable for tests; defaults to tsdown's build. */
439
581
  readonly build?: TsdownBuild;
582
+ /** When set, muzzle tsdown (silent + customLogger) and capture metrics/timing into this collector. */
583
+ readonly collector?: BuildCollector | undefined;
584
+ /** Compute gzip sizes for emitted files (verbose render). Forwarded to the metrics plugin. */
585
+ readonly verbose?: boolean | undefined;
440
586
  }
441
587
  /**
442
588
  * Run tsdown.build() per TargetGroup. Composable so the escape hatch gets multi-group too.
@@ -564,6 +710,24 @@ declare function syncPublicDir(sourceDir: string, targetDir: string): void;
564
710
  */
565
711
  declare function resolveManifest(pkg: ManifestLike$1): Promise<ManifestLike$1>;
566
712
  //#endregion
713
+ //#region src/changesets/next-versions.d.ts
714
+ /** Result of resolving next release versions for a workspace. */
715
+ interface NextVersions {
716
+ /** Monorepo root containing `.changeset/` (or `cwd` when no workspace was found). */
717
+ readonly root: string;
718
+ /** Canonical package name -> next release version (current version when unbumped). */
719
+ readonly versions: ReadonlyMap<string, string>;
720
+ }
721
+ /**
722
+ * Resolve the next release version of every workspace package from pending changesets.
723
+ *
724
+ * Walks up from `cwd` to the monorepo root via `@manypkg/get-packages`, seeds the map with
725
+ * each package's CURRENT version, then overlays `newVersion` for changeset-affected packages
726
+ * via `@changesets/get-release-plan`. Never rejects: any failure (not a workspace, missing
727
+ * `.changeset/config.json`, parse error) degrades to current versions (or an empty map).
728
+ */
729
+ declare function resolveNextVersions(cwd: string): Promise<NextVersions>;
730
+ //#endregion
567
731
  //#region src/errors.d.ts
568
732
  declare const MetaGenerationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
569
733
  readonly _tag: "MetaGenerationError";
@@ -662,20 +826,30 @@ interface TsdocOptions {
662
826
  }
663
827
  /** The `meta` field on defineBuild. Absent means no api-model generation. */
664
828
  interface MetaOptions {
665
- /** Directories to copy the api-model into on `savvy build --target meta`. */
829
+ /** Directories to copy the canonical group's api-model into after `savvy build --target prod`. */
666
830
  readonly localPaths?: ReadonlyArray<string> | undefined;
831
+ /**
832
+ * Forward-look the meta bundle's own `version` and workspace-sibling dep versions to their
833
+ * NEXT release version from pending changesets. `"auto"` (default) is `false` under CI
834
+ * (`CI`/`GITHUB_ACTIONS` set) and `true` locally, so a local bundle matches the CI release build.
835
+ */
836
+ readonly optimistic?: "auto" | boolean | undefined;
667
837
  readonly tsdoc?: TsdocOptions | undefined;
668
838
  }
669
839
  /** Fully-resolved meta options (no optionals). */
670
840
  interface NormalizedMeta {
671
841
  readonly localPaths: ReadonlyArray<string>;
842
+ readonly optimistic: boolean;
672
843
  readonly tsdoc: {
673
844
  readonly suppressWarnings: ReadonlyArray<WarningSuppressionRule>;
674
845
  readonly tagDefinitions: ReadonlyArray<TsdocTagDefinition>;
675
846
  };
676
847
  }
677
848
  /** Fill defaults so downstream code never branches on undefined. */
678
- declare function normalizeMetaOptions(meta: MetaOptions): NormalizedMeta;
849
+ declare function normalizeMetaOptions(meta: MetaOptions, env?: {
850
+ CI?: string | undefined;
851
+ GITHUB_ACTIONS?: string | undefined;
852
+ }): NormalizedMeta;
679
853
  //#endregion
680
854
  //#region src/targets/config.d.ts
681
855
  /** A single object-form publish target. Uses `from` XOR `name` (never both). */
@@ -817,6 +991,12 @@ interface RunExeBuildOptions {
817
991
  readonly specs: ReadonlyArray<NormalizedExe>;
818
992
  /** Injectable tsdown build (defaults to tsdown's build function). */
819
993
  readonly build?: ExeBuild | undefined;
994
+ /** When set with groupId, muzzle tsdown and record an "exe" pass into this collector. */
995
+ readonly collector?: BuildCollector | undefined;
996
+ /** Target-group id the exe pass belongs to (required to record into the collector). */
997
+ readonly groupId?: string | undefined;
998
+ /** Compute gzip sizes (verbose render). */
999
+ readonly verbose?: boolean | undefined;
820
1000
  }
821
1001
  /** Compile each SEA binary via tsdown's exe mode. One tsdown build per spec. */
822
1002
  declare function runExeBuild(options: RunExeBuildOptions): Promise<void>;
@@ -846,6 +1026,14 @@ interface GenerateMetaOptions {
846
1026
  /** Directories (relative to cwd) to copy the meta bundle into. */
847
1027
  readonly localPaths: ReadonlyArray<string>;
848
1028
  readonly tsdoc: NormalizedMeta["tsdoc"];
1029
+ /**
1030
+ * Optional transform applied to the bundle `package.json` (read from `dtsDir`) before it is
1031
+ * written to `outMetaDir` and copied into `localPaths`. Used for the optimistic next-version
1032
+ * rewrite. When omitted, the package.json is copied verbatim.
1033
+ */
1034
+ readonly manifestTransform?: ((pkg: Record<string, unknown>) => Record<string, unknown>) | undefined;
1035
+ /** When set, API Extractor warnings/errors are routed here (and suppressed from console). */
1036
+ readonly onMessage?: ((entry: DiagnosticInput) => void) | undefined;
849
1037
  }
850
1038
  interface MetaResult {
851
1039
  readonly apiJsonPath: string;
@@ -860,6 +1048,15 @@ interface MetaResult {
860
1048
  */
861
1049
  declare function generateMeta(options: GenerateMetaOptions): Promise<MetaResult>;
862
1050
  //#endregion
1051
+ //#region src/meta/optimistic.d.ts
1052
+ /**
1053
+ * Rewrite a meta `package.json` so the package's own `version` and any workspace-sibling
1054
+ * dependency version reflect their NEXT release version from `versions`. Pure: returns a new
1055
+ * object, never mutates the input. External/catalog-resolved deps (names absent from `versions`)
1056
+ * are left as-is.
1057
+ */
1058
+ declare function rewriteMetaVersions(pkg: Record<string, unknown>, versions: ReadonlyMap<string, string>, selfName: string): Record<string, unknown>;
1059
+ //#endregion
863
1060
  //#region src/meta/tsconfig-resolver.d.ts
864
1061
  /**
865
1062
  * Compiler options with enum values converted to their string equivalents.
@@ -969,36 +1166,6 @@ declare class TsconfigResolver {
969
1166
  */
970
1167
  declare function resolvePortableTsconfig(cwd: string, fallbackConfigPath?: string): PortableTsconfig;
971
1168
  //#endregion
972
- //#region src/report/schema.d.ts
973
- declare const ReportTimings: Schema.Struct<{
974
- totalMs: typeof Schema.Number;
975
- }>;
976
- declare const TargetGroupReport: Schema.Struct<{
977
- id: typeof Schema.String;
978
- entries: Schema.Array$<typeof Schema.String>;
979
- emittedFiles: Schema.Array$<typeof Schema.String>;
980
- timings: Schema.Struct<{
981
- totalMs: typeof Schema.Number;
982
- }>;
983
- warnings: Schema.Array$<typeof Schema.String>;
984
- errors: Schema.Array$<typeof Schema.String>;
985
- }>;
986
- declare const BuildReport: Schema.Struct<{
987
- package: typeof Schema.String;
988
- targetGroups: Schema.Array$<Schema.Struct<{
989
- id: typeof Schema.String;
990
- entries: Schema.Array$<typeof Schema.String>;
991
- emittedFiles: Schema.Array$<typeof Schema.String>;
992
- timings: Schema.Struct<{
993
- totalMs: typeof Schema.Number;
994
- }>;
995
- warnings: Schema.Array$<typeof Schema.String>;
996
- errors: Schema.Array$<typeof Schema.String>;
997
- }>>;
998
- }>;
999
- type BuildReport = typeof BuildReport.Type;
1000
- type TargetGroupReport = typeof TargetGroupReport.Type;
1001
- //#endregion
1002
1169
  //#region src/report/formatters/types.d.ts
1003
1170
  interface RenderedOutput {
1004
1171
  readonly target: "stdout" | "file" | "github-summary";
@@ -1007,6 +1174,7 @@ interface RenderedOutput {
1007
1174
  }
1008
1175
  interface FormatterContext {
1009
1176
  readonly noColor: boolean;
1177
+ readonly verbose: boolean;
1010
1178
  }
1011
1179
  interface Formatter {
1012
1180
  readonly format: string;
@@ -1067,6 +1235,16 @@ declare class OutputRenderer extends OutputRenderer_base {}
1067
1235
  //#region src/report/layers/OutputRendererLive.d.ts
1068
1236
  declare const OutputRendererLive: Layer.Layer<OutputRenderer, never, never>;
1069
1237
  //#endregion
1238
+ //#region src/report/metrics-plugin.d.ts
1239
+ /**
1240
+ * Rolldown plugin that records emitted-file metrics into the BuildCollector via writeBundle (which
1241
+ * fires for the JS pass AND the emitDtsOnly dts pass — verified against tsdown 0.22.3), plus a
1242
+ * defensive onLog for rolldown-level diagnostics that bypass tsdown's logger. Append it to each
1243
+ * build pass's `plugins` array. `bytes` is taken from the in-memory chunk/asset content (no fs);
1244
+ * `gzip` is computed only when `verbose`.
1245
+ */
1246
+ declare function buildMetricsPlugin(collector: BuildCollector, groupId: string, pass: PassKind, verbose: boolean): Plugin;
1247
+ //#endregion
1070
1248
  //#region src/report/pipeline.d.ts
1071
1249
  declare const ReportPipelineLive: Layer.Layer<EnvironmentDetector | ExecutorResolver | FormatSelector | OutputRenderer, never, never>;
1072
1250
  interface RenderReportOptions {
@@ -1074,6 +1252,7 @@ interface RenderReportOptions {
1074
1252
  /** Override env detection (mainly for tests). */
1075
1253
  readonly env?: Environment;
1076
1254
  readonly noColor: boolean;
1255
+ readonly verbose?: boolean;
1077
1256
  }
1078
1257
  declare const renderReport: (reports: ReadonlyArray<BuildReport>, options: RenderReportOptions) => Effect.Effect<ReadonlyArray<RenderedOutput>, never, EnvironmentDetector | ExecutorResolver | FormatSelector | OutputRenderer>;
1079
1258
  //#endregion
@@ -1568,6 +1747,25 @@ interface Timer {
1568
1747
  /** Create a wall-clock timer. (Date.now is fine in runtime build code.) */
1569
1748
  declare function createTimer(now?: () => number): Timer;
1570
1749
  //#endregion
1750
+ //#region src/report/tsdown-logger.d.ts
1751
+ /** Structural match for tsdown's Logger interface (tsdown 0.22.x). */
1752
+ interface TsdownLogger {
1753
+ level: "info";
1754
+ info: (...args: unknown[]) => void;
1755
+ warn: (...args: unknown[]) => void;
1756
+ warnOnce: (...args: unknown[]) => void;
1757
+ error: (...args: unknown[]) => void;
1758
+ success: (...args: unknown[]) => void;
1759
+ clearScreen: () => void;
1760
+ }
1761
+ /**
1762
+ * A tsdown `customLogger` that routes warnings/errors into the BuildCollector instead of the
1763
+ * console. Paired with `logLevel: "silent"` in the same build config: silent suppresses tsdown's
1764
+ * own console output while this logger still receives every message (verified against tsdown 0.22.3).
1765
+ * info/success are dropped — file metrics come from the writeBundle plugin and timing from our timer.
1766
+ */
1767
+ declare function createTsdownLogger(collector: BuildCollector, groupId: string): TsdownLogger;
1768
+ //#endregion
1571
1769
  //#region src/targets/binding.d.ts
1572
1770
  /** Write the target-to-group binding to dist/prod/targets.json for the release action to consume. Returns the path. */
1573
1771
  declare function writeTargetsBinding(cwd: string, resolution: TargetResolution): string;
@@ -1579,5 +1777,5 @@ declare function resolveTargets(options: {
1579
1777
  baseName: string;
1580
1778
  }): TargetResolution;
1581
1779
  //#endregion
1582
- export { type BuildEmittedManifestOptions, type BuildFormat, type BuildGroupSpec, type BuildPlatform, type BuildReport, BuildReport as BuildReportSchema, type BuildTargetGroupsOptions, CatalogAssemblyError, CatalogResolutionError, CiAnnotationsFormatter, ConfigValidationError, ConfigValidator, ConfigValidatorLive, type CssOptions, DEFAULT_EXE_NODE_VERSION, type DeriveOptions, type DerivedTsdownOptions, type DualExports, type EmitManifestOptions, type EntryOverride, type Environment, EnvironmentDetector, EnvironmentDetectorLive, type ExeBuild, type ExeConfig, type ExeRewrite, type ExeSeaConfig, type ExeTarget, type ExeTargetInput, type Executor, ExecutorResolver, ExecutorResolverLive, type ExtractOptions, type ExtractResult, FormatSelector, FormatSelectorLive, type Formatter, type FormatterContext, type GenerateMetaOptions, type Json, JsonFormatter, type JsxConfig, type LooseFileSpec, type LooseFiles, type ManifestLike, MarkdownFormatter, MetaGenerationError, type MetaOptions, type MetaResult, type NormalizedExe, type NormalizedLooseFile, type NormalizedMeta, type OutputFormat, OutputRenderer, OutputRendererLive, type PackageJsonEntriesOptions, type PackageJsonLike, type PkgOsCpu, type PortableTsconfig, type PublishTargetObject, type PublishTargetValue, type PublishTargets, type RenderReportOptions, type RenderedOutput, ReportPipelineLive, ReportTimings, type ResolvedCompilerOptions, type ResolvedGroup, type ResolvedTarget, type ResolvedTsconfig, type ResolvedTsconfigOptions, type RunExeBuildOptions, SilentFormatter, type TargetGroupId, type TargetGroupRef, type TargetGroupReport, TargetGroupReport as TargetGroupReportSchema, type TargetResolution, TerminalFormatter, type Timer, type TransformManifestOptions, type TsconfigJsx, TsconfigResolver, type TsdocOptions, type TsdocTagDefinition, type TsdownBuild, type ValidationInput, type WarningSuppressionRule, buildEmittedManifest, buildResolvedTsconfig, buildTargetGroups, cjsDefaultInterop, computeExeFileName, createEntryName, createTimer, defaultManifestTransform, deriveTargetGroupOptions, emitManifest, extractEntries, formatTime, generateBuildReportSchema, generateMeta, isTargetObject, nodeBuiltinDefaultInterop, normalizeBinPaths, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveManifest, resolvePortableTsconfig, resolveTargets, runExeBuild, syncPublicDir, transformBin, transformExports, transformManifest, writeResolvedTsconfig, writeTargetsBinding };
1780
+ export { BuildCollector, BuildCollectorTag, type BuildEmittedManifestOptions, type BuildFormat, type BuildGroupSpec, type BuildPlatform, type BuildReport, BuildReport as BuildReportSchema, type BuildTargetGroupsOptions, CatalogAssemblyError, CatalogResolutionError, CiAnnotationsFormatter, ConfigValidationError, ConfigValidator, ConfigValidatorLive, type CssOptions, DEFAULT_EXE_NODE_VERSION, type DeriveOptions, type DerivedTsdownOptions, type DiagnosticEntry, type DiagnosticInput, type DualExports, type EmitManifestOptions, type EmittedFile, type EntryOverride, type Environment, EnvironmentDetector, EnvironmentDetectorLive, type ExeBuild, type ExeConfig, type ExeRewrite, type ExeSeaConfig, type ExeTarget, type ExeTargetInput, type Executor, ExecutorResolver, ExecutorResolverLive, type ExtractOptions, type ExtractResult, FormatSelector, FormatSelectorLive, type Formatter, type FormatterContext, type GenerateMetaOptions, type Json, JsonFormatter, type JsxConfig, type LooseFileSpec, type LooseFiles, type ManifestLike, MarkdownFormatter, MetaGenerationError, type MetaOptions, type MetaResult, type NextVersions, type NormalizedExe, type NormalizedLooseFile, type NormalizedMeta, type OutputFormat, OutputRenderer, OutputRendererLive, type PackageJsonEntriesOptions, type PackageJsonLike, type PassKind, type PassReport, type PkgOsCpu, type PortableTsconfig, type PublishTargetObject, type PublishTargetValue, type PublishTargets, type RenderReportOptions, type RenderedOutput, ReportPipelineLive, ReportTimings, type ResolvedCompilerOptions, type ResolvedGroup, type ResolvedTarget, type ResolvedTsconfig, type ResolvedTsconfigOptions, type RunExeBuildOptions, SilentFormatter, type TargetGroupId, type TargetGroupRef, type TargetGroupReport, TargetGroupReport as TargetGroupReportSchema, type TargetResolution, TerminalFormatter, type Timer, type TransformManifestOptions, type TsconfigJsx, TsconfigResolver, type TsdocOptions, type TsdocTagDefinition, type TsdownBuild, type TsdownLogger, type ValidationInput, type WarningSuppressionRule, buildEmittedManifest, buildMetricsPlugin, buildResolvedTsconfig, buildTargetGroups, cjsDefaultInterop, computeExeFileName, createEntryName, createTimer, createTsdownLogger, defaultManifestTransform, deriveTargetGroupOptions, emitManifest, extractEntries, formatTime, generateBuildReportSchema, generateMeta, isTargetObject, nodeBuiltinDefaultInterop, normalizeBinPaths, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveManifest, resolveNextVersions, resolvePortableTsconfig, resolveTargets, rewriteMetaVersions, runExeBuild, syncPublicDir, transformBin, transformExports, transformManifest, writeResolvedTsconfig, writeTargetsBinding };
1583
1781
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -2,6 +2,9 @@ import { resolveManifest } from "./catalog/resolve-catalogs.js";
2
2
  import { createEntryName, extractEntries } from "./entry/extract.js";
3
3
  import { defaultManifestTransform, normalizeBinPaths, transformBin, transformExports, transformManifest } from "./manifest/transform.js";
4
4
  import { buildEmittedManifest, emitManifest } from "./manifest/emit-manifest.js";
5
+ import { buildMetricsPlugin } from "./report/metrics-plugin.js";
6
+ import { createTimer, formatTime } from "./report/timer.js";
7
+ import { createTsdownLogger } from "./report/tsdown-logger.js";
5
8
  import { cjsDefaultInterop } from "./build/cjs-default-interop.js";
6
9
  import { nodeBuiltinDefaultInterop } from "./build/node-builtin-default-interop.js";
7
10
  import { syncPublicDir } from "./build/sync-public.js";
@@ -10,6 +13,7 @@ import { buildTargetGroups } from "./build/build-target-groups.js";
10
13
  import { ConfigValidationError, MetaGenerationError } from "./errors.js";
11
14
  import { normalizeLooseFiles } from "./build/loose-files.js";
12
15
  import { removeDeclarationMaps } from "./build/strip-maps.js";
16
+ import { resolveNextVersions } from "./changesets/next-versions.js";
13
17
  import { ConfigValidator } from "./config-validation/ConfigValidator.js";
14
18
  import { DEFAULT_EXE_NODE_VERSION, normalizeExeOptions } from "./exe/config.js";
15
19
  import { isTargetObject } from "./targets/config.js";
@@ -23,11 +27,13 @@ import { readTsconfigJsx, resolveJsxConfig } from "./jsx/config.js";
23
27
  import { normalizeMetaOptions } from "./meta/config.js";
24
28
  import { TsconfigResolver, resolvePortableTsconfig } from "./meta/tsconfig-resolver.js";
25
29
  import { generateMeta } from "./meta/generate.js";
30
+ import { rewriteMetaVersions } from "./meta/optimistic.js";
31
+ import { BuildReport, ReportTimings, TargetGroupReport } from "./report/schema.js";
32
+ import { BuildCollector, BuildCollectorTag } from "./report/collector.js";
26
33
  import { CiAnnotationsFormatter } from "./report/formatters/ci-annotations.js";
27
34
  import { JsonFormatter } from "./report/formatters/json.js";
28
35
  import { MarkdownFormatter } from "./report/formatters/markdown.js";
29
36
  import { SilentFormatter } from "./report/formatters/silent.js";
30
- import { createTimer, formatTime } from "./report/timer.js";
31
37
  import { TerminalFormatter } from "./report/formatters/terminal.js";
32
38
  import { EnvironmentDetector } from "./report/services/EnvironmentDetector.js";
33
39
  import { EnvironmentDetectorLive } from "./report/layers/EnvironmentDetectorLive.js";
@@ -38,9 +44,8 @@ import { FormatSelectorLive } from "./report/layers/FormatSelectorLive.js";
38
44
  import { OutputRenderer } from "./report/services/OutputRenderer.js";
39
45
  import { OutputRendererLive } from "./report/layers/OutputRendererLive.js";
40
46
  import { ReportPipelineLive, renderReport } from "./report/pipeline.js";
41
- import { BuildReport, ReportTimings, TargetGroupReport } from "./report/schema.js";
42
47
  import { generateBuildReportSchema } from "./report/schema-export.js";
43
48
  import { writeTargetsBinding } from "./targets/binding.js";
44
49
  import { CatalogAssemblyError, CatalogResolutionError } from "workspaces-effect";
45
50
 
46
- export { BuildReport as BuildReportSchema, CatalogAssemblyError, CatalogResolutionError, CiAnnotationsFormatter, ConfigValidationError, ConfigValidator, ConfigValidatorLive, DEFAULT_EXE_NODE_VERSION, EnvironmentDetector, EnvironmentDetectorLive, ExecutorResolver, ExecutorResolverLive, FormatSelector, FormatSelectorLive, JsonFormatter, MarkdownFormatter, MetaGenerationError, OutputRenderer, OutputRendererLive, ReportPipelineLive, ReportTimings, SilentFormatter, TargetGroupReport as TargetGroupReportSchema, TerminalFormatter, TsconfigResolver, buildEmittedManifest, buildResolvedTsconfig, buildTargetGroups, cjsDefaultInterop, computeExeFileName, createEntryName, createTimer, defaultManifestTransform, deriveTargetGroupOptions, emitManifest, extractEntries, formatTime, generateBuildReportSchema, generateMeta, isTargetObject, nodeBuiltinDefaultInterop, normalizeBinPaths, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveManifest, resolvePortableTsconfig, resolveTargets, runExeBuild, syncPublicDir, transformBin, transformExports, transformManifest, writeResolvedTsconfig, writeTargetsBinding };
51
+ export { BuildCollector, BuildCollectorTag, BuildReport as BuildReportSchema, CatalogAssemblyError, CatalogResolutionError, CiAnnotationsFormatter, ConfigValidationError, ConfigValidator, ConfigValidatorLive, DEFAULT_EXE_NODE_VERSION, EnvironmentDetector, EnvironmentDetectorLive, ExecutorResolver, ExecutorResolverLive, FormatSelector, FormatSelectorLive, JsonFormatter, MarkdownFormatter, MetaGenerationError, OutputRenderer, OutputRendererLive, ReportPipelineLive, ReportTimings, SilentFormatter, TargetGroupReport as TargetGroupReportSchema, TerminalFormatter, TsconfigResolver, buildEmittedManifest, buildMetricsPlugin, buildResolvedTsconfig, buildTargetGroups, cjsDefaultInterop, computeExeFileName, createEntryName, createTimer, createTsdownLogger, defaultManifestTransform, deriveTargetGroupOptions, emitManifest, extractEntries, formatTime, generateBuildReportSchema, generateMeta, isTargetObject, nodeBuiltinDefaultInterop, normalizeBinPaths, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveManifest, resolveNextVersions, resolvePortableTsconfig, resolveTargets, rewriteMetaVersions, runExeBuild, syncPublicDir, transformBin, transformExports, transformManifest, writeResolvedTsconfig, writeTargetsBinding };
@@ -7,6 +7,20 @@ import { TSDocConfigFile } from "@microsoft/tsdoc-config";
7
7
 
8
8
  //#region src/meta/api-extractor.ts
9
9
  const require_ = createRequire(import.meta.url);
10
+ /** Map an API Extractor message to a collector DiagnosticInput, or undefined if not warn/error. */
11
+ function mapExtractorMessage(message) {
12
+ const isError = message.logLevel === ExtractorLogLevel.Error;
13
+ const isWarning = message.logLevel === ExtractorLogLevel.Warning;
14
+ if (!isError && !isWarning) return void 0;
15
+ return {
16
+ source: "api-extractor",
17
+ level: isError ? "error" : "warn",
18
+ text: message.text,
19
+ ...message.sourceFilePath !== void 0 ? { file: message.sourceFilePath } : {},
20
+ ...message.sourceFileLine !== void 0 ? { line: message.sourceFileLine } : {},
21
+ ...message.sourceFileColumn !== void 0 ? { column: message.sourceFileColumn } : {}
22
+ };
23
+ }
10
24
  /** Run API Extractor over a single entry's .d.ts, writing the .api.json (and optionally tsdoc-metadata.json). Throws on failure. */
11
25
  function runApiExtractor(options) {
12
26
  const suppressor = createMessageSuppressor(options.suppressWarnings);
@@ -47,6 +61,13 @@ function runApiExtractor(options) {
47
61
  message.logLevel = ExtractorLogLevel.None;
48
62
  message.handled = true;
49
63
  }
64
+ if (options.onMessage !== void 0) {
65
+ const entry = mapExtractorMessage(message);
66
+ if (entry !== void 0) {
67
+ options.onMessage(entry);
68
+ message.handled = true;
69
+ }
70
+ }
50
71
  }
51
72
  });
52
73
  if (!result.succeeded) throw new MetaGenerationError({
package/meta/config.js CHANGED
@@ -1,8 +1,14 @@
1
1
  //#region src/meta/config.ts
2
+ /** Resolve `"auto"` against the environment; explicit booleans pass through. */
3
+ function resolveOptimistic(value, env) {
4
+ if (value === true || value === false) return value;
5
+ return !(env.CI || env.GITHUB_ACTIONS);
6
+ }
2
7
  /** Fill defaults so downstream code never branches on undefined. */
3
- function normalizeMetaOptions(meta) {
8
+ function normalizeMetaOptions(meta, env = process.env) {
4
9
  return {
5
10
  localPaths: meta.localPaths ?? [],
11
+ optimistic: resolveOptimistic(meta.optimistic, env),
6
12
  tsdoc: {
7
13
  suppressWarnings: meta.tsdoc?.suppressWarnings ?? [],
8
14
  tagDefinitions: meta.tsdoc?.tagDefinitions ?? []
package/meta/generate.js CHANGED
@@ -18,7 +18,7 @@ function unscopedName(name) {
18
18
  * published-package artifact and is written into `dtsDir` (the built pkg/), not the meta bundle.
19
19
  */
20
20
  async function generateMeta(options) {
21
- const { cwd, packageName, tsconfigPath, dtsDir, entries, exportPaths, outMetaDir, localPaths, tsdoc } = options;
21
+ const { cwd, packageName, tsconfigPath, dtsDir, entries, exportPaths, outMetaDir, localPaths, tsdoc, manifestTransform, onMessage } = options;
22
22
  const tsdocConfigPath = writeTsdocConfig(cwd, tsdoc);
23
23
  const packageJsonPath = join(cwd, "package.json");
24
24
  mkdirSync(outMetaDir, { recursive: true });
@@ -41,7 +41,8 @@ async function generateMeta(options) {
41
41
  tsdocConfigPath,
42
42
  apiJsonPath: perEntryApiJson,
43
43
  ...isMain && !mainEntryDidTsdocMetadata ? { tsdocMetadataPath } : {},
44
- suppressWarnings: tsdoc.suppressWarnings
44
+ suppressWarnings: tsdoc.suppressWarnings,
45
+ ...onMessage !== void 0 ? { onMessage } : {}
45
46
  });
46
47
  if (isMain) mainEntryDidTsdocMetadata = true;
47
48
  perEntryModels.set(entryName, JSON.parse(readFileSync(perEntryApiJson, "utf-8")));
@@ -55,7 +56,9 @@ async function generateMeta(options) {
55
56
  writeFileSync(apiJsonPath, `${JSON.stringify(finalModel, null, 2)}\n`, "utf-8");
56
57
  for (const intermediate of intermediateApiJsons) rmSync(intermediate, { force: true });
57
58
  const bundlePackageJson = join(outMetaDir, "package.json");
58
- copyFileSync(join(dtsDir, "package.json"), bundlePackageJson);
59
+ const builtPkg = JSON.parse(readFileSync(join(dtsDir, "package.json"), "utf-8"));
60
+ const finalPkg = manifestTransform ? manifestTransform(builtPkg) : builtPkg;
61
+ writeFileSync(bundlePackageJson, `${JSON.stringify(finalPkg, null, 2)}\n`, "utf-8");
59
62
  const bundleTsconfig = join(outMetaDir, "tsconfig.json");
60
63
  const portableTsconfig = resolvePortableTsconfig(cwd, tsconfigPath);
61
64
  writeFileSync(bundleTsconfig, `${JSON.stringify(portableTsconfig, null, 2)}\n`, "utf-8");
@@ -0,0 +1,32 @@
1
+ //#region src/meta/optimistic.ts
2
+ /** Dependency map fields whose workspace-sibling versions get the optimistic bump. */
3
+ const DEP_FIELDS = [
4
+ "dependencies",
5
+ "peerDependencies",
6
+ "optionalDependencies"
7
+ ];
8
+ /**
9
+ * Rewrite a meta `package.json` so the package's own `version` and any workspace-sibling
10
+ * dependency version reflect their NEXT release version from `versions`. Pure: returns a new
11
+ * object, never mutates the input. External/catalog-resolved deps (names absent from `versions`)
12
+ * are left as-is.
13
+ */
14
+ function rewriteMetaVersions(pkg, versions, selfName) {
15
+ const out = { ...pkg };
16
+ const selfNext = versions.get(selfName);
17
+ if (selfNext !== void 0) out.version = selfNext;
18
+ for (const field of DEP_FIELDS) {
19
+ const deps = pkg[field];
20
+ if (deps === null || typeof deps !== "object") continue;
21
+ const next = { ...deps };
22
+ for (const depName of Object.keys(next)) {
23
+ const v = versions.get(depName);
24
+ if (v !== void 0) next[depName] = v;
25
+ }
26
+ out[field] = next;
27
+ }
28
+ return out;
29
+ }
30
+
31
+ //#endregion
32
+ export { rewriteMetaVersions };
@@ -85,7 +85,7 @@ const PRESERVED_STRING_OPTIONS = [
85
85
  */
86
86
  var TsconfigResolver = class TsconfigResolver {
87
87
  /** @internal */
88
- static SCRIPT_TARGET_MAP = new Map([
88
+ static SCRIPT_TARGET_MAP = /* @__PURE__ */ new Map([
89
89
  [ScriptTarget.ES5, "es5"],
90
90
  [ScriptTarget.ES2015, "es2015"],
91
91
  [ScriptTarget.ES2016, "es2016"],
@@ -102,7 +102,7 @@ var TsconfigResolver = class TsconfigResolver {
102
102
  [ScriptTarget.JSON, "json"]
103
103
  ]);
104
104
  /** @internal */
105
- static MODULE_KIND_MAP = new Map([
105
+ static MODULE_KIND_MAP = /* @__PURE__ */ new Map([
106
106
  [ModuleKind.CommonJS, "commonjs"],
107
107
  [ModuleKind.ES2015, "es2015"],
108
108
  [ModuleKind.ES2020, "es2020"],
@@ -115,14 +115,14 @@ var TsconfigResolver = class TsconfigResolver {
115
115
  [ModuleKind.Preserve, "preserve"]
116
116
  ]);
117
117
  /** @internal */
118
- static MODULE_RESOLUTION_MAP = new Map([
118
+ static MODULE_RESOLUTION_MAP = /* @__PURE__ */ new Map([
119
119
  [ModuleResolutionKind.Node10, "node10"],
120
120
  [ModuleResolutionKind.Node16, "node16"],
121
121
  [ModuleResolutionKind.NodeNext, "nodenext"],
122
122
  [ModuleResolutionKind.Bundler, "bundler"]
123
123
  ]);
124
124
  /** @internal */
125
- static JSX_EMIT_MAP = new Map([
125
+ static JSX_EMIT_MAP = /* @__PURE__ */ new Map([
126
126
  [JsxEmit.None, "none"],
127
127
  [JsxEmit.Preserve, "preserve"],
128
128
  [JsxEmit.React, "react"],
@@ -131,13 +131,13 @@ var TsconfigResolver = class TsconfigResolver {
131
131
  [JsxEmit.ReactJSXDev, "react-jsxdev"]
132
132
  ]);
133
133
  /** @internal */
134
- static MODULE_DETECTION_MAP = new Map([
134
+ static MODULE_DETECTION_MAP = /* @__PURE__ */ new Map([
135
135
  [ModuleDetectionKind.Legacy, "legacy"],
136
136
  [ModuleDetectionKind.Auto, "auto"],
137
137
  [ModuleDetectionKind.Force, "force"]
138
138
  ]);
139
139
  /** @internal */
140
- static NEW_LINE_MAP = new Map([[NewLineKind.CarriageReturnLineFeed, "crlf"], [NewLineKind.LineFeed, "lf"]]);
140
+ static NEW_LINE_MAP = /* @__PURE__ */ new Map([[NewLineKind.CarriageReturnLineFeed, "crlf"], [NewLineKind.LineFeed, "lf"]]);
141
141
  /** Converts a {@link ScriptTarget} enum value to its string form (e.g. `es2023`). */
142
142
  static convertScriptTarget(target) {
143
143
  if (target === void 0) return void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/tsdown-plugins",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
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",
@@ -28,7 +28,9 @@
28
28
  "./package.json": "./package.json"
29
29
  },
30
30
  "dependencies": {
31
+ "@changesets/get-release-plan": "^4.0.16",
31
32
  "@effect/platform-node": "^0.107.0",
33
+ "@manypkg/get-packages": "^1.1.3",
32
34
  "@microsoft/api-extractor": "^7.58.9",
33
35
  "@microsoft/tsdoc": "^0.16.0",
34
36
  "@microsoft/tsdoc-config": "^0.18.1",
@@ -0,0 +1,126 @@
1
+ import { BuildReport, DiagnosticEntry, EmittedFile, PassReport, ReportTimings, TargetGroupReport } from "./schema.js";
2
+ import { Context } from "effect";
3
+
4
+ //#region src/report/collector.ts
5
+ /**
6
+ * Stateful build-event accumulator. The write surface is synchronous so it can be called directly
7
+ * from tsdown's customLogger and API Extractor's messageCallback (both invoked synchronously).
8
+ * `snapshot` builds the immutable BuildReport the Effect render pipeline consumes.
9
+ */
10
+ var BuildCollector = class {
11
+ groups = /* @__PURE__ */ new Map();
12
+ group(groupId) {
13
+ let g = this.groups.get(groupId);
14
+ if (g === void 0) {
15
+ g = {
16
+ id: groupId,
17
+ entries: [],
18
+ passes: /* @__PURE__ */ new Map(),
19
+ warnings: [],
20
+ errors: [],
21
+ seenPaths: /* @__PURE__ */ new Set(),
22
+ seenDiagnostics: /* @__PURE__ */ new Set()
23
+ };
24
+ this.groups.set(groupId, g);
25
+ }
26
+ return g;
27
+ }
28
+ pass(groupId, pass) {
29
+ const g = this.group(groupId);
30
+ let p = g.passes.get(pass);
31
+ if (p === void 0) {
32
+ p = {
33
+ files: [],
34
+ ms: 0
35
+ };
36
+ g.passes.set(pass, p);
37
+ }
38
+ return p;
39
+ }
40
+ registerGroup(groupId, entries) {
41
+ this.group(groupId).entries = [...entries];
42
+ }
43
+ recordEmitted(groupId, pass, file) {
44
+ const g = this.group(groupId);
45
+ if (g.seenPaths.has(file.path)) return;
46
+ g.seenPaths.add(file.path);
47
+ this.pass(groupId, pass).files.push({
48
+ path: file.path,
49
+ bytes: file.bytes,
50
+ ...file.gzip !== void 0 ? { gzip: file.gzip } : {}
51
+ });
52
+ }
53
+ recordPassTiming(groupId, pass, ms) {
54
+ this.pass(groupId, pass).ms += ms;
55
+ }
56
+ recordWarning(groupId, entry) {
57
+ const g = this.group(groupId);
58
+ const key = diagnosticKey(entry);
59
+ if (g.seenDiagnostics.has(key)) return;
60
+ g.seenDiagnostics.add(key);
61
+ g.warnings.push(toEntry(entry));
62
+ }
63
+ recordError(groupId, entry) {
64
+ const g = this.group(groupId);
65
+ const key = diagnosticKey(entry);
66
+ if (g.seenDiagnostics.has(key)) return;
67
+ g.seenDiagnostics.add(key);
68
+ g.errors.push(toEntry(entry));
69
+ }
70
+ snapshot(packageName) {
71
+ const targetGroups = [];
72
+ for (const g of this.groups.values()) {
73
+ const passes = [];
74
+ let totalMs = 0;
75
+ for (const [id, p] of g.passes) {
76
+ const files = p.files.map((f) => new EmittedFile({
77
+ path: f.path,
78
+ bytes: f.bytes,
79
+ ...f.gzip !== void 0 ? { gzip: f.gzip } : {}
80
+ }));
81
+ passes.push(new PassReport({
82
+ id,
83
+ files,
84
+ ms: p.ms
85
+ }));
86
+ totalMs += p.ms;
87
+ }
88
+ targetGroups.push(new TargetGroupReport({
89
+ id: g.id,
90
+ entries: [...g.entries],
91
+ passes,
92
+ warnings: [...g.warnings],
93
+ errors: [...g.errors],
94
+ timings: new ReportTimings({ totalMs })
95
+ }));
96
+ }
97
+ return [new BuildReport({
98
+ package: packageName,
99
+ targetGroups
100
+ })];
101
+ }
102
+ };
103
+ function diagnosticKey(input) {
104
+ return [
105
+ input.source,
106
+ input.level,
107
+ input.text,
108
+ input.file ?? "",
109
+ String(input.line ?? ""),
110
+ String(input.column ?? "")
111
+ ].join("\0");
112
+ }
113
+ function toEntry(input) {
114
+ return new DiagnosticEntry({
115
+ source: input.source,
116
+ level: input.level,
117
+ text: input.text,
118
+ ...input.file !== void 0 ? { file: input.file } : {},
119
+ ...input.line !== void 0 ? { line: input.line } : {},
120
+ ...input.column !== void 0 ? { column: input.column } : {}
121
+ });
122
+ }
123
+ var BuildCollectorTag = class extends Context.Tag("@savvy-web/tsdown-plugins/BuildCollector")() {};
124
+
125
+ //#endregion
126
+ export { BuildCollector, BuildCollectorTag };
@@ -5,8 +5,14 @@ const CiAnnotationsFormatter = {
5
5
  render: (reports) => {
6
6
  const lines = [];
7
7
  for (const r of reports) for (const g of r.targetGroups) {
8
- for (const e of g.errors) lines.push(`::error title=${esc(r.package)} (${esc(g.id)})::${esc(e)}`);
9
- for (const w of g.warnings) lines.push(`::warning title=${esc(r.package)} (${esc(g.id)})::${esc(w)}`);
8
+ for (const e of g.errors) {
9
+ const loc = e.file !== void 0 ? ` file=${esc(e.file)}${e.line !== void 0 ? `,line=${e.line}` : ""}` : "";
10
+ lines.push(`::error title=${esc(r.package)} (${esc(g.id)})${loc}::${esc(e.text)}`);
11
+ }
12
+ for (const w of g.warnings) {
13
+ const loc = w.file !== void 0 ? ` file=${esc(w.file)}${w.line !== void 0 ? `,line=${w.line}` : ""}` : "";
14
+ lines.push(`::warning title=${esc(r.package)} (${esc(g.id)})${loc}::${esc(w.text)}`);
15
+ }
10
16
  }
11
17
  return lines.length === 0 ? [] : [{
12
18
  target: "stdout",
@@ -9,7 +9,7 @@ const MarkdownFormatter = {
9
9
  lines.push(`## ❌ ${r.package}`);
10
10
  for (const g of failing) {
11
11
  lines.push(`- **${g.id}**`);
12
- for (const e of g.errors) lines.push(` - ${e}`);
12
+ for (const e of g.errors) lines.push(` - ${e.text}`);
13
13
  }
14
14
  } else lines.push(`## ✅ ${r.package}`);
15
15
  }
@@ -2,20 +2,40 @@ import { formatTime } from "../timer.js";
2
2
  import pc from "picocolors";
3
3
 
4
4
  //#region src/report/formatters/terminal.ts
5
+ const fmtBytes = (n) => n < 1024 ? `${n} B` : `${(n / 1024).toFixed(2)} kB`;
6
+ const fileCount = (g) => g.passes.reduce((sum, p) => sum + p.files.length, 0);
7
+ const diagLine = (d) => {
8
+ return `${d.file !== void 0 ? ` ${d.file}${d.line !== void 0 ? `:${d.line}` : ""}` : ""} ${d.text}`.trim();
9
+ };
5
10
  const TerminalFormatter = {
6
11
  format: "terminal",
7
12
  render: (reports, ctx) => {
8
13
  const color = (fn, s) => ctx.noColor ? s : fn(s);
9
14
  const lines = [];
15
+ let totalMs = 0;
10
16
  for (const r of reports) {
11
17
  lines.push(color(pc.bold, r.package));
12
18
  for (const g of r.targetGroups) {
13
19
  const status = g.errors.length ? color(pc.red, "✗") : color(pc.green, "✓");
14
- lines.push(` ${status} ${g.id}${g.emittedFiles.length} files (${formatTime(g.timings.totalMs)})`);
15
- for (const e of g.errors) lines.push(` ${color(pc.red, "error")}: ${e}`);
16
- for (const w of g.warnings) lines.push(` ${color(pc.yellow, "warn")}: ${w}`);
20
+ lines.push(` ${status} ${g.id} ${fileCount(g)} files · ${formatTime(g.timings.totalMs)}`);
21
+ totalMs += g.timings.totalMs;
22
+ if (ctx.verbose) for (const p of g.passes) {
23
+ lines.push(` ${color(pc.dim, `${p.id} (${formatTime(p.ms)})`)}`);
24
+ for (const f of p.files) {
25
+ const gz = f.gzip !== void 0 ? ` │ gzip ${fmtBytes(f.gzip)}` : "";
26
+ lines.push(` ${f.path} ${fmtBytes(f.bytes)}${gz}`);
27
+ }
28
+ }
29
+ for (const e of g.errors) lines.push(` ${color(pc.red, "error")} ${diagLine(e)}`);
30
+ for (const w of g.warnings) lines.push(` ${color(pc.yellow, "warn")} ${diagLine(w)}`);
17
31
  }
18
32
  }
33
+ const pkgs = reports.length;
34
+ if (pkgs > 0) {
35
+ const pkgLabel = `${pkgs} package${pkgs === 1 ? "" : "s"}`;
36
+ const hasErrors = reports.some((r) => r.targetGroups.some((g) => g.errors.length > 0));
37
+ lines.push(hasErrors ? `${color(pc.red, "✗")} build failed · ${pkgLabel} · ${formatTime(totalMs)}` : `${color(pc.green, "✔")} build complete · ${pkgLabel} · ${formatTime(totalMs)}`);
38
+ }
19
39
  const content = lines.join("\n");
20
40
  return content === "" ? [] : [{
21
41
  target: "stdout",
@@ -7,7 +7,7 @@ import { OutputRenderer } from "../services/OutputRenderer.js";
7
7
  import { Effect, Layer } from "effect";
8
8
 
9
9
  //#region src/report/layers/OutputRendererLive.ts
10
- const formatters = new Map([
10
+ const formatters = /* @__PURE__ */ new Map([
11
11
  ["terminal", TerminalFormatter],
12
12
  ["json", JsonFormatter],
13
13
  ["markdown", MarkdownFormatter],
@@ -0,0 +1,62 @@
1
+ import { gzipSync } from "node:zlib";
2
+
3
+ //#region src/report/metrics-plugin.ts
4
+ function contentOf(chunk) {
5
+ const c = chunk;
6
+ if (c.type === "chunk") return c.code;
7
+ if (c.type === "asset") return c.source;
8
+ return c.code ?? c.source;
9
+ }
10
+ function byteLength(content) {
11
+ return typeof content === "string" ? Buffer.byteLength(content) : content.byteLength;
12
+ }
13
+ /**
14
+ * Rolldown plugin that records emitted-file metrics into the BuildCollector via writeBundle (which
15
+ * fires for the JS pass AND the emitDtsOnly dts pass — verified against tsdown 0.22.3), plus a
16
+ * defensive onLog for rolldown-level diagnostics that bypass tsdown's logger. Append it to each
17
+ * build pass's `plugins` array. `bytes` is taken from the in-memory chunk/asset content (no fs);
18
+ * `gzip` is computed only when `verbose`.
19
+ */
20
+ function buildMetricsPlugin(collector, groupId, pass, verbose) {
21
+ return {
22
+ name: "savvy:build-metrics",
23
+ writeBundle(_outputOptions, bundle) {
24
+ for (const [key, chunk] of Object.entries(bundle)) {
25
+ const content = contentOf(chunk);
26
+ if (content === void 0) continue;
27
+ const bytes = byteLength(content);
28
+ collector.recordEmitted(groupId, pass, {
29
+ path: key,
30
+ bytes,
31
+ ...verbose ? { gzip: gzipSync(typeof content === "string" ? Buffer.from(content) : content).length } : {}
32
+ });
33
+ }
34
+ },
35
+ onLog(level, log) {
36
+ const l = log;
37
+ const entry = {
38
+ text: l.message ?? String(log),
39
+ ...l.id !== void 0 ? { file: l.id } : {},
40
+ ...l.loc?.line !== void 0 ? { line: l.loc.line } : {},
41
+ ...l.loc?.column !== void 0 ? { column: l.loc.column } : {}
42
+ };
43
+ if (level === "error") {
44
+ collector.recordError(groupId, {
45
+ source: "rolldown",
46
+ level: "error",
47
+ ...entry
48
+ });
49
+ return;
50
+ }
51
+ if (level === "warn") collector.recordWarning(groupId, {
52
+ source: "rolldown",
53
+ level: "warn",
54
+ ...entry
55
+ });
56
+ return false;
57
+ }
58
+ };
59
+ }
60
+
61
+ //#endregion
62
+ export { buildMetricsPlugin };
@@ -18,7 +18,10 @@ const renderReport = (reports, options) => Effect.gen(function* () {
18
18
  const env = options.env ?? (yield* detector.detect());
19
19
  const executor = yield* executorResolver.resolve(env);
20
20
  const format = yield* formatSelector.select(executor, options.explicitFormat, env);
21
- return yield* renderer.render(reports, format, { noColor: options.noColor });
21
+ return yield* renderer.render(reports, format, {
22
+ noColor: options.noColor,
23
+ verbose: options.verbose ?? false
24
+ });
22
25
  });
23
26
 
24
27
  //#endregion
package/report/schema.js CHANGED
@@ -1,19 +1,40 @@
1
1
  import { Schema } from "effect";
2
2
 
3
3
  //#region src/report/schema.ts
4
- const ReportTimings = Schema.Struct({ totalMs: Schema.Number }).annotations({ identifier: "ReportTimings" });
5
- const TargetGroupReport = Schema.Struct({
4
+ var ReportTimings = class extends Schema.Class("ReportTimings")({ totalMs: Schema.Number }) {};
5
+ /** A captured warning or error, from tsdown's logger, rolldown's onLog, or API Extractor. */
6
+ var DiagnosticEntry = class extends Schema.Class("DiagnosticEntry")({
7
+ source: Schema.Literal("tsdown", "rolldown", "api-extractor"),
8
+ level: Schema.Literal("warn", "error"),
9
+ text: Schema.String,
10
+ file: Schema.optional(Schema.String),
11
+ line: Schema.optional(Schema.Number),
12
+ column: Schema.optional(Schema.Number)
13
+ }) {};
14
+ /** One emitted output file with its in-memory byte size (gzip only when --verbose). */
15
+ var EmittedFile = class extends Schema.Class("EmittedFile")({
16
+ path: Schema.String,
17
+ bytes: Schema.Number,
18
+ gzip: Schema.optional(Schema.Number)
19
+ }) {};
20
+ /** One build pass within a target group (js / dts / loose / exe / meta). */
21
+ var PassReport = class extends Schema.Class("PassReport")({
22
+ id: Schema.Literal("js", "dts", "loose", "exe", "meta"),
23
+ files: Schema.Array(EmittedFile),
24
+ ms: Schema.Number
25
+ }) {};
26
+ var TargetGroupReport = class extends Schema.Class("TargetGroupReport")({
6
27
  id: Schema.String,
7
28
  entries: Schema.Array(Schema.String),
8
- emittedFiles: Schema.Array(Schema.String),
9
- timings: ReportTimings,
10
- warnings: Schema.Array(Schema.String),
11
- errors: Schema.Array(Schema.String)
12
- }).annotations({ identifier: "TargetGroupReport" });
13
- const BuildReport = Schema.Struct({
29
+ passes: Schema.Array(PassReport),
30
+ warnings: Schema.Array(DiagnosticEntry),
31
+ errors: Schema.Array(DiagnosticEntry),
32
+ timings: ReportTimings
33
+ }) {};
34
+ var BuildReport = class extends Schema.Class("BuildReport")({
14
35
  package: Schema.String,
15
36
  targetGroups: Schema.Array(TargetGroupReport)
16
- }).annotations({ identifier: "BuildReport" });
37
+ }) {};
17
38
 
18
39
  //#endregion
19
- export { BuildReport, ReportTimings, TargetGroupReport };
40
+ export { BuildReport, DiagnosticEntry, EmittedFile, PassReport, ReportTimings, TargetGroupReport };
@@ -0,0 +1,41 @@
1
+ //#region src/report/tsdown-logger.ts
2
+ const ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
3
+ const join = (args) => args.map((a) => String(a)).join(" ").replace(ANSI, "").trim();
4
+ /**
5
+ * A tsdown `customLogger` that routes warnings/errors into the BuildCollector instead of the
6
+ * console. Paired with `logLevel: "silent"` in the same build config: silent suppresses tsdown's
7
+ * own console output while this logger still receives every message (verified against tsdown 0.22.3).
8
+ * info/success are dropped — file metrics come from the writeBundle plugin and timing from our timer.
9
+ */
10
+ function createTsdownLogger(collector, groupId) {
11
+ const seenOnce = /* @__PURE__ */ new Set();
12
+ return {
13
+ level: "info",
14
+ info: () => {},
15
+ success: () => {},
16
+ clearScreen: () => {},
17
+ warn: (...args) => collector.recordWarning(groupId, {
18
+ source: "tsdown",
19
+ level: "warn",
20
+ text: join(args)
21
+ }),
22
+ warnOnce: (...args) => {
23
+ const text = join(args);
24
+ if (seenOnce.has(text)) return;
25
+ seenOnce.add(text);
26
+ collector.recordWarning(groupId, {
27
+ source: "tsdown",
28
+ level: "warn",
29
+ text
30
+ });
31
+ },
32
+ error: (...args) => collector.recordError(groupId, {
33
+ source: "tsdown",
34
+ level: "error",
35
+ text: join(args)
36
+ })
37
+ };
38
+ }
39
+
40
+ //#endregion
41
+ export { createTsdownLogger };