-
Notifications
You must be signed in to change notification settings - Fork 16
ci: run the full offline test suite and auto-rebuild stale dist before tests #212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,34 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| // Jest globalSetup: tests import the built package from dist/, so a stale | ||||||||||||||||||||||||||||||||||||||||||||||
| // build silently tests old code. Rebuild automatically when any file under | ||||||||||||||||||||||||||||||||||||||||||||||
| // src/ (or the build inputs) is newer than dist/index.cjs. Costs nothing | ||||||||||||||||||||||||||||||||||||||||||||||
| // when dist is fresh; one tsdown run (~3s) when it is not. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const { execSync } = require("node:child_process"); | ||||||||||||||||||||||||||||||||||||||||||||||
| const fs = require("node:fs"); | ||||||||||||||||||||||||||||||||||||||||||||||
| const path = require("node:path"); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const ROOT = path.join(__dirname, ".."); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| function newestMtimeMs(entry) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const stat = fs.statSync(entry); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!stat.isDirectory()) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return stat.mtimeMs; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| let newest = 0; | ||||||||||||||||||||||||||||||||||||||||||||||
| for (const name of fs.readdirSync(entry)) { | ||||||||||||||||||||||||||||||||||||||||||||||
| newest = Math.max(newest, newestMtimeMs(path.join(entry, name))); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| return newest; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add symlink protection to prevent infinite loops. The recursive directory traversal does not check for symbolic links before descending. If 🛡️ Proposed fix to skip symlinks function newestMtimeMs(entry) {
const stat = fs.statSync(entry);
- if (!stat.isDirectory()) {
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
return stat.mtimeMs;
}
let newest = 0;Alternatively, use function newestMtimeMs(entry) {
- const stat = fs.statSync(entry);
+ const stat = fs.lstatSync(entry);
- if (!stat.isDirectory()) {
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
return stat.mtimeMs;
}
let newest = 0;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| module.exports = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const distEntry = path.join(ROOT, "dist", "index.cjs"); | ||||||||||||||||||||||||||||||||||||||||||||||
| const inputs = ["src", "package.json", "tsconfig.json", "tsdown.config.ts"] | ||||||||||||||||||||||||||||||||||||||||||||||
| .map((p) => path.join(ROOT, p)) | ||||||||||||||||||||||||||||||||||||||||||||||
| .filter((p) => fs.existsSync(p)); | ||||||||||||||||||||||||||||||||||||||||||||||
| const newestInput = Math.max(...inputs.map(newestMtimeMs)); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(distEntry) || fs.statSync(distEntry).mtimeMs < newestInput) { | ||||||||||||||||||||||||||||||||||||||||||||||
| console.log("\ndist/ is stale relative to src/ — rebuilding before tests..."); | ||||||||||||||||||||||||||||||||||||||||||||||
| execSync("npm run build", { stdio: "inherit", cwd: ROOT }); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a source file is deleted or renamed without touching another input, this recursion ignores directory mtimes and only considers the remaining files. In that refactor scenario the containing directory's mtime is the only timestamp newer than
dist/index.cjs, soglobalSetupskips the rebuild and targeted Jest runs keep testing a bundle that still contains the removed code. Seed the directory case withstat.mtimeMs(or otherwise include directory mtimes) so deletions invalidatedist/too.Useful? React with 👍 / 👎.