@savvy-web/tsdown-plugins 2.1.8 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/index.d.ts +36 -0
- package/package.json +5 -5
- package/report/issues-artifact.js +33 -3
package/README.md
CHANGED
|
@@ -53,7 +53,19 @@ export default defineConfig({
|
|
|
53
53
|
- **Bundled declarations** — each target runs two `tsdown` passes: a JavaScript pass that preserves per-module output, then a declaration-only pass that rolls every re-exported type into a single `.d.ts` per public entry (`deriveDtsPassOptions`). Per-module JavaScript stays intact while consumers keep reaching re-exported types through your published subpaths.
|
|
54
54
|
- **API Extractor meta** — `runMetaPass` is the single meta-generation orchestrator the bundler front door and both self-hosting escape hatches share: it derives the export paths, applies the optimistic next-version forward-look and drives API Extractor over a package's emitted `.d.ts` to write an api-model bundle (`.api.json`, `tsdoc-metadata.json`, resolved `tsconfig.json`). `generateMeta` is the lower-level pass it wraps, and `normalizeMetaOptions` fills the `MetaOptions` defaults that drive it.
|
|
55
55
|
- **Output reporter** — `renderReport` plus the `BuildReport` schema and a set of formatters (terminal, JSON, markdown, CI annotations, silent) render a build report for humans, agents or CI.
|
|
56
|
-
- **Issues artifact** — `writeIssuesArtifact` (with the pure `flattenIssues` and `serializeIssues` behind it) writes a deduplicated `dist/<target>/issues.json` on every build, collecting the build's warnings, errors and suppressed diagnostics in a stable JSON shape (`BuildIssues`/`PlainDiagnostic`) so an agent or CI script reads the diagnostics straight from disk instead of parsing terminal output.
|
|
56
|
+
- **Issues artifact** — `writeIssuesArtifact` (with the pure `flattenIssues` and `serializeIssues` behind it) writes a deduplicated `dist/<target>/issues.json` on every build, successful or failed, collecting the build's warnings, errors and suppressed diagnostics in a stable JSON shape (`BuildIssues`/`PlainDiagnostic`) so an agent or CI script reads the diagnostics straight from disk instead of parsing terminal output. The artifact lands through a temp file and a rename, so a concurrent reader sees the previous file or the complete new one, never a half-written one.
|
|
57
|
+
|
|
58
|
+
## Reading the issues artifact
|
|
59
|
+
|
|
60
|
+
`dist/<target>/issues.json` is written on every terminal path — the build that finished and the build that blew up both leave one behind. Read the `buildOk` field before you read anything else: a crashed build leaves `warnings`, `errors` and `suppressed` empty, which is byte-for-byte what a clean build writes, so empty buckets alone prove nothing. When `buildOk` is `false`, the optional `failure` object carries the terminal error's `name` and `message` (truncated at 2000 characters).
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
jq -c '{ ok: .buildOk, errors: (.errors | length) }' dist/prod/issues.json
|
|
64
|
+
# {"ok":true,"errors":0} a build that ran to completion with no diagnostics
|
|
65
|
+
# {"ok":false,"errors":0} a build that died before it could report any
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
An artifact produced before the field existed has no `buildOk` at all. Treat a missing value as unknown, not as a pass. If you drive `writeIssuesArtifact` yourself from a hand-written config, it defaults `buildOk` to `true` — pass `buildOk: false` (and a `failure`) explicitly from your own error path, or your artifact will claim a clean gate the build never reached.
|
|
57
69
|
|
|
58
70
|
## Effect
|
|
59
71
|
|
package/index.d.ts
CHANGED
|
@@ -1713,6 +1713,23 @@ interface BuildIssues {
|
|
|
1713
1713
|
generatedAt: string;
|
|
1714
1714
|
package: string;
|
|
1715
1715
|
target: "dev" | "prod";
|
|
1716
|
+
/**
|
|
1717
|
+
* Whether the build that produced this artifact reached its end without a terminal failure.
|
|
1718
|
+
*
|
|
1719
|
+
* The artifact is written on EVERY terminal path — success and failure alike — so a reader must
|
|
1720
|
+
* gate on this flag, not on `errors.length`. A crashed build (API Extractor blowing up, a racing
|
|
1721
|
+
* `rm -rf dist`) can leave all three diagnostic buckets empty; without this stamp that file is
|
|
1722
|
+
* byte-identical to a perfectly clean gate. Absent on artifacts written before this field existed;
|
|
1723
|
+
* treat a missing value as unknown rather than as a pass.
|
|
1724
|
+
*/
|
|
1725
|
+
buildOk: boolean;
|
|
1726
|
+
/** The terminal error that ended the build. Present only when `buildOk` is false. */
|
|
1727
|
+
failure?: {
|
|
1728
|
+
/** The error's `name` (e.g. `"Error"`, `"ConfigValidationError"`), when it has one. */
|
|
1729
|
+
name?: string;
|
|
1730
|
+
/** The error message, truncated to 2000 characters. */
|
|
1731
|
+
message: string;
|
|
1732
|
+
};
|
|
1716
1733
|
warnings: PlainDiagnostic[];
|
|
1717
1734
|
errors: PlainDiagnostic[];
|
|
1718
1735
|
suppressed: PlainDiagnostic[];
|
|
@@ -1720,11 +1737,20 @@ interface BuildIssues {
|
|
|
1720
1737
|
/**
|
|
1721
1738
|
* Flatten a build snapshot into the aggregated, de-duplicated issues artifact. Pure.
|
|
1722
1739
|
*
|
|
1740
|
+
* `buildOk` defaults to `true`, so an existing caller that never fails keeps its current output plus
|
|
1741
|
+
* the stamp; a caller that also writes on a failure path MUST pass `buildOk: false` (and ideally the
|
|
1742
|
+
* `failure`), otherwise the artifact reads as a clean gate.
|
|
1743
|
+
*
|
|
1723
1744
|
* @public
|
|
1724
1745
|
*/
|
|
1725
1746
|
declare function flattenIssues(reports: ReadonlyArray<BuildReport>, opts: {
|
|
1726
1747
|
target: "dev" | "prod";
|
|
1727
1748
|
generatedAt: string;
|
|
1749
|
+
buildOk?: boolean | undefined;
|
|
1750
|
+
failure?: {
|
|
1751
|
+
name?: string | undefined;
|
|
1752
|
+
message: string;
|
|
1753
|
+
} | undefined;
|
|
1728
1754
|
}): BuildIssues;
|
|
1729
1755
|
/**
|
|
1730
1756
|
* Serialize the issues artifact to pretty JSON with a trailing newline.
|
|
@@ -1735,6 +1761,11 @@ declare function serializeIssues(issues: BuildIssues): string;
|
|
|
1735
1761
|
/**
|
|
1736
1762
|
* Write the aggregated issues artifact to `<cwd>/dist/<target>/issues.json`. Returns the path written.
|
|
1737
1763
|
*
|
|
1764
|
+
* The write is atomic: the JSON lands in a sibling temp file which is then `rename`d over the
|
|
1765
|
+
* destination, so a concurrent reader observes either the previous artifact or the complete new one,
|
|
1766
|
+
* never a torn or half-written file. Pass `buildOk: false` (plus `failure`, when there is an error to
|
|
1767
|
+
* report) on a failure path — see the `buildOk` field of `BuildIssues`.
|
|
1768
|
+
*
|
|
1738
1769
|
* @public
|
|
1739
1770
|
*/
|
|
1740
1771
|
declare function writeIssuesArtifact(opts: {
|
|
@@ -1742,6 +1773,11 @@ declare function writeIssuesArtifact(opts: {
|
|
|
1742
1773
|
target: "dev" | "prod";
|
|
1743
1774
|
reports: ReadonlyArray<BuildReport>;
|
|
1744
1775
|
now?: () => Date;
|
|
1776
|
+
buildOk?: boolean | undefined;
|
|
1777
|
+
failure?: {
|
|
1778
|
+
name?: string | undefined;
|
|
1779
|
+
message: string;
|
|
1780
|
+
} | undefined;
|
|
1745
1781
|
}): string;
|
|
1746
1782
|
//#endregion
|
|
1747
1783
|
//#region src/report/services/EnvironmentDetector.d.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/tsdown-plugins",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@changesets/get-release-plan": "^5.0.0-next.7",
|
|
33
33
|
"@effect/platform-node": "4.0.0-beta.101",
|
|
34
|
-
"@effected/npm": "^0.
|
|
35
|
-
"@effected/package-json": "^0.5.
|
|
36
|
-
"@effected/tsconfig-json": "^0.3.
|
|
37
|
-
"@effected/workspaces": "^0.
|
|
34
|
+
"@effected/npm": "^0.4.0",
|
|
35
|
+
"@effected/package-json": "^0.5.2",
|
|
36
|
+
"@effected/tsconfig-json": "^0.3.2",
|
|
37
|
+
"@effected/workspaces": "^0.8.0",
|
|
38
38
|
"@microsoft/api-extractor": "^7.58.12",
|
|
39
39
|
"@microsoft/tsdoc": "^0.16.0",
|
|
40
40
|
"@microsoft/tsdoc-config": "^0.18.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
|
|
4
4
|
//#region src/report/issues-artifact.ts
|
|
@@ -36,9 +36,23 @@ function dedupe(entries) {
|
|
|
36
36
|
}
|
|
37
37
|
return out;
|
|
38
38
|
}
|
|
39
|
+
/** Longest failure message kept in the artifact — enough for a stack-free error, short of a log dump. */
|
|
40
|
+
const MAX_FAILURE_MESSAGE = 2e3;
|
|
41
|
+
/** Normalize a caller-supplied failure into the stamped shape (message truncated, empty name dropped). */
|
|
42
|
+
function toFailure(failure) {
|
|
43
|
+
const message = failure.message.length > MAX_FAILURE_MESSAGE ? `${failure.message.slice(0, MAX_FAILURE_MESSAGE - 1)}…` : failure.message;
|
|
44
|
+
return failure.name !== void 0 && failure.name !== "" ? {
|
|
45
|
+
name: failure.name,
|
|
46
|
+
message
|
|
47
|
+
} : { message };
|
|
48
|
+
}
|
|
39
49
|
/**
|
|
40
50
|
* Flatten a build snapshot into the aggregated, de-duplicated issues artifact. Pure.
|
|
41
51
|
*
|
|
52
|
+
* `buildOk` defaults to `true`, so an existing caller that never fails keeps its current output plus
|
|
53
|
+
* the stamp; a caller that also writes on a failure path MUST pass `buildOk: false` (and ideally the
|
|
54
|
+
* `failure`), otherwise the artifact reads as a clean gate.
|
|
55
|
+
*
|
|
42
56
|
* @public
|
|
43
57
|
*/
|
|
44
58
|
function flattenIssues(reports, opts) {
|
|
@@ -54,6 +68,8 @@ function flattenIssues(reports, opts) {
|
|
|
54
68
|
generatedAt: opts.generatedAt,
|
|
55
69
|
package: reports[0]?.package ?? "unknown",
|
|
56
70
|
target: opts.target,
|
|
71
|
+
buildOk: opts.buildOk ?? true,
|
|
72
|
+
...opts.failure !== void 0 ? { failure: toFailure(opts.failure) } : {},
|
|
57
73
|
warnings: dedupe(warnings),
|
|
58
74
|
errors: dedupe(errors),
|
|
59
75
|
suppressed: dedupe(suppressed)
|
|
@@ -70,17 +86,31 @@ function serializeIssues(issues) {
|
|
|
70
86
|
/**
|
|
71
87
|
* Write the aggregated issues artifact to `<cwd>/dist/<target>/issues.json`. Returns the path written.
|
|
72
88
|
*
|
|
89
|
+
* The write is atomic: the JSON lands in a sibling temp file which is then `rename`d over the
|
|
90
|
+
* destination, so a concurrent reader observes either the previous artifact or the complete new one,
|
|
91
|
+
* never a torn or half-written file. Pass `buildOk: false` (plus `failure`, when there is an error to
|
|
92
|
+
* report) on a failure path — see the `buildOk` field of `BuildIssues`.
|
|
93
|
+
*
|
|
73
94
|
* @public
|
|
74
95
|
*/
|
|
75
96
|
function writeIssuesArtifact(opts) {
|
|
76
97
|
const clock = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
77
98
|
const issues = flattenIssues(opts.reports, {
|
|
78
99
|
target: opts.target,
|
|
79
|
-
generatedAt: clock().toISOString()
|
|
100
|
+
generatedAt: clock().toISOString(),
|
|
101
|
+
buildOk: opts.buildOk,
|
|
102
|
+
failure: opts.failure
|
|
80
103
|
});
|
|
81
104
|
const outPath = join(opts.cwd, "dist", opts.target, "issues.json");
|
|
82
105
|
mkdirSync(dirname(outPath), { recursive: true });
|
|
83
|
-
|
|
106
|
+
const tmpPath = `${outPath}.${process.pid}.tmp`;
|
|
107
|
+
try {
|
|
108
|
+
writeFileSync(tmpPath, serializeIssues(issues), "utf8");
|
|
109
|
+
renameSync(tmpPath, outPath);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
rmSync(tmpPath, { force: true });
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
84
114
|
return outPath;
|
|
85
115
|
}
|
|
86
116
|
|