Compare commits

..
1 Commits
Author SHA1 Message Date
sneak ea15c76af7 Harden the backup tree's atomic copy (closes #22)
check / check (push) Successful in 28s
The backup copy now fsyncs its temp file before the rename and the
directory after it, using the download writer's new fsyncPath helper.
Each backup run deletes .quak-backup-*.tmp files whose process is no
longer running, leaving those of a concurrent backup alone. The rename
sites and the README backup layout state that a symlink at the
destination is replaced and the new file takes the temp file's
permissions, and the README names the temp files. Adds tests for a
missing and an unwritable destination directory for downloadFile and
downloadThumbnail.

Model: opus-5-5
2026-09-23 00:12:15 +00:00
6 changed files with 9 additions and 92 deletions
-9
View File
@@ -25,15 +25,6 @@ Tag v1.0.0.
temp files and states that the rename replaces a symlink and takes the temp
file's permissions. Added tests for a missing and an unwritable destination
directory for `downloadFile` and `downloadThumbnail`.
- 2026-09-22: Stopped `make test` collecting tests from checkouts nested under
`.claude/` (issue 25). vitest ignores `.gitignore` when finding tests, so a
nested checkout ran the whole suite again; `vitest.config.ts` now adds
`.claude/**` to vitest's default excludes, and
`test/packaging/nested-checkout.test.ts` plants a nested checkout in a temp
directory and fails if vitest would collect it.
- 2026-09-22: Dropped the deprecated `@types/libsodium-wrappers-sumo` stub from
`devDependencies` (issue 27). It shipped no declarations; the types come from
`libsodium-wrappers-sumo` itself. `yarn.lock` regenerated by `yarn remove`.
- 2026-09-22: Hardened the client session lifecycle (issue 10).
`Client.fromJSON` checks every snapshot field and each key's decoded length
and names the bad field; `toJSON` reads the token through
+1
View File
@@ -29,6 +29,7 @@
},
"devDependencies": {
"@eslint/js": "9.38.0",
"@types/libsodium-wrappers-sumo": "0.8.2",
"@types/node": "22.18.13",
"eslint": "9.38.0",
"prettier": "3.8.1",
-22
View File
@@ -599,26 +599,4 @@ describe("lib.backup", () => {
expect(names).toContain(inProgress);
lib.close();
});
it("removes leftover temp files in thumbnails/ but not those of a backup still running", async () => {
const outDir = join(root, "backup");
const thumbnails = join(outDir, "thumbnails");
mkdirSync(thumbnails, { recursive: true });
const exitedPID = spawnSync(process.execPath, ["-e", ""]).pid;
const leftover = `.quak-backup-100.jpg-${exitedPID}-abc123.tmp`;
const inProgress = `.quak-backup-101.jpg-${process.pid}-def456.tmp`;
writeFileSync(join(thumbnails, leftover), "partial");
writeFileSync(join(thumbnails, inProgress), "partial");
const lib = await openLibrary(stubSource());
await lib.backup({
downloadDirectory: outDir,
includeThumbnails: true,
});
const names = readdirSync(thumbnails);
expect(names).not.toContain(leftover);
expect(names).toContain(inProgress);
lib.close();
});
});
-50
View File
@@ -1,50 +0,0 @@
// A checkout nested under `.claude/` must not add its tests to this suite.
// The test plants one in a temporary directory next to a real test file and
// asks vitest, with this repo's config, which test files it would run.
import { afterEach, describe, expect, it } from "vitest";
import { execFileSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = fileURLToPath(new URL("../../", import.meta.url));
let root = "";
afterEach(() => {
rmSync(root, { recursive: true, force: true });
});
const writeTest = (path: string): void => {
mkdirSync(join(root, path, ".."), { recursive: true });
writeFileSync(
join(root, path),
'import { it } from "vitest";\nit("runs", () => {});\n',
);
};
describe("vitest.config.ts", () => {
it("does not collect tests from a checkout nested under .claude/", () => {
root = mkdtempSync(join(tmpdir(), "quak-nested-checkout-"));
writeTest("test/real.test.ts");
writeTest(".claude/worktrees/other/test/real.test.ts");
const output = execFileSync(
process.execPath,
[
join(repoRoot, "node_modules/vitest/vitest.mjs"),
"list",
"--filesOnly",
"--config",
join(repoRoot, "vitest.config.ts"),
"--root",
root,
],
{ cwd: root, encoding: "utf-8" },
);
const files = output.split("\n").filter((line) => line !== "");
expect(files).toEqual(["test/real.test.ts"]);
});
});
-10
View File
@@ -1,10 +0,0 @@
import { configDefaults, defineConfig } from "vitest/config";
// vitest does not read .gitignore when looking for tests. A checkout nested
// under .claude/ has its own test/ tree, and without this exclude the suite
// runs once per nested checkout and still reports success.
export default defineConfig({
test: {
exclude: [...configDefaults.exclude, ".claude/**"],
},
});
+8 -1
View File
@@ -528,6 +528,13 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/libsodium-wrappers-sumo@0.8.2":
version "0.8.2"
resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.8.2.tgz#488e8747fbb982fe901020b5afeaddfa63da6830"
integrity sha512-uFOBpg/r21hExVlh2ty8YpDfSR+Yy3Jn8XS4+SSjitbhTxdYq+pBz/49XRxyUFe8SzqujHf/Wu0/O4d+FUtNfQ==
dependencies:
libsodium-wrappers-sumo "*"
"@types/node@22.18.13":
version "22.18.13"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.13.tgz#a037c4f474b860be660e05dbe92a9ef945472e28"
@@ -1242,7 +1249,7 @@ libsodium-sumo@^0.8.0:
resolved "https://registry.yarnpkg.com/libsodium-sumo/-/libsodium-sumo-0.8.4.tgz#6d4687781fa0ad398af14a7df872d5c27cf8cd31"
integrity sha512-TMtHShQfVVsaxDygyapvUC3o7YsPgXa/hRWeIgzyFz6w5k/1hirGptCxp1U7XwW3rCskaTTYKgV10v86UiGgNw==
libsodium-wrappers-sumo@0.8.4:
libsodium-wrappers-sumo@*, libsodium-wrappers-sumo@0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.8.4.tgz#6656a3e7e0551ecce08ddee4bfb501a092eac6fa"
integrity sha512-ql7hcgulKZ3ekfa2DGAogcCKsWU0diA/0nArz1CFzh93WQdb46/Kj18ka/Hifq6uA3Ush34Pc6vU/6HXeRwUkg==