Skip to content

Commit 620c7ad

Browse files
authored
switch to blake3 hashing (#602)
1 parent 2a0ffdf commit 620c7ad

25 files changed

Lines changed: 168 additions & 143 deletions

.changeset/loud-windows-type.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@fuzdev/gro': minor
3+
---
4+
5+
switch to blake3 hashing, add peer dep `@fuzdev/blake3_wasm`

package-lock.json

Lines changed: 31 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"zod": "^4.3.6"
6060
},
6161
"peerDependencies": {
62+
"@fuzdev/blake3_wasm": "^0.1.0",
6263
"@fuzdev/fuz_util": ">=0.52.1",
6364
"@sveltejs/kit": "^2",
6465
"esbuild": "^0.27.0",
@@ -80,10 +81,11 @@
8081
"devDependencies": {
8182
"@changesets/changelog-git": "^0.2.1",
8283
"@changesets/types": "^6.1.0",
84+
"@fuzdev/blake3_wasm": "^0.1.0",
8385
"@fuzdev/fuz_code": "^0.45.1",
84-
"@fuzdev/fuz_css": "^0.54.0",
86+
"@fuzdev/fuz_css": "^0.55.0",
8587
"@fuzdev/fuz_ui": "^0.185.2",
86-
"@fuzdev/fuz_util": "^0.52.1",
88+
"@fuzdev/fuz_util": "^0.53.0",
8789
"@jridgewell/trace-mapping": "^0.3.31",
8890
"@ryanatkn/eslint-config": "^0.9.0",
8991
"@sveltejs/adapter-static": "^3.0.10",

src/docs/config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ build_cache_config: () => ({
358358
build_cache_config: async () => ({
359359
data_version: await fs.promises.readFile('data/version.txt', 'utf-8'),
360360
// hash large files instead of including content
361-
schema_hash: await hash_secure(await fs.promises.readFile('schema.sql')),
361+
schema_hash: hash_blake3(await fs.promises.readFile('schema.sql')),
362362
})
363363
```
364364

src/lib/build_cache.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {Logger} from '@fuzdev/fuz_util/log.js';
66
import {git_current_commit_hash} from '@fuzdev/fuz_util/git.js';
77
import {fs_exists} from '@fuzdev/fuz_util/fs.js';
88
import {map_concurrent} from '@fuzdev/fuz_util/async.js';
9-
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
9+
import {hash_blake3} from '@fuzdev/fuz_util/hash_blake3.js';
1010

1111
import type {GroConfig} from './gro_config.ts';
1212
import {paths} from './paths.ts';
@@ -172,7 +172,7 @@ export const validate_build_cache = async (metadata: BuildCacheMetadata): Promis
172172
const results = await map_concurrent(metadata.outputs, 20, async (output) => {
173173
try {
174174
const contents = await readFile(output.path);
175-
const actual_hash = await hash_secure(contents);
175+
const actual_hash = hash_blake3(contents);
176176
return actual_hash === output.hash;
177177
} catch {
178178
// File deleted/inaccessible between checks = cache invalid
@@ -245,7 +245,7 @@ export const collect_build_outputs = async (
245245
cache_key: string;
246246
}
247247

248-
const files_hash_secure: Array<FileEntry> = [];
248+
const files_to_hash: Array<FileEntry> = [];
249249

250250
// Recursively collect files
251251
const collect_files = async (
@@ -269,7 +269,7 @@ export const collect_build_outputs = async (
269269
// eslint-disable-next-line no-await-in-loop
270270
await collect_files(full_path, relative_path, dir_prefix);
271271
} else if (entry.isFile()) {
272-
files_hash_secure.push({full_path, cache_key});
272+
files_to_hash.push({full_path, cache_key});
273273
}
274274
// Symlinks are intentionally ignored - we only hash regular files
275275
}
@@ -287,12 +287,12 @@ export const collect_build_outputs = async (
287287

288288
// Hash files with controlled concurrency and collect stats (could be 10k+ files)
289289
return map_concurrent(
290-
files_hash_secure,
290+
files_to_hash,
291291
20,
292292
async ({full_path, cache_key}): Promise<BuildOutputEntry> => {
293293
const stats = await stat(full_path);
294294
const contents = await readFile(full_path);
295-
const hash = await hash_secure(contents);
295+
const hash = hash_blake3(contents);
296296

297297
return {
298298
path: cache_key,

src/lib/filer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {UnreachableError} from '@fuzdev/fuz_util/error.js';
77
import type {Logger} from '@fuzdev/fuz_util/log.js';
88
import type {PackageJson} from '@fuzdev/fuz_util/package_json.js';
99
import type {FileFilter, PathId} from '@fuzdev/fuz_util/path.js';
10-
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
10+
import {hash_blake3} from '@fuzdev/fuz_util/hash_blake3.js';
1111

1212
import {
1313
watch_dir,
@@ -244,7 +244,7 @@ export class Filer {
244244
}
245245

246246
// Compute hash for new contents
247-
const new_hash = new_contents !== null ? await hash_secure(new_contents) : null;
247+
const new_hash = new_contents !== null ? hash_blake3(new_contents) : null;
248248

249249
file.ctime = stats?.ctimeMs ?? null;
250250
file.mtime = stats?.mtimeMs ?? null;

src/lib/gro_config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {fs_exists} from '@fuzdev/fuz_util/fs.js';
33
import {identity} from '@fuzdev/fuz_util/function.js';
44
import type {PathFilter, PathId} from '@fuzdev/fuz_util/path.js';
55
import {json_stringify_deterministic} from '@fuzdev/fuz_util/json.js';
6-
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
6+
import {hash_blake3} from '@fuzdev/fuz_util/hash_blake3.js';
77

88
import {GRO_DIST_DIR, IS_THIS_GRO, paths} from './paths.ts';
99
import {
@@ -22,11 +22,11 @@ import type {ParsedSvelteConfig} from './svelte_config.ts';
2222
import type {FilerOptions} from './filer.ts';
2323

2424
/**
25-
* SHA-256 hash of empty string, used for configs without build_cache_config.
25+
* BLAKE3 hash of empty string, used for configs without build_cache_config.
2626
* This ensures consistent cache behavior when no custom config is provided.
2727
*/
2828
export const EMPTY_BUILD_CACHE_CONFIG_HASH =
29-
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
29+
'af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262';
3030

3131
/**
3232
* The config that users can extend via `gro.config.ts`.
@@ -186,7 +186,7 @@ export const cook_gro_config = async (raw_config: RawGroConfig): Promise<GroConf
186186
typeof build_cache_config === 'function' ? await build_cache_config() : build_cache_config;
187187

188188
// Hash the JSON representation with deterministic key ordering
189-
build_cache_config_hash = await hash_secure(json_stringify_deterministic(resolved));
189+
build_cache_config_hash = hash_blake3(json_stringify_deterministic(resolved));
190190
}
191191

192192
// Delete the raw value to ensure it doesn't persist in memory

src/lib/gro_plugin_deno_compile.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import {spawn, spawn_result_to_message} from '@fuzdev/fuz_util/process.js';
18-
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
18+
import {hash_sha256} from '@fuzdev/fuz_util/hash.js';
1919
import type {Plugin} from './plugin.js';
2020
import {TaskError} from './task.js';
2121
import {join, relative} from 'node:path';
@@ -128,7 +128,7 @@ export const gro_plugin_deno_compile = (options: GroPluginDenoCompileOptions): P
128128
// Generate SHA-256 hash file for verification
129129
if (generate_hash) {
130130
const binary = await readFile(output_path);
131-
const hash = await hash_secure(binary, 'SHA-256');
131+
const hash = await hash_sha256(binary);
132132
const hash_path = `${output_path}.sha256`;
133133
// Use sha256sum format: "<hash> <filename>"
134134
await writeFile(hash_path, `${hash} ${output_name}\n`);

src/routes/library.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"zod": "^4.3.6"
7171
},
7272
"peerDependencies": {
73+
"@fuzdev/blake3_wasm": "^0.1.0",
7374
"@fuzdev/fuz_util": ">=0.52.1",
7475
"@sveltejs/kit": "^2",
7576
"esbuild": "^0.27.0",
@@ -91,10 +92,11 @@
9192
"devDependencies": {
9293
"@changesets/changelog-git": "^0.2.1",
9394
"@changesets/types": "^6.1.0",
95+
"@fuzdev/blake3_wasm": "^0.1.0",
9496
"@fuzdev/fuz_code": "^0.45.1",
95-
"@fuzdev/fuz_css": "^0.54.0",
97+
"@fuzdev/fuz_css": "^0.55.0",
9698
"@fuzdev/fuz_ui": "^0.185.2",
97-
"@fuzdev/fuz_util": "^0.52.1",
99+
"@fuzdev/fuz_util": "^0.53.0",
98100
"@jridgewell/trace-mapping": "^0.3.31",
99101
"@ryanatkn/eslint-config": "^0.9.0",
100102
"@sveltejs/adapter-static": "^3.0.10",
@@ -2655,9 +2657,9 @@
26552657
{
26562658
"name": "EMPTY_BUILD_CACHE_CONFIG_HASH",
26572659
"kind": "variable",
2658-
"doc_comment": "SHA-256 hash of empty string, used for configs without build_cache_config.\nThis ensures consistent cache behavior when no custom config is provided.",
2660+
"doc_comment": "BLAKE3 hash of empty string, used for configs without build_cache_config.\nThis ensures consistent cache behavior when no custom config is provided.",
26592661
"source_line": 28,
2660-
"type_signature": "\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\""
2662+
"type_signature": "\"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262\""
26612663
},
26622664
{
26632665
"name": "GroConfig",

src/test/build_cache.cache_validation.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ vi.mock('@fuzdev/fuz_util/fs.js', () => ({
3737
fs_exists: vi.fn(),
3838
}));
3939

40-
vi.mock('@fuzdev/fuz_util/hash.js', () => ({
41-
hash_secure: vi.fn(),
40+
vi.mock('@fuzdev/fuz_util/hash_blake3.js', () => ({
41+
hash_blake3: vi.fn(),
4242
}));
4343

4444
describe('is_build_cache_valid', () => {
@@ -50,7 +50,7 @@ describe('is_build_cache_valid', () => {
5050
const {git_current_commit_hash} = await import('@fuzdev/fuz_util/git.js');
5151
const {fs_exists} = vi.mocked(await import('@fuzdev/fuz_util/fs.js'));
5252
const {readFile} = vi.mocked(await import('node:fs/promises'));
53-
const {hash_secure} = await import('@fuzdev/fuz_util/hash.js');
53+
const {hash_blake3} = await import('@fuzdev/fuz_util/hash_blake3.js');
5454

5555
const metadata = create_mock_build_cache_metadata({
5656
git_commit: 'abc123',
@@ -60,7 +60,7 @@ describe('is_build_cache_valid', () => {
6060
vi.mocked(fs_exists).mockResolvedValue(true);
6161
vi.mocked(readFile).mockResolvedValue(JSON.stringify(metadata));
6262
vi.mocked(git_current_commit_hash).mockResolvedValue('abc123');
63-
vi.mocked(hash_secure).mockResolvedValue('jkl012');
63+
vi.mocked(hash_blake3).mockReturnValue('jkl012');
6464

6565
const config = await create_mock_config();
6666
const log = create_mock_logger();
@@ -112,7 +112,7 @@ describe('is_build_cache_valid', () => {
112112
const {git_current_commit_hash} = await import('@fuzdev/fuz_util/git.js');
113113
const {fs_exists} = vi.mocked(await import('@fuzdev/fuz_util/fs.js'));
114114
const {readFile} = vi.mocked(await import('node:fs/promises'));
115-
const {hash_secure} = await import('@fuzdev/fuz_util/hash.js');
115+
const {hash_blake3} = await import('@fuzdev/fuz_util/hash_blake3.js');
116116

117117
const metadata = create_mock_build_cache_metadata({
118118
git_commit: 'abc123',
@@ -122,7 +122,7 @@ describe('is_build_cache_valid', () => {
122122
vi.mocked(fs_exists).mockResolvedValue(true);
123123
vi.mocked(readFile).mockResolvedValue(JSON.stringify(metadata));
124124
vi.mocked(git_current_commit_hash).mockResolvedValue('abc123');
125-
vi.mocked(hash_secure).mockResolvedValue('new_config_hash');
125+
vi.mocked(hash_blake3).mockReturnValue('new_config_hash');
126126

127127
const config = await create_mock_config({
128128
build_cache_config: {changed: true},

0 commit comments

Comments
 (0)