@savvy-web/tsdown-plugins 0.7.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.
Files changed (57) hide show
  1. package/README.md +2 -1
  2. package/build/build-target-groups.js +42 -9
  3. package/build/cjs-default-interop.js +1 -0
  4. package/build/loose-files.js +1 -0
  5. package/build/node-builtin-default-interop.js +15 -9
  6. package/build/strip-maps.js +1 -0
  7. package/build/sync-public.js +1 -0
  8. package/build/target-groups.js +7 -2
  9. package/catalog/resolve-catalogs.js +1 -0
  10. package/changesets/next-versions.js +1 -0
  11. package/config-validation/ConfigValidator.js +5 -1
  12. package/config-validation/ConfigValidatorLive.js +6 -2
  13. package/dts/resolved-tsconfig.js +10 -2
  14. package/entry/extract.js +1 -0
  15. package/entry/package-json-entries.js +5 -1
  16. package/errors.js +10 -2
  17. package/exe/build.js +20 -2
  18. package/exe/config.js +6 -1
  19. package/exe/filename.js +1 -0
  20. package/index.d.ts +679 -593
  21. package/index.js +8 -4
  22. package/jsx/config.js +2 -0
  23. package/manifest/emit-manifest.js +10 -2
  24. package/manifest/transform.js +18 -4
  25. package/meta/api-extractor.js +41 -1
  26. package/meta/config.js +5 -1
  27. package/meta/generate.js +6 -2
  28. package/meta/optimistic.js +1 -0
  29. package/meta/run-pass.js +75 -0
  30. package/meta/tsconfig-resolver.js +12 -12
  31. package/package.json +1 -1
  32. package/report/collector.js +141 -0
  33. package/report/formatters/ci-annotations.js +9 -2
  34. package/report/formatters/diagnostics.js +23 -0
  35. package/report/formatters/json.js +1 -0
  36. package/report/formatters/markdown.js +19 -9
  37. package/report/formatters/silent.js +1 -0
  38. package/report/formatters/terminal.js +32 -3
  39. package/report/issues-artifact.js +88 -0
  40. package/report/layers/EnvironmentDetectorLive.js +1 -0
  41. package/report/layers/ExecutorResolverLive.js +1 -0
  42. package/report/layers/FormatSelectorLive.js +1 -0
  43. package/report/layers/OutputRendererLive.js +2 -1
  44. package/report/metrics-plugin.js +63 -0
  45. package/report/pipeline.js +6 -1
  46. package/report/schema.js +52 -10
  47. package/report/services/EnvironmentDetector.js +1 -0
  48. package/report/services/ExecutorResolver.js +1 -0
  49. package/report/services/FormatSelector.js +1 -0
  50. package/report/services/OutputRenderer.js +1 -0
  51. package/report/timer.js +6 -1
  52. package/report/tsdown-logger.js +42 -0
  53. package/targets/binding.js +5 -1
  54. package/targets/config.js +5 -1
  55. package/targets/resolve-targets.js +5 -1
  56. package/tsdoc-metadata.json +11 -0
  57. package/report/schema-export.js +0 -18
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";
@@ -25,12 +28,15 @@ import { normalizeMetaOptions } from "./meta/config.js";
25
28
  import { TsconfigResolver, resolvePortableTsconfig } from "./meta/tsconfig-resolver.js";
26
29
  import { generateMeta } from "./meta/generate.js";
27
30
  import { rewriteMetaVersions } from "./meta/optimistic.js";
31
+ import { applySubdirMetaEntries, deriveExportPaths, runMetaPass } from "./meta/run-pass.js";
32
+ import { BuildReport, ReportTimings, TargetGroupReport } from "./report/schema.js";
33
+ import { BuildCollector, BuildCollectorTag } from "./report/collector.js";
28
34
  import { CiAnnotationsFormatter } from "./report/formatters/ci-annotations.js";
29
35
  import { JsonFormatter } from "./report/formatters/json.js";
30
36
  import { MarkdownFormatter } from "./report/formatters/markdown.js";
31
37
  import { SilentFormatter } from "./report/formatters/silent.js";
32
- import { createTimer, formatTime } from "./report/timer.js";
33
38
  import { TerminalFormatter } from "./report/formatters/terminal.js";
39
+ import { flattenIssues, serializeIssues, writeIssuesArtifact } from "./report/issues-artifact.js";
34
40
  import { EnvironmentDetector } from "./report/services/EnvironmentDetector.js";
35
41
  import { EnvironmentDetectorLive } from "./report/layers/EnvironmentDetectorLive.js";
36
42
  import { ExecutorResolver } from "./report/services/ExecutorResolver.js";
@@ -40,9 +46,7 @@ import { FormatSelectorLive } from "./report/layers/FormatSelectorLive.js";
40
46
  import { OutputRenderer } from "./report/services/OutputRenderer.js";
41
47
  import { OutputRendererLive } from "./report/layers/OutputRendererLive.js";
42
48
  import { ReportPipelineLive, renderReport } from "./report/pipeline.js";
43
- import { BuildReport, ReportTimings, TargetGroupReport } from "./report/schema.js";
44
- import { generateBuildReportSchema } from "./report/schema-export.js";
45
49
  import { writeTargetsBinding } from "./targets/binding.js";
46
50
  import { CatalogAssemblyError, CatalogResolutionError } from "workspaces-effect";
47
51
 
48
- 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, resolveNextVersions, resolvePortableTsconfig, resolveTargets, rewriteMetaVersions, runExeBuild, syncPublicDir, transformBin, transformExports, transformManifest, writeResolvedTsconfig, writeTargetsBinding };
52
+ 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, applySubdirMetaEntries, buildEmittedManifest, buildMetricsPlugin, buildResolvedTsconfig, buildTargetGroups, cjsDefaultInterop, computeExeFileName, createEntryName, createTimer, createTsdownLogger, defaultManifestTransform, deriveExportPaths, deriveTargetGroupOptions, emitManifest, extractEntries, flattenIssues, formatTime, generateMeta, isTargetObject, nodeBuiltinDefaultInterop, normalizeBinPaths, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveManifest, resolveNextVersions, resolvePortableTsconfig, resolveTargets, rewriteMetaVersions, runExeBuild, runMetaPass, serializeIssues, syncPublicDir, transformBin, transformExports, transformManifest, writeIssuesArtifact, writeResolvedTsconfig, writeTargetsBinding };
package/jsx/config.js CHANGED
@@ -5,6 +5,7 @@ import { existsSync, readFileSync } from "node:fs";
5
5
  /**
6
6
  * Resolve the effective JSX config: an explicit override wins; otherwise infer from the tsconfig
7
7
  * values. Returns undefined when no JSX transform is needed (preserve/none).
8
+ * @public
8
9
  */
9
10
  function resolveJsxConfig(tsconfig, override) {
10
11
  if (override !== void 0) return override.runtime === "automatic" ? {
@@ -21,6 +22,7 @@ function resolveJsxConfig(tsconfig, override) {
21
22
  /**
22
23
  * Read the jsx-relevant compilerOptions from a package's own tsconfig.json (best-effort;
23
24
  * returns empty on absence or parse error).
25
+ * @public
24
26
  */
25
27
  function readTsconfigJsx(cwd) {
26
28
  const path = join(cwd, "tsconfig.json");
@@ -4,7 +4,11 @@ import { join } from "node:path";
4
4
  import { readFile } from "node:fs/promises";
5
5
 
6
6
  //#region src/manifest/emit-manifest.ts
7
- /** Compute the final manifest bytes for a TargetGroup (catalog resolution + standard transforms). */
7
+ /**
8
+ * Compute the final manifest bytes for a TargetGroup (catalog resolution + standard transforms).
9
+ *
10
+ * @public
11
+ */
8
12
  async function buildEmittedManifest(options) {
9
13
  const { pkg, targetGroup, devManifest, transform } = options;
10
14
  const shouldResolve = targetGroup.isProd || devManifest === "resolve";
@@ -24,7 +28,11 @@ async function buildEmittedManifest(options) {
24
28
  exeRewrite: options.exeRewrite
25
29
  });
26
30
  }
27
- /** Rolldown plugin: emit the transformed package.json + LICENSE/README into the output pkg/ root. */
31
+ /**
32
+ * Rolldown plugin: emit the transformed package.json + LICENSE/README into the output pkg/ root.
33
+ *
34
+ * @public
35
+ */
28
36
  function emitManifest(options) {
29
37
  const sourceDir = options.sourceDir;
30
38
  return {
@@ -21,7 +21,7 @@ const NON_PUBLISHED_FIELDS = [
21
21
  /**
22
22
  * The default `transform` applied to every package's manifest when its
23
23
  * `savvy.build.ts` does not provide one of its own. Strips the build/dev-only
24
- * fields in {@link NON_PUBLISHED_FIELDS} from the emitted package.json.
24
+ * fields in `NON_PUBLISHED_FIELDS` from the emitted package.json.
25
25
  *
26
26
  * This is the pattern nearly every package repeated by hand (inherited from
27
27
  * rslib-builder); `defineBuild` now applies it automatically so a package needs a
@@ -35,6 +35,7 @@ const NON_PUBLISHED_FIELDS = [
35
35
  * Pure: the supplied `pkg` is NOT mutated — a shallow copy with the fields removed
36
36
  * is returned, so external callers invoking this from a custom transform keep their
37
37
  * input intact.
38
+ * @public
38
39
  */
39
40
  function defaultManifestTransform({ pkg }) {
40
41
  const out = { ...pkg };
@@ -91,6 +92,7 @@ const tsConditions = (exportKey, dual, subdirExports) => ({
91
92
  *
92
93
  * Export keys in `subdirExports` are built into an isolated `<key>/index.*` subdir (e.g. an
93
94
  * RSPress `./runtime`), so their conditions gain an `/index` segment.
95
+ * @public
94
96
  */
95
97
  function transformExports(exports, dual = false, subdirExports) {
96
98
  if (typeof exports === "string") return isTs(exports) ? tsConditions(".", isDualKey(dual, "."), subdirExports) : exports;
@@ -108,7 +110,11 @@ function transformExports(exports, dual = false, subdirExports) {
108
110
  }
109
111
  return exports;
110
112
  }
111
- /** Rewrite bin: TS targets to bin/[command].js (string to bin/cli.js); strip leading ./ otherwise. */
113
+ /**
114
+ * Rewrite bin: TS targets to bin/[command].js (string to bin/cli.js); strip leading ./ otherwise.
115
+ *
116
+ * @public
117
+ */
112
118
  function transformBin(bin) {
113
119
  if (typeof bin === "string") return isTs(bin) ? "bin/cli.js" : stripLeadingDotSlash(bin);
114
120
  if (bin && typeof bin === "object") {
@@ -118,7 +124,11 @@ function transformBin(bin) {
118
124
  }
119
125
  return bin;
120
126
  }
121
- /** FINAL guard: strip leading ./ from bin paths (npm 11.x drops ./-prefixed bins). */
127
+ /**
128
+ * FINAL guard: strip leading ./ from bin paths (npm 11.x drops ./-prefixed bins).
129
+ *
130
+ * @public
131
+ */
122
132
  function normalizeBinPaths(bin) {
123
133
  if (typeof bin === "string") return stripLeadingDotSlash(bin);
124
134
  if (bin && typeof bin === "object") {
@@ -128,7 +138,11 @@ function normalizeBinPaths(bin) {
128
138
  }
129
139
  return bin;
130
140
  }
131
- /** Apply the full standard manifest transform (excluding catalog resolution, done upstream). */
141
+ /**
142
+ * Apply the full standard manifest transform (excluding catalog resolution, done upstream).
143
+ *
144
+ * @public
145
+ */
132
146
  function transformManifest(pkg, options = {}) {
133
147
  const { publishConfig, scripts, ...rest } = pkg;
134
148
  const isPrivate = !(publishConfig?.access === "public");
@@ -7,6 +7,23 @@ 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
+ /** messageIds that become a hard build error in CI (they corrupt the .api.json). */
11
+ const CI_FATAL_MESSAGE_IDS = /* @__PURE__ */ new Set(["ae-forgotten-export"]);
12
+ /** Map an API Extractor message to a collector DiagnosticInput, or undefined if not warn/error. */
13
+ function mapExtractorMessage(message) {
14
+ const isError = message.logLevel === ExtractorLogLevel.Error;
15
+ const isWarning = message.logLevel === ExtractorLogLevel.Warning;
16
+ if (!isError && !isWarning) return void 0;
17
+ return {
18
+ source: "api-extractor",
19
+ level: isError ? "error" : "warn",
20
+ text: message.text,
21
+ ...message.messageId !== void 0 ? { code: message.messageId } : {},
22
+ ...message.sourceFilePath !== void 0 ? { file: message.sourceFilePath } : {},
23
+ ...message.sourceFileLine !== void 0 ? { line: message.sourceFileLine } : {},
24
+ ...message.sourceFileColumn !== void 0 ? { column: message.sourceFileColumn } : {}
25
+ };
26
+ }
10
27
  /** Run API Extractor over a single entry's .d.ts, writing the .api.json (and optionally tsdoc-metadata.json). Throws on failure. */
11
28
  function runApiExtractor(options) {
12
29
  const suppressor = createMessageSuppressor(options.suppressWarnings);
@@ -27,7 +44,15 @@ function runApiExtractor(options) {
27
44
  tsdocMetadataFilePath: options.tsdocMetadataPath
28
45
  } } : {},
29
46
  dtsRollup: { enabled: false },
30
- apiReport: { enabled: false }
47
+ apiReport: { enabled: false },
48
+ messages: {
49
+ extractorMessageReporting: {
50
+ default: { logLevel: "warning" },
51
+ "ae-internal-missing-underscore": { logLevel: "none" },
52
+ ...options.ci === true ? Object.fromEntries([...CI_FATAL_MESSAGE_IDS].map((id) => [id, { logLevel: "error" }])) : {}
53
+ },
54
+ tsdocMessageReporting: { default: { logLevel: "warning" } }
55
+ }
31
56
  },
32
57
  packageJsonFullPath: options.packageJsonPath,
33
58
  configObjectFullPath: void 0,
@@ -39,6 +64,10 @@ function runApiExtractor(options) {
39
64
  showVerboseMessages: false,
40
65
  messageCallback: (message) => {
41
66
  if (suppressor.matches(message.messageId, message.text)) {
67
+ if (options.onSuppressed !== void 0) {
68
+ const entry = mapExtractorMessage(message);
69
+ if (entry !== void 0) options.onSuppressed(entry);
70
+ }
42
71
  message.logLevel = ExtractorLogLevel.None;
43
72
  message.handled = true;
44
73
  return;
@@ -47,6 +76,17 @@ function runApiExtractor(options) {
47
76
  message.logLevel = ExtractorLogLevel.None;
48
77
  message.handled = true;
49
78
  }
79
+ if (options.onMessage !== void 0) {
80
+ const entry = mapExtractorMessage(message);
81
+ if (entry !== void 0) {
82
+ const ciFatal = options.ci !== true && entry.level === "warn" && entry.code !== void 0 && CI_FATAL_MESSAGE_IDS.has(entry.code);
83
+ options.onMessage(ciFatal ? {
84
+ ...entry,
85
+ ciFatal: true
86
+ } : entry);
87
+ message.handled = true;
88
+ }
89
+ }
50
90
  }
51
91
  });
52
92
  if (!result.succeeded) throw new MetaGenerationError({
package/meta/config.js CHANGED
@@ -4,7 +4,11 @@ function resolveOptimistic(value, env) {
4
4
  if (value === true || value === false) return value;
5
5
  return !(env.CI || env.GITHUB_ACTIONS);
6
6
  }
7
- /** Fill defaults so downstream code never branches on undefined. */
7
+ /**
8
+ * Fill defaults so downstream code never branches on undefined.
9
+ *
10
+ * @public
11
+ */
8
12
  function normalizeMetaOptions(meta, env = process.env) {
9
13
  return {
10
14
  localPaths: meta.localPaths ?? [],
package/meta/generate.js CHANGED
@@ -16,9 +16,10 @@ function unscopedName(name) {
16
16
  * outMetaDir (`<unscoped>.api.json` + the final `package.json` + a portable `tsconfig.json`),
17
17
  * copying that trio into each localPaths dir. The api-extractor `tsdoc-metadata.json` is a
18
18
  * published-package artifact and is written into `dtsDir` (the built pkg/), not the meta bundle.
19
+ * @public
19
20
  */
20
21
  async function generateMeta(options) {
21
- const { cwd, packageName, tsconfigPath, dtsDir, entries, exportPaths, outMetaDir, localPaths, tsdoc, manifestTransform } = options;
22
+ const { cwd, packageName, tsconfigPath, dtsDir, entries, exportPaths, outMetaDir, localPaths, tsdoc, manifestTransform, onMessage, ci, onSuppressed } = options;
22
23
  const tsdocConfigPath = writeTsdocConfig(cwd, tsdoc);
23
24
  const packageJsonPath = join(cwd, "package.json");
24
25
  mkdirSync(outMetaDir, { recursive: true });
@@ -41,7 +42,10 @@ async function generateMeta(options) {
41
42
  tsdocConfigPath,
42
43
  apiJsonPath: perEntryApiJson,
43
44
  ...isMain && !mainEntryDidTsdocMetadata ? { tsdocMetadataPath } : {},
44
- suppressWarnings: tsdoc.suppressWarnings
45
+ suppressWarnings: tsdoc.suppressWarnings,
46
+ ...ci !== void 0 ? { ci } : {},
47
+ ...onMessage !== void 0 ? { onMessage } : {},
48
+ ...onSuppressed !== void 0 ? { onSuppressed } : {}
45
49
  });
46
50
  if (isMain) mainEntryDidTsdocMetadata = true;
47
51
  perEntryModels.set(entryName, JSON.parse(readFileSync(perEntryApiJson, "utf-8")));
@@ -10,6 +10,7 @@ const DEP_FIELDS = [
10
10
  * dependency version reflect their NEXT release version from `versions`. Pure: returns a new
11
11
  * object, never mutates the input. External/catalog-resolved deps (names absent from `versions`)
12
12
  * are left as-is.
13
+ * @public
13
14
  */
14
15
  function rewriteMetaVersions(pkg, versions, selfName) {
15
16
  const out = { ...pkg };
@@ -0,0 +1,75 @@
1
+ import { createEntryName } from "../entry/extract.js";
2
+ import { resolveNextVersions } from "../changesets/next-versions.js";
3
+ import { normalizeMetaOptions } from "./config.js";
4
+ import { generateMeta } from "./generate.js";
5
+ import { rewriteMetaVersions } from "./optimistic.js";
6
+ import { join } from "node:path";
7
+
8
+ //#region src/meta/run-pass.ts
9
+ /**
10
+ * Meta-pass orchestrator: derives export paths, filters bin/ entries, resolves optimistic
11
+ * next-versions, and calls generateMeta once per publish group.
12
+ * @public
13
+ */
14
+ async function runMetaPass(o) {
15
+ const gen = o.generateMeta ?? generateMeta;
16
+ const norm = normalizeMetaOptions(o.meta);
17
+ const canonicalId = (o.groups.find((g) => g.name === o.packageName) ?? o.groups[0])?.id ?? "npm";
18
+ const dtsBasenames = {};
19
+ const nonBinEntries = {};
20
+ for (const [name, src] of Object.entries(o.entries)) if (!name.startsWith("bin/")) {
21
+ dtsBasenames[name] = name;
22
+ nonBinEntries[name] = src;
23
+ }
24
+ const exportPaths = deriveExportPaths(nonBinEntries, o.exportsMap);
25
+ applySubdirMetaEntries(o.overrides, dtsBasenames, exportPaths);
26
+ const resolveNext = o.resolveNextVersions ?? resolveNextVersions;
27
+ const nextVersions = norm.optimistic ? await resolveNext(o.cwd) : void 0;
28
+ const manifestTransform = nextVersions ? (m) => rewriteMetaVersions(m, nextVersions.versions, o.packageName) : void 0;
29
+ for (const g of o.groups) await gen({
30
+ cwd: o.cwd,
31
+ packageName: o.packageName,
32
+ tsconfigPath: o.tsconfigPath,
33
+ dtsDir: join(o.cwd, "dist", "prod", g.id, "pkg"),
34
+ entries: dtsBasenames,
35
+ exportPaths,
36
+ outMetaDir: join(o.cwd, "dist", "prod", g.id, "meta"),
37
+ localPaths: g.id === canonicalId ? norm.localPaths : [],
38
+ tsdoc: norm.tsdoc,
39
+ ...manifestTransform !== void 0 ? { manifestTransform } : {},
40
+ ci: o.ci,
41
+ onMessage: (e) => e.level === "error" ? o.collector.recordError(g.id, e) : o.collector.recordWarning(g.id, e),
42
+ onSuppressed: (e) => o.collector.recordSuppressed(g.id, e)
43
+ });
44
+ }
45
+ /**
46
+ * Map entry names to export paths using the package exports map. index maps to ".".
47
+ *
48
+ * @public
49
+ */
50
+ function deriveExportPaths(entries, exportsMap) {
51
+ const out = {};
52
+ const sourceToKey = /* @__PURE__ */ new Map();
53
+ if (exportsMap) for (const [key, src] of Object.entries(exportsMap)) sourceToKey.set(src, key);
54
+ for (const [entryName, src] of Object.entries(entries)) out[entryName] = sourceToKey.get(src) ?? (entryName === "index" ? "." : `./${entryName}`);
55
+ return out;
56
+ }
57
+ /**
58
+ * For each `outSubdir` override, point its meta entry at the isolated sub-package barrel.
59
+ *
60
+ * @public
61
+ */
62
+ function applySubdirMetaEntries(overrides, dtsBasenames, exportPaths) {
63
+ if (overrides === void 0) return;
64
+ for (const ov of overrides) {
65
+ if (ov.outSubdir === void 0) continue;
66
+ const exportPath = ov.entries[0];
67
+ if (exportPath === void 0) continue;
68
+ const flatName = createEntryName(exportPath, false);
69
+ dtsBasenames[flatName] = `${ov.outSubdir}/index`;
70
+ exportPaths[flatName] = exportPath;
71
+ }
72
+ }
73
+
74
+ //#endregion
75
+ export { applySubdirMetaEntries, deriveExportPaths, runMetaPass };
@@ -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,49 +131,49 @@ 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"]]);
141
- /** Converts a {@link ScriptTarget} enum value to its string form (e.g. `es2023`). */
140
+ static NEW_LINE_MAP = /* @__PURE__ */ new Map([[NewLineKind.CarriageReturnLineFeed, "crlf"], [NewLineKind.LineFeed, "lf"]]);
141
+ /** Converts a `ScriptTarget` enum value to its string form (e.g. `es2023`). */
142
142
  static convertScriptTarget(target) {
143
143
  if (target === void 0) return void 0;
144
144
  const mapped = TsconfigResolver.SCRIPT_TARGET_MAP.get(target);
145
145
  if (mapped !== void 0) return mapped;
146
146
  return `es${target}`;
147
147
  }
148
- /** Converts a {@link ModuleKind} enum value to its string form (e.g. `nodenext`). */
148
+ /** Converts a `ModuleKind` enum value to its string form (e.g. `nodenext`). */
149
149
  static convertModuleKind(module) {
150
150
  if (module === void 0) return void 0;
151
151
  const mapped = TsconfigResolver.MODULE_KIND_MAP.get(module);
152
152
  if (mapped !== void 0) return mapped;
153
153
  return String(module);
154
154
  }
155
- /** Converts a {@link ModuleResolutionKind} enum value to its string form (e.g. `nodenext`). */
155
+ /** Converts a `ModuleResolutionKind` enum value to its string form (e.g. `nodenext`). */
156
156
  static convertModuleResolution(resolution) {
157
157
  if (resolution === void 0) return void 0;
158
158
  const mapped = TsconfigResolver.MODULE_RESOLUTION_MAP.get(resolution);
159
159
  if (mapped !== void 0) return mapped;
160
160
  return String(resolution);
161
161
  }
162
- /** Converts a {@link JsxEmit} enum value to its string form (e.g. `preserve`, `react-jsx`). */
162
+ /** Converts a `JsxEmit` enum value to its string form (e.g. `preserve`, `react-jsx`). */
163
163
  static convertJsxEmit(jsx) {
164
164
  if (jsx === void 0) return void 0;
165
165
  const mapped = TsconfigResolver.JSX_EMIT_MAP.get(jsx);
166
166
  if (mapped !== void 0) return mapped;
167
167
  return String(jsx);
168
168
  }
169
- /** Converts a {@link ModuleDetectionKind} enum value to its string form (e.g. `force`). */
169
+ /** Converts a `ModuleDetectionKind` enum value to its string form (e.g. `force`). */
170
170
  static convertModuleDetection(detection) {
171
171
  if (detection === void 0) return void 0;
172
172
  const mapped = TsconfigResolver.MODULE_DETECTION_MAP.get(detection);
173
173
  if (mapped !== void 0) return mapped;
174
174
  return String(detection);
175
175
  }
176
- /** Converts a {@link NewLineKind} enum value to its string form (`lf` or `crlf`). */
176
+ /** Converts a `NewLineKind` enum value to its string form (`lf` or `crlf`). */
177
177
  static convertNewLine(newLine) {
178
178
  if (newLine === void 0) return void 0;
179
179
  const mapped = TsconfigResolver.NEW_LINE_MAP.get(newLine);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/tsdown-plugins",
3
- "version": "0.7.0",
3
+ "version": "0.9.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",
@@ -0,0 +1,141 @@
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
+ * @public
10
+ */
11
+ var BuildCollector = class {
12
+ groups = /* @__PURE__ */ new Map();
13
+ group(groupId) {
14
+ let g = this.groups.get(groupId);
15
+ if (g === void 0) {
16
+ g = {
17
+ id: groupId,
18
+ entries: [],
19
+ passes: /* @__PURE__ */ new Map(),
20
+ warnings: [],
21
+ errors: [],
22
+ suppressed: [],
23
+ seenPaths: /* @__PURE__ */ new Set(),
24
+ seenDiagnostics: /* @__PURE__ */ new Set(),
25
+ seenSuppressed: /* @__PURE__ */ new Set()
26
+ };
27
+ this.groups.set(groupId, g);
28
+ }
29
+ return g;
30
+ }
31
+ pass(groupId, pass) {
32
+ const g = this.group(groupId);
33
+ let p = g.passes.get(pass);
34
+ if (p === void 0) {
35
+ p = {
36
+ files: [],
37
+ ms: 0
38
+ };
39
+ g.passes.set(pass, p);
40
+ }
41
+ return p;
42
+ }
43
+ registerGroup(groupId, entries) {
44
+ this.group(groupId).entries = [...entries];
45
+ }
46
+ recordEmitted(groupId, pass, file) {
47
+ const g = this.group(groupId);
48
+ if (g.seenPaths.has(file.path)) return;
49
+ g.seenPaths.add(file.path);
50
+ this.pass(groupId, pass).files.push({
51
+ path: file.path,
52
+ bytes: file.bytes,
53
+ ...file.gzip !== void 0 ? { gzip: file.gzip } : {}
54
+ });
55
+ }
56
+ recordPassTiming(groupId, pass, ms) {
57
+ this.pass(groupId, pass).ms += ms;
58
+ }
59
+ recordWarning(groupId, entry) {
60
+ const g = this.group(groupId);
61
+ const key = diagnosticKey(entry);
62
+ if (g.seenDiagnostics.has(key)) return;
63
+ g.seenDiagnostics.add(key);
64
+ g.warnings.push(toEntry(entry));
65
+ }
66
+ recordError(groupId, entry) {
67
+ const g = this.group(groupId);
68
+ const key = diagnosticKey(entry);
69
+ if (g.seenDiagnostics.has(key)) return;
70
+ g.seenDiagnostics.add(key);
71
+ g.errors.push(toEntry(entry));
72
+ }
73
+ recordSuppressed(groupId, entry) {
74
+ const g = this.group(groupId);
75
+ const key = diagnosticKey(entry);
76
+ if (g.seenSuppressed.has(key)) return;
77
+ g.seenSuppressed.add(key);
78
+ g.suppressed.push(toEntry(entry));
79
+ }
80
+ snapshot(packageName) {
81
+ const targetGroups = [];
82
+ for (const g of this.groups.values()) {
83
+ const passes = [];
84
+ let totalMs = 0;
85
+ for (const [id, p] of g.passes) {
86
+ const files = p.files.map((f) => new EmittedFile({
87
+ path: f.path,
88
+ bytes: f.bytes,
89
+ ...f.gzip !== void 0 ? { gzip: f.gzip } : {}
90
+ }));
91
+ passes.push(new PassReport({
92
+ id,
93
+ files,
94
+ ms: p.ms
95
+ }));
96
+ totalMs += p.ms;
97
+ }
98
+ targetGroups.push(new TargetGroupReport({
99
+ id: g.id,
100
+ entries: [...g.entries],
101
+ passes,
102
+ warnings: [...g.warnings],
103
+ errors: [...g.errors],
104
+ suppressed: [...g.suppressed],
105
+ timings: new ReportTimings({ totalMs })
106
+ }));
107
+ }
108
+ return [new BuildReport({
109
+ package: packageName,
110
+ targetGroups
111
+ })];
112
+ }
113
+ };
114
+ function diagnosticKey(input) {
115
+ return [
116
+ input.source,
117
+ input.level,
118
+ input.code ?? "",
119
+ input.text,
120
+ input.file ?? "",
121
+ String(input.line ?? ""),
122
+ String(input.column ?? "")
123
+ ].join("\0");
124
+ }
125
+ function toEntry(input) {
126
+ return new DiagnosticEntry({
127
+ source: input.source,
128
+ level: input.level,
129
+ text: input.text,
130
+ ...input.code !== void 0 ? { code: input.code } : {},
131
+ ...input.ciFatal !== void 0 ? { ciFatal: input.ciFatal } : {},
132
+ ...input.file !== void 0 ? { file: input.file } : {},
133
+ ...input.line !== void 0 ? { line: input.line } : {},
134
+ ...input.column !== void 0 ? { column: input.column } : {}
135
+ });
136
+ }
137
+ /** @public */
138
+ var BuildCollectorTag = class extends Context.Tag("@savvy-web/tsdown-plugins/BuildCollector")() {};
139
+
140
+ //#endregion
141
+ export { BuildCollector, BuildCollectorTag };
@@ -1,12 +1,19 @@
1
1
  //#region src/report/formatters/ci-annotations.ts
2
2
  const esc = (s) => s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
3
+ /** @public */
3
4
  const CiAnnotationsFormatter = {
4
5
  format: "ci-annotations",
5
6
  render: (reports) => {
6
7
  const lines = [];
7
8
  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)}`);
9
+ for (const e of g.errors) {
10
+ const loc = e.file !== void 0 ? ` file=${esc(e.file)}${e.line !== void 0 ? `,line=${e.line}` : ""}` : "";
11
+ lines.push(`::error title=${esc(r.package)} (${esc(g.id)})${loc}::${esc(e.text)}`);
12
+ }
13
+ for (const w of g.warnings) {
14
+ const loc = w.file !== void 0 ? ` file=${esc(w.file)}${w.line !== void 0 ? `,line=${w.line}` : ""}` : "";
15
+ lines.push(`::warning title=${esc(r.package)} (${esc(g.id)})${loc}::${esc(w.text)}`);
16
+ }
10
17
  }
11
18
  return lines.length === 0 ? [] : [{
12
19
  target: "stdout",
@@ -0,0 +1,23 @@
1
+ //#region src/report/formatters/diagnostics.ts
2
+ /** "code ×N, code2 ×M", sorted by count desc then code asc. */
3
+ function suppressedSummary(entries) {
4
+ const counts = /* @__PURE__ */ new Map();
5
+ for (const e of entries) {
6
+ const code = e.code ?? "unknown";
7
+ counts.set(code, (counts.get(code) ?? 0) + 1);
8
+ }
9
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([code, n]) => `${code} ×${n}`).join(", ");
10
+ }
11
+ /** Number of warnings flagged ci-fatal across every group of a package. */
12
+ function ciFatalCountForPackage(report) {
13
+ let n = 0;
14
+ for (const g of report.targetGroups) for (const w of g.warnings) if (w.ciFatal === true) n += 1;
15
+ return n;
16
+ }
17
+ /** The persuasive callout shown when ci-fatal warnings are present locally. */
18
+ function ciFatalCallout(n) {
19
+ return `⛔ ${n} ${n === 1 ? "issue" : "issues"} here ${n === 1 ? "becomes" : "become"} a hard error in CI and will fail the build. Fix before pushing.`;
20
+ }
21
+
22
+ //#endregion
23
+ export { ciFatalCallout, ciFatalCountForPackage, suppressedSummary };
@@ -1,4 +1,5 @@
1
1
  //#region src/report/formatters/json.ts
2
+ /** @public */
2
3
  const JsonFormatter = {
3
4
  format: "json",
4
5
  render: (reports) => [{