Skip to content

Commit 17aaaef

Browse files
committed
refactor(ci): simplify generated WASM recovery
1 parent edb92d0 commit 17aaaef

7 files changed

Lines changed: 123 additions & 415 deletions

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

Lines changed: 30 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -7,121 +7,65 @@ const srcDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
77
const wasmDir = path.join(srcDir, "node_modules", "tree-sitter-wasms", "out")
88
const distDir = path.join(srcDir, "dist")
99
const wasmPattern = /^tree-sitter-.*\.wasm$/
10-
const temporaryPattern = /^tree-sitter-.*\.wasm\.\d+\.tmp$/
11-
12-
export function cleanPublishedTreeSitterWasms(destinationDir, filesystem = fs) {
13-
if (!filesystem.existsSync(destinationDir)) return
14-
for (const filename of filesystem.readdirSync(destinationDir)) {
15-
if (wasmPattern.test(filename) || temporaryPattern.test(filename)) {
16-
filesystem.rmSync(path.join(destinationDir, filename), { force: true })
17-
}
18-
}
19-
}
2010

2111
export async function publishTreeSitterWasms(
2212
sourceDir,
2313
destinationDir,
2414
{ filesystem = fs.promises, onStep = async () => {}, signalState = { requested: undefined } } = {},
2515
) {
26-
const transactionDir = `${destinationDir}.tree-sitter-wasms-transaction`
27-
const stagedDir = path.join(transactionDir, "staged")
28-
const backupDir = path.join(transactionDir, "backup")
29-
const quarantineDir = path.join(transactionDir, "quarantine")
16+
const stagingDir = `${destinationDir}.tree-sitter-wasms-staging`
3017
const sourceFiles = (await filesystem.readdir(sourceDir)).filter((filename) => wasmPattern.test(filename)).sort()
3118
if (sourceFiles.length === 0) throw new Error("WASM source set is empty")
32-
const step = async (name, filename) => {
19+
const checkpoint = async (name, filename) => {
3320
await onStep(name, filename)
34-
if (signalState.requested) throw Object.assign(new Error("WASM publication cancelled"), { code: "CANCELLED" })
21+
if (signalState.requested) throw new Error("WASM publication cancelled")
3522
}
3623

37-
let commitStarted = false
38-
let committed = false
39-
let ownsTransaction = false
40-
const publishedFiles = []
24+
await filesystem.rm(stagingDir, { recursive: true, force: true })
25+
await filesystem.mkdir(stagingDir, { recursive: true })
26+
await filesystem.mkdir(destinationDir, { recursive: true })
4127
try {
42-
await filesystem.mkdir(destinationDir, { recursive: true })
43-
await step("initialized", destinationDir)
44-
await filesystem.mkdir(transactionDir)
45-
ownsTransaction = true
46-
await step("initialized", transactionDir)
47-
await filesystem.mkdir(stagedDir)
48-
await step("initialized", stagedDir)
49-
await filesystem.mkdir(backupDir)
50-
await step("initialized", backupDir)
51-
await filesystem.mkdir(quarantineDir)
52-
await step("initialized", quarantineDir)
53-
5428
for (const filename of sourceFiles) {
55-
await filesystem.copyFile(path.join(sourceDir, filename), path.join(stagedDir, filename))
56-
await step("staged", filename)
29+
await filesystem.copyFile(path.join(sourceDir, filename), path.join(stagingDir, filename))
30+
await checkpoint("staged", filename)
5731
}
58-
59-
const previousFiles = (await filesystem.readdir(destinationDir)).filter((filename) =>
60-
wasmPattern.test(filename),
61-
)
62-
commitStarted = true
63-
for (const filename of previousFiles) {
64-
await filesystem.rename(path.join(destinationDir, filename), path.join(backupDir, filename))
65-
await step("backed-up", filename)
66-
}
67-
for (const filename of sourceFiles) {
68-
await filesystem.rename(path.join(stagedDir, filename), path.join(destinationDir, filename))
69-
publishedFiles.push(filename)
70-
await step("published", filename)
32+
for (const filename of await filesystem.readdir(stagingDir)) {
33+
if (!wasmPattern.test(filename)) throw new Error(`Unexpected staged WASM: ${filename}`)
7134
}
72-
const destinationFiles = await filesystem.readdir(destinationDir)
73-
await step("inspected-temporaries", destinationDir)
74-
for (const filename of destinationFiles) {
75-
if (temporaryPattern.test(filename)) {
35+
await checkpoint("validated", stagingDir)
36+
37+
for (const filename of await filesystem.readdir(destinationDir)) {
38+
if (wasmPattern.test(filename) || (filename.includes("tree-sitter-") && filename.endsWith(".tmp"))) {
7639
await filesystem.rm(path.join(destinationDir, filename), { force: true })
77-
await step("removed-temporary", filename)
7840
}
7941
}
80-
81-
committed = true
82-
await filesystem.rm(transactionDir, { recursive: true, force: true })
83-
return { sourceFiles, cleanup: () => cleanPublishedTreeSitterWasms(destinationDir) }
84-
} catch (error) {
85-
if (committed) throw error
86-
if (!commitStarted && ownsTransaction) {
87-
await filesystem.rm(transactionDir, { recursive: true, force: true })
88-
}
89-
if (!commitStarted) throw error
90-
91-
const failures = []
92-
for (const filename of publishedFiles) {
93-
try {
94-
await filesystem.rename(path.join(destinationDir, filename), path.join(quarantineDir, filename))
95-
} catch (rollbackError) {
96-
if (rollbackError.code !== "ENOENT") failures.push(rollbackError)
97-
}
42+
for (const filename of sourceFiles) {
43+
await filesystem.rename(path.join(stagingDir, filename), path.join(destinationDir, filename))
44+
await checkpoint("published", filename)
9845
}
99-
for (const filename of await filesystem.readdir(backupDir)) {
100-
try {
101-
await filesystem.rename(path.join(backupDir, filename), path.join(destinationDir, filename))
102-
await onStep("restored", filename)
103-
} catch (rollbackError) {
104-
failures.push(rollbackError)
46+
await checkpoint("completed", destinationDir)
47+
} catch (error) {
48+
try {
49+
for (const filename of await filesystem.readdir(destinationDir)) {
50+
if (wasmPattern.test(filename))
51+
await filesystem.rm(path.join(destinationDir, filename), { force: true })
10552
}
106-
}
107-
if (failures.length > 0) {
53+
} catch (cleanupError) {
10854
throw new AggregateError(
109-
[error, ...failures],
110-
`WASM rollback incomplete; recovery retained at ${transactionDir}`,
55+
[error, cleanupError],
56+
`WASM publication and cleanup failed; remove ${stagingDir} and rerun to rebuild`,
11157
)
11258
}
113-
await filesystem.rm(transactionDir, { recursive: true, force: true })
11459
throw error
60+
} finally {
61+
await filesystem.rm(stagingDir, { recursive: true, force: true })
11562
}
63+
return { sourceFiles }
11664
}
11765

11866
if (process.argv[1] === fileURLToPath(import.meta.url)) {
11967
const signalState = { requested: undefined }
120-
for (const signal of ["SIGINT", "SIGTERM"]) {
121-
process.on(signal, () => {
122-
signalState.requested ??= signal
123-
})
124-
}
68+
for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => (signalState.requested ??= signal))
12569
try {
12670
await publishTreeSitterWasms(wasmDir, distDir, { signalState })
12771
if (signalState.requested) process.exitCode = signalState.requested === "SIGINT" ? 130 : 143

0 commit comments

Comments
 (0)