tsdown-stale-guard 0.1.0 → 0.1.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 +62 -0
- package/dist/cli.mjs +3 -11
- package/dist/{check-CWbzg4YR.mjs → guard-CAD3_wn0.mjs} +33 -1
- package/dist/index.d.mts +37 -1
- package/dist/index.mjs +3 -3
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ Build freshness validation for [tsdown](https://github.com/rolldown/tsdown). Rec
|
|
|
17
17
|
- **Find-up search** — detects lock files and configs in monorepo setups
|
|
18
18
|
- **CLI** — `tsdown-stale-guard` for CI pipelines
|
|
19
19
|
- **Programmatic API** — `checkBuildState()` for tool integrations
|
|
20
|
+
- **Build guard** — `guardStaleBuild()` throws on stale builds, great for test setups
|
|
21
|
+
- **Structured diagnostics** — errors use [logs-sdk](https://github.com/vercel-labs/logs-sdk) with stable codes and actionable fixes
|
|
20
22
|
|
|
21
23
|
## Install
|
|
22
24
|
|
|
@@ -103,6 +105,66 @@ else {
|
|
|
103
105
|
}
|
|
104
106
|
```
|
|
105
107
|
|
|
108
|
+
### Guard Stale Build
|
|
109
|
+
|
|
110
|
+
`guardStaleBuild()` checks the build state and throws a structured [`TSDSG0002`](./docs/errors/tsdsg0002.md) error if the build is stale. This is useful for CI pipelines or test setups where you want to fail early when the build output is outdated.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { guardStaleBuild } from 'tsdown-stale-guard'
|
|
114
|
+
|
|
115
|
+
// Throws if the build is stale
|
|
116
|
+
await guardStaleBuild()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### With Vitest
|
|
120
|
+
|
|
121
|
+
When writing tests against the built output (`dist/`), you can use `guardStaleBuild()` to ensure the build is fresh before tests run. Use `beforeAll` for a per-test-file check:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { guardStaleBuild } from 'tsdown-stale-guard'
|
|
125
|
+
import { beforeAll, describe, it } from 'vitest'
|
|
126
|
+
|
|
127
|
+
beforeAll(async () => {
|
|
128
|
+
await guardStaleBuild()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('should work', async () => {
|
|
132
|
+
const { myFunction } = await import('../dist/index.mjs')
|
|
133
|
+
// test against the built output
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Or use `globalSetup` for a one-time global check:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// test/setup.ts
|
|
141
|
+
import { guardStaleBuild } from 'tsdown-stale-guard'
|
|
142
|
+
|
|
143
|
+
await guardStaleBuild()
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
// vitest.config.ts
|
|
148
|
+
import { defineConfig } from 'vitest/config'
|
|
149
|
+
|
|
150
|
+
export default defineConfig({
|
|
151
|
+
test: {
|
|
152
|
+
globalSetup: ['test/setup.ts'],
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Either way, tests fail immediately with a clear error message if the build is stale, instead of producing confusing failures from outdated output.
|
|
158
|
+
|
|
159
|
+
### Diagnostic Codes
|
|
160
|
+
|
|
161
|
+
All errors thrown by `tsdown-stale-guard` are structured [`CodedError`](https://github.com/vercel-labs/logs-sdk) objects with stable codes, actionable `fix` messages, and documentation links.
|
|
162
|
+
|
|
163
|
+
| Code | Level | Description |
|
|
164
|
+
|------|-------|-------------|
|
|
165
|
+
| [`TSDSG0001`](./docs/errors/tsdsg0001.md) | error | `tsdownConfigResolved` hook was not called (tsdown version too old) |
|
|
166
|
+
| [`TSDSG0002`](./docs/errors/tsdsg0002.md) | error | Build is stale — source files, config, or dependencies changed |
|
|
167
|
+
|
|
106
168
|
## How It Works
|
|
107
169
|
|
|
108
170
|
The plugin hooks into Rolldown's build pipeline:
|
package/dist/cli.mjs
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as
|
|
3
|
-
import process from "node:process";
|
|
2
|
+
import { t as guardStaleBuild } from "./guard-CAD3_wn0.mjs";
|
|
4
3
|
import cac from "cac";
|
|
5
4
|
//#region src/cli.ts
|
|
6
5
|
const cli = cac("tsdown-stale-guard");
|
|
7
6
|
cli.command("[...args]", "Check if the build is up to date").option("--hash-file <path>", "Path to the hash file").action(async (_args, options) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
console.log("Build is up to date.");
|
|
11
|
-
process.exit(0);
|
|
12
|
-
} else {
|
|
13
|
-
console.log("Build is stale. Changes detected:");
|
|
14
|
-
for (const change of result.changes) console.log(` ${change.type}: [${change.category}] ${change.file}`);
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
7
|
+
await guardStaleBuild({ hashFile: options.hashFile });
|
|
8
|
+
console.log("Build is up to date.");
|
|
17
9
|
});
|
|
18
10
|
cli.help();
|
|
19
11
|
cli.parse();
|
|
@@ -4,6 +4,7 @@ import process from "node:process";
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import { parse, stringify } from "yaml";
|
|
7
|
+
import { consoleReporter, createLogger, defineDiagnostics } from "logs-sdk";
|
|
7
8
|
//#region src/hash.ts
|
|
8
9
|
async function hashFile(path) {
|
|
9
10
|
const content = await readFile(path);
|
|
@@ -116,4 +117,35 @@ async function checkEntry(entry, category, root, changes) {
|
|
|
116
117
|
});
|
|
117
118
|
}
|
|
118
119
|
//#endregion
|
|
119
|
-
|
|
120
|
+
//#region src/diagnostics.ts
|
|
121
|
+
const diagnostics = defineDiagnostics({
|
|
122
|
+
docsBase: (code) => `https://github.com/antfu-collective/tsdown-stale-guard/blob/main/docs/errors/${code.toLowerCase()}.md`,
|
|
123
|
+
codes: {
|
|
124
|
+
TSDSG0001: {
|
|
125
|
+
message: "The `tsdownConfigResolved` hook was not called.",
|
|
126
|
+
why: "The `tsdownConfigResolved` hook is only available since `tsdown@0.21.9`. Your version of tsdown may not support it.",
|
|
127
|
+
fix: "Upgrade tsdown to `0.21.9` or later."
|
|
128
|
+
},
|
|
129
|
+
TSDSG0002: {
|
|
130
|
+
message: (p) => `Build is stale in \`${p.root}\`. ${p.changeCount} file${p.changeCount === 1 ? "" : "s"} changed since last build.`,
|
|
131
|
+
why: "Source files, config, or dependencies have changed since the last build.",
|
|
132
|
+
fix: "Run your build script (e.g. `npm run build`) or `tsdown` directly to rebuild the project."
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
const log = createLogger({
|
|
137
|
+
diagnostics: [diagnostics],
|
|
138
|
+
reporters: consoleReporter
|
|
139
|
+
});
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/guard.ts
|
|
142
|
+
async function guardStaleBuild(options = {}) {
|
|
143
|
+
const root = options.root || process.cwd();
|
|
144
|
+
const result = await checkBuildState(options);
|
|
145
|
+
if (result.stale) return log.TSDSG0002({
|
|
146
|
+
root,
|
|
147
|
+
changeCount: result.changes.length
|
|
148
|
+
}).throw();
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
export { parseHashFile as a, writeHashFile as c, hashFiles as d, checkBuildState as i, computeCompositeHash as l, diagnostics as n, readHashFile as o, log as r, serializeHashFile as s, guardStaleBuild as t, hashFile as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as _$logs_sdk0 from "logs-sdk";
|
|
1
2
|
import { TsdownPlugin } from "tsdown";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
@@ -56,6 +57,41 @@ interface CheckOptions {
|
|
|
56
57
|
//#region src/check.d.ts
|
|
57
58
|
declare function checkBuildState(options?: CheckOptions): Promise<CheckResult>;
|
|
58
59
|
//#endregion
|
|
60
|
+
//#region src/diagnostics.d.ts
|
|
61
|
+
declare const diagnostics: _$logs_sdk0.DiagnosticsResult<{
|
|
62
|
+
TSDSG0001: {
|
|
63
|
+
message: string;
|
|
64
|
+
why: string;
|
|
65
|
+
fix: string;
|
|
66
|
+
};
|
|
67
|
+
TSDSG0002: {
|
|
68
|
+
message: (p: {
|
|
69
|
+
root: string;
|
|
70
|
+
changeCount: number;
|
|
71
|
+
}) => string;
|
|
72
|
+
why: string;
|
|
73
|
+
fix: string;
|
|
74
|
+
};
|
|
75
|
+
}>;
|
|
76
|
+
declare const log: _$logs_sdk0.Logger<[_$logs_sdk0.DiagnosticsResult<{
|
|
77
|
+
TSDSG0001: {
|
|
78
|
+
message: string;
|
|
79
|
+
why: string;
|
|
80
|
+
fix: string;
|
|
81
|
+
};
|
|
82
|
+
TSDSG0002: {
|
|
83
|
+
message: (p: {
|
|
84
|
+
root: string;
|
|
85
|
+
changeCount: number;
|
|
86
|
+
}) => string;
|
|
87
|
+
why: string;
|
|
88
|
+
fix: string;
|
|
89
|
+
};
|
|
90
|
+
}>]>;
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/guard.d.ts
|
|
93
|
+
declare function guardStaleBuild(options?: CheckOptions): Promise<void>;
|
|
94
|
+
//#endregion
|
|
59
95
|
//#region src/lockfile.d.ts
|
|
60
96
|
declare function serializeHashFile(data: StaleGuardData): string;
|
|
61
97
|
declare function parseHashFile(content: string): StaleGuardData;
|
|
@@ -64,4 +100,4 @@ declare function readHashFile(path: string): Promise<StaleGuardData>;
|
|
|
64
100
|
//#region src/plugin.d.ts
|
|
65
101
|
declare function StaleGuardRecorder(options?: StaleGuardRecorderOptions): TsdownPlugin;
|
|
66
102
|
//#endregion
|
|
67
|
-
export { type CheckChange, type CheckOptions, type CheckResult, type StaleGuardData, type StaleGuardEntry, StaleGuardRecorder, type StaleGuardRecorderOptions, checkBuildState, parseHashFile, readHashFile, serializeHashFile };
|
|
103
|
+
export { type CheckChange, type CheckOptions, type CheckResult, type StaleGuardData, type StaleGuardEntry, StaleGuardRecorder, type StaleGuardRecorderOptions, checkBuildState, diagnostics, guardStaleBuild, log, parseHashFile, readHashFile, serializeHashFile };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as parseHashFile, c as writeHashFile, d as hashFiles, i as checkBuildState, l as computeCompositeHash, n as diagnostics, o as readHashFile, r as log, s as serializeHashFile, t as guardStaleBuild, u as hashFile } from "./guard-CAD3_wn0.mjs";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { readdir } from "node:fs/promises";
|
|
@@ -54,7 +54,7 @@ function StaleGuardRecorder(options = {}) {
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
async writeBundle() {
|
|
57
|
-
if (!isTsdownConfigResolvedCalled)
|
|
57
|
+
if (!isTsdownConfigResolvedCalled) return log.TSDSG0001().throw();
|
|
58
58
|
const resolvedHashFile = resolve(root, hashFilePath);
|
|
59
59
|
const sources = await hashFiles([...sourceIds].filter((f) => existsSync(f)), root);
|
|
60
60
|
let outputs = [];
|
|
@@ -105,4 +105,4 @@ function toForwardSlash(p) {
|
|
|
105
105
|
return p.replace(/\\/g, "/");
|
|
106
106
|
}
|
|
107
107
|
//#endregion
|
|
108
|
-
export { StaleGuardRecorder, checkBuildState, parseHashFile, readHashFile, serializeHashFile };
|
|
108
|
+
export { StaleGuardRecorder, checkBuildState, diagnostics, guardStaleBuild, log, parseHashFile, readHashFile, serializeHashFile };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsdown-stale-guard",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"description": "Build freshness validation for tsdown",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"cac": "^7.0.0",
|
|
40
|
+
"logs-sdk": "0.0.6",
|
|
40
41
|
"yaml": "^2.8.3"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
@@ -49,10 +50,11 @@
|
|
|
49
50
|
"lint-staged": "^16.4.0",
|
|
50
51
|
"publint": "^0.3.18",
|
|
51
52
|
"simple-git-hooks": "^2.13.1",
|
|
53
|
+
"skills-npm": "^1.1.1",
|
|
52
54
|
"tsdown": "^0.21.9",
|
|
53
|
-
"tsnapi": "^0.3.
|
|
55
|
+
"tsnapi": "^0.3.2",
|
|
54
56
|
"tsx": "^4.21.0",
|
|
55
|
-
"typescript": "^6.0.
|
|
57
|
+
"typescript": "^6.0.3",
|
|
56
58
|
"vite": "^8.0.8",
|
|
57
59
|
"vitest": "^4.1.4"
|
|
58
60
|
},
|