@savvy-web/tsdown-plugins 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/build/build-target-groups.js +1 -0
- package/build/cjs-default-interop.js +1 -0
- package/build/loose-files.js +1 -0
- package/build/node-builtin-default-interop.js +15 -9
- package/build/strip-maps.js +1 -0
- package/build/sync-public.js +1 -0
- package/build/target-groups.js +5 -1
- package/catalog/resolve-catalogs.js +1 -0
- package/changesets/next-versions.js +1 -0
- package/config-validation/ConfigValidator.js +5 -1
- package/config-validation/ConfigValidatorLive.js +5 -1
- package/dts/resolved-tsconfig.js +10 -2
- package/entry/extract.js +1 -0
- package/entry/package-json-entries.js +5 -1
- package/errors.js +10 -2
- package/exe/build.js +5 -1
- package/exe/config.js +6 -1
- package/exe/filename.js +1 -0
- package/index.d.ts +502 -571
- package/index.js +3 -2
- package/jsx/config.js +2 -0
- package/manifest/emit-manifest.js +10 -2
- package/manifest/transform.js +18 -4
- package/meta/api-extractor.js +21 -2
- package/meta/config.js +5 -1
- package/meta/generate.js +5 -2
- package/meta/optimistic.js +1 -0
- package/meta/run-pass.js +75 -0
- package/meta/tsconfig-resolver.js +6 -6
- package/package.json +1 -1
- package/report/collector.js +16 -1
- package/report/formatters/ci-annotations.js +1 -0
- package/report/formatters/diagnostics.js +23 -0
- package/report/formatters/json.js +1 -0
- package/report/formatters/markdown.js +19 -9
- package/report/formatters/silent.js +1 -0
- package/report/formatters/terminal.js +10 -1
- package/report/issues-artifact.js +88 -0
- package/report/layers/EnvironmentDetectorLive.js +1 -0
- package/report/layers/ExecutorResolverLive.js +1 -0
- package/report/layers/FormatSelectorLive.js +1 -0
- package/report/layers/OutputRendererLive.js +1 -0
- package/report/metrics-plugin.js +2 -0
- package/report/pipeline.js +2 -0
- package/report/schema.js +24 -3
- package/report/services/EnvironmentDetector.js +1 -0
- package/report/services/ExecutorResolver.js +1 -0
- package/report/services/FormatSelector.js +1 -0
- package/report/services/OutputRenderer.js +1 -0
- package/report/timer.js +6 -1
- package/report/tsdown-logger.js +1 -0
- package/targets/binding.js +5 -1
- package/targets/config.js +5 -1
- package/targets/resolve-targets.js +5 -1
- package/tsdoc-metadata.json +11 -0
- package/report/schema-export.js +0 -18
package/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import { normalizeMetaOptions } from "./meta/config.js";
|
|
|
28
28
|
import { TsconfigResolver, resolvePortableTsconfig } from "./meta/tsconfig-resolver.js";
|
|
29
29
|
import { generateMeta } from "./meta/generate.js";
|
|
30
30
|
import { rewriteMetaVersions } from "./meta/optimistic.js";
|
|
31
|
+
import { applySubdirMetaEntries, deriveExportPaths, runMetaPass } from "./meta/run-pass.js";
|
|
31
32
|
import { BuildReport, ReportTimings, TargetGroupReport } from "./report/schema.js";
|
|
32
33
|
import { BuildCollector, BuildCollectorTag } from "./report/collector.js";
|
|
33
34
|
import { CiAnnotationsFormatter } from "./report/formatters/ci-annotations.js";
|
|
@@ -35,6 +36,7 @@ import { JsonFormatter } from "./report/formatters/json.js";
|
|
|
35
36
|
import { MarkdownFormatter } from "./report/formatters/markdown.js";
|
|
36
37
|
import { SilentFormatter } from "./report/formatters/silent.js";
|
|
37
38
|
import { TerminalFormatter } from "./report/formatters/terminal.js";
|
|
39
|
+
import { flattenIssues, serializeIssues, writeIssuesArtifact } from "./report/issues-artifact.js";
|
|
38
40
|
import { EnvironmentDetector } from "./report/services/EnvironmentDetector.js";
|
|
39
41
|
import { EnvironmentDetectorLive } from "./report/layers/EnvironmentDetectorLive.js";
|
|
40
42
|
import { ExecutorResolver } from "./report/services/ExecutorResolver.js";
|
|
@@ -44,8 +46,7 @@ import { FormatSelectorLive } from "./report/layers/FormatSelectorLive.js";
|
|
|
44
46
|
import { OutputRenderer } from "./report/services/OutputRenderer.js";
|
|
45
47
|
import { OutputRendererLive } from "./report/layers/OutputRendererLive.js";
|
|
46
48
|
import { ReportPipelineLive, renderReport } from "./report/pipeline.js";
|
|
47
|
-
import { generateBuildReportSchema } from "./report/schema-export.js";
|
|
48
49
|
import { writeTargetsBinding } from "./targets/binding.js";
|
|
49
50
|
import { CatalogAssemblyError, CatalogResolutionError } from "workspaces-effect";
|
|
50
51
|
|
|
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,
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 {
|
package/manifest/transform.js
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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");
|
package/meta/api-extractor.js
CHANGED
|
@@ -7,6 +7,8 @@ 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"]);
|
|
10
12
|
/** Map an API Extractor message to a collector DiagnosticInput, or undefined if not warn/error. */
|
|
11
13
|
function mapExtractorMessage(message) {
|
|
12
14
|
const isError = message.logLevel === ExtractorLogLevel.Error;
|
|
@@ -16,6 +18,7 @@ function mapExtractorMessage(message) {
|
|
|
16
18
|
source: "api-extractor",
|
|
17
19
|
level: isError ? "error" : "warn",
|
|
18
20
|
text: message.text,
|
|
21
|
+
...message.messageId !== void 0 ? { code: message.messageId } : {},
|
|
19
22
|
...message.sourceFilePath !== void 0 ? { file: message.sourceFilePath } : {},
|
|
20
23
|
...message.sourceFileLine !== void 0 ? { line: message.sourceFileLine } : {},
|
|
21
24
|
...message.sourceFileColumn !== void 0 ? { column: message.sourceFileColumn } : {}
|
|
@@ -41,7 +44,15 @@ function runApiExtractor(options) {
|
|
|
41
44
|
tsdocMetadataFilePath: options.tsdocMetadataPath
|
|
42
45
|
} } : {},
|
|
43
46
|
dtsRollup: { enabled: false },
|
|
44
|
-
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
|
+
}
|
|
45
56
|
},
|
|
46
57
|
packageJsonFullPath: options.packageJsonPath,
|
|
47
58
|
configObjectFullPath: void 0,
|
|
@@ -53,6 +64,10 @@ function runApiExtractor(options) {
|
|
|
53
64
|
showVerboseMessages: false,
|
|
54
65
|
messageCallback: (message) => {
|
|
55
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
|
+
}
|
|
56
71
|
message.logLevel = ExtractorLogLevel.None;
|
|
57
72
|
message.handled = true;
|
|
58
73
|
return;
|
|
@@ -64,7 +79,11 @@ function runApiExtractor(options) {
|
|
|
64
79
|
if (options.onMessage !== void 0) {
|
|
65
80
|
const entry = mapExtractorMessage(message);
|
|
66
81
|
if (entry !== void 0) {
|
|
67
|
-
options.
|
|
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);
|
|
68
87
|
message.handled = true;
|
|
69
88
|
}
|
|
70
89
|
}
|
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
|
-
/**
|
|
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, onMessage } = 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 });
|
|
@@ -42,7 +43,9 @@ async function generateMeta(options) {
|
|
|
42
43
|
apiJsonPath: perEntryApiJson,
|
|
43
44
|
...isMain && !mainEntryDidTsdocMetadata ? { tsdocMetadataPath } : {},
|
|
44
45
|
suppressWarnings: tsdoc.suppressWarnings,
|
|
45
|
-
...
|
|
46
|
+
...ci !== void 0 ? { ci } : {},
|
|
47
|
+
...onMessage !== void 0 ? { onMessage } : {},
|
|
48
|
+
...onSuppressed !== void 0 ? { onSuppressed } : {}
|
|
46
49
|
});
|
|
47
50
|
if (isMain) mainEntryDidTsdocMetadata = true;
|
|
48
51
|
perEntryModels.set(entryName, JSON.parse(readFileSync(perEntryApiJson, "utf-8")));
|
package/meta/optimistic.js
CHANGED
|
@@ -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 };
|
package/meta/run-pass.js
ADDED
|
@@ -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 };
|
|
@@ -138,42 +138,42 @@ var TsconfigResolver = class TsconfigResolver {
|
|
|
138
138
|
]);
|
|
139
139
|
/** @internal */
|
|
140
140
|
static NEW_LINE_MAP = /* @__PURE__ */ new Map([[NewLineKind.CarriageReturnLineFeed, "crlf"], [NewLineKind.LineFeed, "lf"]]);
|
|
141
|
-
/** Converts a
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.9.1",
|
|
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",
|
package/report/collector.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Context } from "effect";
|
|
|
6
6
|
* Stateful build-event accumulator. The write surface is synchronous so it can be called directly
|
|
7
7
|
* from tsdown's customLogger and API Extractor's messageCallback (both invoked synchronously).
|
|
8
8
|
* `snapshot` builds the immutable BuildReport the Effect render pipeline consumes.
|
|
9
|
+
* @public
|
|
9
10
|
*/
|
|
10
11
|
var BuildCollector = class {
|
|
11
12
|
groups = /* @__PURE__ */ new Map();
|
|
@@ -18,8 +19,10 @@ var BuildCollector = class {
|
|
|
18
19
|
passes: /* @__PURE__ */ new Map(),
|
|
19
20
|
warnings: [],
|
|
20
21
|
errors: [],
|
|
22
|
+
suppressed: [],
|
|
21
23
|
seenPaths: /* @__PURE__ */ new Set(),
|
|
22
|
-
seenDiagnostics: /* @__PURE__ */ new Set()
|
|
24
|
+
seenDiagnostics: /* @__PURE__ */ new Set(),
|
|
25
|
+
seenSuppressed: /* @__PURE__ */ new Set()
|
|
23
26
|
};
|
|
24
27
|
this.groups.set(groupId, g);
|
|
25
28
|
}
|
|
@@ -67,6 +70,13 @@ var BuildCollector = class {
|
|
|
67
70
|
g.seenDiagnostics.add(key);
|
|
68
71
|
g.errors.push(toEntry(entry));
|
|
69
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
|
+
}
|
|
70
80
|
snapshot(packageName) {
|
|
71
81
|
const targetGroups = [];
|
|
72
82
|
for (const g of this.groups.values()) {
|
|
@@ -91,6 +101,7 @@ var BuildCollector = class {
|
|
|
91
101
|
passes,
|
|
92
102
|
warnings: [...g.warnings],
|
|
93
103
|
errors: [...g.errors],
|
|
104
|
+
suppressed: [...g.suppressed],
|
|
94
105
|
timings: new ReportTimings({ totalMs })
|
|
95
106
|
}));
|
|
96
107
|
}
|
|
@@ -104,6 +115,7 @@ function diagnosticKey(input) {
|
|
|
104
115
|
return [
|
|
105
116
|
input.source,
|
|
106
117
|
input.level,
|
|
118
|
+
input.code ?? "",
|
|
107
119
|
input.text,
|
|
108
120
|
input.file ?? "",
|
|
109
121
|
String(input.line ?? ""),
|
|
@@ -115,11 +127,14 @@ function toEntry(input) {
|
|
|
115
127
|
source: input.source,
|
|
116
128
|
level: input.level,
|
|
117
129
|
text: input.text,
|
|
130
|
+
...input.code !== void 0 ? { code: input.code } : {},
|
|
131
|
+
...input.ciFatal !== void 0 ? { ciFatal: input.ciFatal } : {},
|
|
118
132
|
...input.file !== void 0 ? { file: input.file } : {},
|
|
119
133
|
...input.line !== void 0 ? { line: input.line } : {},
|
|
120
134
|
...input.column !== void 0 ? { column: input.column } : {}
|
|
121
135
|
});
|
|
122
136
|
}
|
|
137
|
+
/** @public */
|
|
123
138
|
var BuildCollectorTag = class extends Context.Tag("@savvy-web/tsdown-plugins/BuildCollector")() {};
|
|
124
139
|
|
|
125
140
|
//#endregion
|
|
@@ -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,17 +1,27 @@
|
|
|
1
|
+
import { ciFatalCallout, ciFatalCountForPackage, suppressedSummary } from "./diagnostics.js";
|
|
2
|
+
|
|
1
3
|
//#region src/report/formatters/markdown.ts
|
|
4
|
+
/** @public */
|
|
2
5
|
const MarkdownFormatter = {
|
|
3
6
|
format: "markdown",
|
|
4
|
-
render: (reports) => {
|
|
7
|
+
render: (reports, ctx) => {
|
|
5
8
|
const lines = [];
|
|
6
9
|
for (const r of reports) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const hasErrors = r.targetGroups.some((g) => g.errors.length > 0);
|
|
11
|
+
const hasWarnings = r.targetGroups.some((g) => g.warnings.length > 0);
|
|
12
|
+
const hasSuppressed = r.targetGroups.some((g) => g.suppressed.length > 0);
|
|
13
|
+
const icon = hasErrors ? "❌" : hasWarnings || hasSuppressed ? "⚠️" : "✅";
|
|
14
|
+
lines.push(`## ${icon} ${r.package}`);
|
|
15
|
+
for (const g of r.targetGroups) {
|
|
16
|
+
if (g.errors.length === 0 && g.warnings.length === 0 && g.suppressed.length === 0) continue;
|
|
17
|
+
lines.push(`- **${g.id}**`);
|
|
18
|
+
for (const e of g.errors) lines.push(` - ❌ ${e.text}`);
|
|
19
|
+
for (const w of g.warnings) lines.push(` - ⚠️ ${w.text}${w.ciFatal === true ? " [fails CI]" : ""}`);
|
|
20
|
+
if (g.suppressed.length > 0) if (ctx.verbose) for (const s of g.suppressed) lines.push(` - 🔇 ${s.code ?? "?"}: ${s.text}`);
|
|
21
|
+
else lines.push(` - 🔇 suppressed ${g.suppressed.length}: ${suppressedSummary(g.suppressed)}`);
|
|
22
|
+
}
|
|
23
|
+
const fatal = ciFatalCountForPackage(r);
|
|
24
|
+
if (fatal > 0) lines.push(`> ${ciFatalCallout(fatal)}`);
|
|
15
25
|
}
|
|
16
26
|
return [{
|
|
17
27
|
target: "stdout",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatTime } from "../timer.js";
|
|
2
|
+
import { ciFatalCallout, ciFatalCountForPackage, suppressedSummary } from "./diagnostics.js";
|
|
2
3
|
import pc from "picocolors";
|
|
3
4
|
|
|
4
5
|
//#region src/report/formatters/terminal.ts
|
|
@@ -7,6 +8,7 @@ const fileCount = (g) => g.passes.reduce((sum, p) => sum + p.files.length, 0);
|
|
|
7
8
|
const diagLine = (d) => {
|
|
8
9
|
return `${d.file !== void 0 ? ` ${d.file}${d.line !== void 0 ? `:${d.line}` : ""}` : ""} ${d.text}`.trim();
|
|
9
10
|
};
|
|
11
|
+
/** @public */
|
|
10
12
|
const TerminalFormatter = {
|
|
11
13
|
format: "terminal",
|
|
12
14
|
render: (reports, ctx) => {
|
|
@@ -27,8 +29,15 @@ const TerminalFormatter = {
|
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
for (const e of g.errors) lines.push(` ${color(pc.red, "error")} ${diagLine(e)}`);
|
|
30
|
-
for (const w of g.warnings)
|
|
32
|
+
for (const w of g.warnings) {
|
|
33
|
+
const tag = w.ciFatal === true ? color(pc.red, " [fails CI]") : "";
|
|
34
|
+
lines.push(` ${color(pc.yellow, "warn")} ${diagLine(w)}${tag}`);
|
|
35
|
+
}
|
|
36
|
+
if (g.suppressed.length > 0) if (ctx.verbose) for (const s of g.suppressed) lines.push(` ${color(pc.dim, `suppressed ${s.code ?? "?"}`)} ${diagLine(s)}`);
|
|
37
|
+
else lines.push(` ${color(pc.dim, `suppressed ${g.suppressed.length}: ${suppressedSummary(g.suppressed)}`)}`);
|
|
31
38
|
}
|
|
39
|
+
const fatal = ciFatalCountForPackage(r);
|
|
40
|
+
if (fatal > 0) lines.push(` ${color(pc.red, ciFatalCallout(fatal))}`);
|
|
32
41
|
}
|
|
33
42
|
const pkgs = reports.length;
|
|
34
43
|
if (pkgs > 0) {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
//#region src/report/issues-artifact.ts
|
|
5
|
+
/** Copy a DiagnosticEntry to a plain object, omitting undefined optionals for stable output. */
|
|
6
|
+
function toPlain(d) {
|
|
7
|
+
const out = {
|
|
8
|
+
source: d.source,
|
|
9
|
+
level: d.level,
|
|
10
|
+
text: d.text
|
|
11
|
+
};
|
|
12
|
+
if (d.code !== void 0) out.code = d.code;
|
|
13
|
+
if (d.ciFatal !== void 0) out.ciFatal = d.ciFatal;
|
|
14
|
+
if (d.file !== void 0) out.file = d.file;
|
|
15
|
+
if (d.line !== void 0) out.line = d.line;
|
|
16
|
+
if (d.column !== void 0) out.column = d.column;
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
/** De-duplicate by the identity-bearing fields (registry target-groups carry identical diagnostics). */
|
|
20
|
+
function dedupe(entries) {
|
|
21
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const e of entries) {
|
|
24
|
+
const key = JSON.stringify([
|
|
25
|
+
e.source,
|
|
26
|
+
e.level,
|
|
27
|
+
e.code ?? "",
|
|
28
|
+
e.text,
|
|
29
|
+
e.file ?? "",
|
|
30
|
+
e.line ?? -1,
|
|
31
|
+
e.column ?? -1
|
|
32
|
+
]);
|
|
33
|
+
if (seen.has(key)) continue;
|
|
34
|
+
seen.add(key);
|
|
35
|
+
out.push(e);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Flatten a build snapshot into the aggregated, de-duplicated issues artifact. Pure.
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
function flattenIssues(reports, opts) {
|
|
45
|
+
const warnings = [];
|
|
46
|
+
const errors = [];
|
|
47
|
+
const suppressed = [];
|
|
48
|
+
for (const report of reports) for (const g of report.targetGroups) {
|
|
49
|
+
for (const w of g.warnings) warnings.push(toPlain(w));
|
|
50
|
+
for (const e of g.errors) errors.push(toPlain(e));
|
|
51
|
+
for (const s of g.suppressed) suppressed.push(toPlain(s));
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
generatedAt: opts.generatedAt,
|
|
55
|
+
package: reports[0]?.package ?? "unknown",
|
|
56
|
+
target: opts.target,
|
|
57
|
+
warnings: dedupe(warnings),
|
|
58
|
+
errors: dedupe(errors),
|
|
59
|
+
suppressed: dedupe(suppressed)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Serialize the issues artifact to pretty JSON with a trailing newline.
|
|
64
|
+
*
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
function serializeIssues(issues) {
|
|
68
|
+
return `${JSON.stringify(issues, null, 2)}\n`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Write the aggregated issues artifact to `<cwd>/dist/<target>/issues.json`. Returns the path written.
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
function writeIssuesArtifact(opts) {
|
|
76
|
+
const clock = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
77
|
+
const issues = flattenIssues(opts.reports, {
|
|
78
|
+
target: opts.target,
|
|
79
|
+
generatedAt: clock().toISOString()
|
|
80
|
+
});
|
|
81
|
+
const outPath = join(opts.cwd, "dist", opts.target, "issues.json");
|
|
82
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
83
|
+
writeFileSync(outPath, serializeIssues(issues), "utf8");
|
|
84
|
+
return outPath;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { flattenIssues, serializeIssues, writeIssuesArtifact };
|
|
@@ -4,6 +4,7 @@ import { isAgent, isCI } from "std-env";
|
|
|
4
4
|
|
|
5
5
|
//#region src/report/layers/EnvironmentDetectorLive.ts
|
|
6
6
|
const isGitHub = () => process.env.GITHUB_ACTIONS === "true" || process.env.GITHUB_ACTIONS === "1";
|
|
7
|
+
/** @public */
|
|
7
8
|
const EnvironmentDetectorLive = Layer.succeed(EnvironmentDetector, { detect: () => Effect.sync(() => {
|
|
8
9
|
if (isAgent) return "agent-shell";
|
|
9
10
|
if (isGitHub()) return "ci-github";
|
|
@@ -2,6 +2,7 @@ import { ExecutorResolver } from "../services/ExecutorResolver.js";
|
|
|
2
2
|
import { Effect, Layer } from "effect";
|
|
3
3
|
|
|
4
4
|
//#region src/report/layers/ExecutorResolverLive.ts
|
|
5
|
+
/** @public */
|
|
5
6
|
const ExecutorResolverLive = Layer.succeed(ExecutorResolver, { resolve: (env) => Effect.succeed(env === "agent-shell" ? "agent" : env === "terminal" ? "human" : "ci") });
|
|
6
7
|
|
|
7
8
|
//#endregion
|