tsdown-stale-guard 0.1.0 → 0.1.2

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 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 checkBuildState } from "./check-CWbzg4YR.mjs";
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
- const result = await checkBuildState({ hashFile: options.hashFile });
9
- if (result.fresh) {
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
- export { writeHashFile as a, hashFiles as c, serializeHashFile as i, parseHashFile as n, computeCompositeHash as o, readHashFile as r, hashFile as s, checkBuildState as t };
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 writeHashFile, c as hashFiles, i as serializeHashFile, n as parseHashFile, o as computeCompositeHash, r as readHashFile, s as hashFile, t as checkBuildState } from "./check-CWbzg4YR.mjs";
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) throw new Error("tsdownConfigResolved is not being called correctly. `tsdown-stale-guard` requires `tsdown@0.21.9` or later, please check your tsdown version.");
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.0",
4
+ "version": "0.1.2",
5
5
  "description": "Build freshness validation for tsdown",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -33,28 +33,31 @@
33
33
  "dist"
34
34
  ],
35
35
  "peerDependencies": {
36
- "tsdown": "^0.21.9"
36
+ "tsdown": "^0.21.9 || ^0.22.0"
37
37
  },
38
38
  "dependencies": {
39
39
  "cac": "^7.0.0",
40
- "yaml": "^2.8.3"
40
+ "logs-sdk": "0.0.6",
41
+ "yaml": "^2.8.4"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@antfu/eslint-config": "^8.2.0",
44
- "@antfu/ni": "^30.0.0",
45
+ "@antfu/ni": "^30.1.0",
45
46
  "@antfu/utils": "^9.3.0",
46
- "@types/node": "^25.6.0",
47
- "bumpp": "^11.0.1",
48
- "eslint": "^10.2.0",
49
- "lint-staged": "^16.4.0",
50
- "publint": "^0.3.18",
47
+ "@types/node": "^25.6.2",
48
+ "bumpp": "^11.1.0",
49
+ "eslint": "^10.3.0",
50
+ "lint-staged": "^17.0.4",
51
+ "publint": "^0.3.20",
51
52
  "simple-git-hooks": "^2.13.1",
52
- "tsdown": "^0.21.9",
53
- "tsnapi": "^0.3.0",
53
+ "skills-npm": "^1.1.1",
54
+ "tsdown": "^0.22.0",
55
+ "tsnapi": "^0.3.2",
54
56
  "tsx": "^4.21.0",
55
- "typescript": "^6.0.2",
56
- "vite": "^8.0.8",
57
- "vitest": "^4.1.4"
57
+ "typescript": "^6.0.3",
58
+ "unrun": "^0.3.0",
59
+ "vite": "^8.0.11",
60
+ "vitest": "^4.1.5"
58
61
  },
59
62
  "simple-git-hooks": {
60
63
  "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm run build && npx lint-staged"