Skip to content

Commit 11d99af

Browse files
committed
fix(ci): preserve verified WASM artifacts
1 parent ffdcd63 commit 11d99af

5 files changed

Lines changed: 91 additions & 13 deletions

File tree

src/scripts/copy-tree-sitter-wasms.mjs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,26 @@ export function publishTreeSitterWasms(sourceDir, destinationDir, filesystem = f
2626
const cleanup = () => cleanPublishedTreeSitterWasms(destinationDir, filesystem)
2727

2828
filesystem.mkdirSync(destinationDir, { recursive: true })
29+
const previousFiles = new Map(
30+
filesystem
31+
.readdirSync(destinationDir)
32+
.filter((filename) => wasmPattern.test(filename))
33+
.map((filename) => [filename, filesystem.readFileSync(path.join(destinationDir, filename))]),
34+
)
2935
for (const filename of filesystem.readdirSync(destinationDir)) {
3036
if (temporaryPattern.test(filename)) filesystem.rmSync(path.join(destinationDir, filename), { force: true })
3137
}
3238

39+
const temporaryFiles = []
3340
try {
3441
for (const filename of sourceFiles) {
3542
const destination = path.join(destinationDir, filename)
3643
const temporary = `${destination}.${process.pid}.tmp`
37-
38-
try {
39-
filesystem.copyFileSync(path.join(sourceDir, filename), temporary)
40-
filesystem.renameSync(temporary, destination)
41-
} finally {
42-
filesystem.rmSync(temporary, { force: true })
43-
}
44+
temporaryFiles.push(temporary)
45+
filesystem.copyFileSync(path.join(sourceDir, filename), temporary)
46+
}
47+
for (const [index, filename] of sourceFiles.entries()) {
48+
filesystem.renameSync(temporaryFiles[index], path.join(destinationDir, filename))
4449
}
4550

4651
for (const filename of filesystem.readdirSync(destinationDir)) {
@@ -50,7 +55,12 @@ export function publishTreeSitterWasms(sourceDir, destinationDir, filesystem = f
5055
}
5156
} catch (error) {
5257
cleanup()
58+
for (const [filename, content] of previousFiles) {
59+
filesystem.writeFileSync(path.join(destinationDir, filename), content)
60+
}
5361
throw error
62+
} finally {
63+
for (const temporary of temporaryFiles) filesystem.rmSync(temporary, { force: true })
5464
}
5565

5666
return { sourceFiles, cleanup }

src/scripts/copy-tree-sitter-wasms.spec.mjs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ describe("publishTreeSitterWasms", () => {
3030
expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("a")
3131
})
3232

33-
it("removes published and temporary outputs when publication fails", () => {
33+
it("restores published outputs when publication fails", () => {
3434
const source = path.join(root, "source")
3535
const destination = path.join(root, "dist")
3636
fs.mkdirSync(source)
37+
fs.mkdirSync(destination)
3738
fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "a")
3839
fs.writeFileSync(path.join(source, "tree-sitter-b.wasm"), "b")
40+
fs.writeFileSync(path.join(destination, "tree-sitter-existing.wasm"), "existing")
3941
let copies = 0
4042
const filesystem = {
4143
...fs,
@@ -46,22 +48,31 @@ describe("publishTreeSitterWasms", () => {
4648
}
4749

4850
expect(() => publishTreeSitterWasms(source, destination, filesystem)).toThrow("copy failed")
49-
expect(fs.readdirSync(destination)).toEqual([])
51+
expect(fs.readdirSync(destination)).toEqual(["tree-sitter-existing.wasm"])
52+
expect(fs.readFileSync(path.join(destination, "tree-sitter-existing.wasm"), "utf8")).toBe("existing")
5053
})
5154

52-
it("removes published and temporary outputs when an atomic rename fails", () => {
55+
it("restores published outputs when an atomic rename fails", () => {
5356
const source = path.join(root, "source")
5457
const destination = path.join(root, "dist")
5558
fs.mkdirSync(source)
59+
fs.mkdirSync(destination)
5660
fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "a")
61+
fs.writeFileSync(path.join(source, "tree-sitter-b.wasm"), "b")
62+
fs.writeFileSync(path.join(destination, "tree-sitter-a.wasm"), "previous-a")
63+
fs.writeFileSync(path.join(destination, "tree-sitter-b.wasm"), "previous-b")
64+
let renames = 0
5765
const filesystem = {
5866
...fs,
59-
renameSync() {
60-
throw new Error("rename failed")
67+
renameSync(...args) {
68+
if (++renames === 2) throw new Error("rename failed")
69+
return fs.renameSync(...args)
6170
},
6271
}
6372

6473
expect(() => publishTreeSitterWasms(source, destination, filesystem)).toThrow("rename failed")
65-
expect(fs.readdirSync(destination)).toEqual([])
74+
expect(fs.readdirSync(destination)).toEqual(["tree-sitter-a.wasm", "tree-sitter-b.wasm"])
75+
expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("previous-a")
76+
expect(fs.readFileSync(path.join(destination, "tree-sitter-b.wasm"), "utf8")).toBe("previous-b")
6677
})
6778
})

src/scripts/verify-coverage-contract.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import path from "node:path"
44
import process from "node:process"
55
import { fileURLToPath } from "node:url"
66

7+
import { assertMatchingFiles } from "./verify-wasm-files.mjs"
8+
79
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..")
810
const pnpm = process.platform === "win32" ? process.env.npm_execpath : "pnpm"
911
if (!pnpm) throw new Error("pnpm executable path is unavailable")
@@ -60,6 +62,12 @@ try {
6062
if (source.length === 0) throw new Error("Dependency contains no tree-sitter WASMs")
6163
if (JSON.stringify(source) !== JSON.stringify(published))
6264
throw new Error("Published WASM set does not match dependency")
65+
assertMatchingFiles(
66+
path.join(root, "src", "node_modules", "tree-sitter-wasms", "out"),
67+
dist,
68+
source,
69+
"Published WASM content does not match dependency",
70+
)
6371
if (fs.readdirSync(dist).some((filename) => filename.endsWith(".tmp")))
6472
throw new Error("Temporary WASM files remain")
6573

@@ -85,6 +93,12 @@ try {
8593
.filter((filename) => /^tree-sitter-.*\.wasm$/.test(filename))
8694
.sort()
8795
if (JSON.stringify(source) !== JSON.stringify(restored)) throw new Error("WASM cache did not restore exact outputs")
96+
assertMatchingFiles(
97+
path.join(root, "src", "node_modules", "tree-sitter-wasms", "out"),
98+
dist,
99+
source,
100+
"WASM cache restored corrupted output",
101+
)
88102
} finally {
89103
fs.rmSync(cacheDir, { recursive: true, force: true })
90104
}

src/scripts/verify-wasm-files.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import fs from "node:fs"
2+
import path from "node:path"
3+
4+
export function assertMatchingFiles(expectedDir, actualDir, filenames, message, filesystem = fs) {
5+
for (const filename of filenames) {
6+
if (
7+
!filesystem
8+
.readFileSync(path.join(expectedDir, filename))
9+
.equals(filesystem.readFileSync(path.join(actualDir, filename)))
10+
) {
11+
throw new Error(`${message}: ${filename}`)
12+
}
13+
}
14+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import fs from "node:fs"
2+
import os from "node:os"
3+
import path from "node:path"
4+
import { afterEach, beforeEach, describe, expect, it } from "vitest"
5+
6+
import { assertMatchingFiles } from "./verify-wasm-files.mjs"
7+
8+
describe("assertMatchingFiles", () => {
9+
let root
10+
11+
beforeEach(() => {
12+
root = fs.mkdtempSync(path.join(os.tmpdir(), "verify-wasm-files-"))
13+
})
14+
15+
afterEach(() => fs.rmSync(root, { recursive: true, force: true }))
16+
17+
it("rejects a restored WASM with corrupted content", () => {
18+
const expected = path.join(root, "expected")
19+
const actual = path.join(root, "actual")
20+
fs.mkdirSync(expected)
21+
fs.mkdirSync(actual)
22+
fs.writeFileSync(path.join(expected, "tree-sitter-a.wasm"), "expected")
23+
fs.writeFileSync(path.join(actual, "tree-sitter-a.wasm"), "corrupted")
24+
25+
expect(() => assertMatchingFiles(expected, actual, ["tree-sitter-a.wasm"], "WASM mismatch")).toThrow(
26+
"WASM mismatch: tree-sitter-a.wasm",
27+
)
28+
})
29+
})

0 commit comments

Comments
 (0)