Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/loud-windows-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuzdev/gro': minor
---

switch to blake3 hashing, add peer dep `@fuzdev/blake3_wasm`
39 changes: 31 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"zod": "^4.3.6"
},
"peerDependencies": {
"@fuzdev/blake3_wasm": "^0.1.0",
"@fuzdev/fuz_util": ">=0.52.1",
"@sveltejs/kit": "^2",
"esbuild": "^0.27.0",
Expand All @@ -80,10 +81,11 @@
"devDependencies": {
"@changesets/changelog-git": "^0.2.1",
"@changesets/types": "^6.1.0",
"@fuzdev/blake3_wasm": "^0.1.0",
"@fuzdev/fuz_code": "^0.45.1",
"@fuzdev/fuz_css": "^0.54.0",
"@fuzdev/fuz_css": "^0.55.0",
"@fuzdev/fuz_ui": "^0.185.2",
"@fuzdev/fuz_util": "^0.52.1",
"@fuzdev/fuz_util": "^0.53.0",
"@jridgewell/trace-mapping": "^0.3.31",
"@ryanatkn/eslint-config": "^0.9.0",
"@sveltejs/adapter-static": "^3.0.10",
Expand Down
2 changes: 1 addition & 1 deletion src/docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ build_cache_config: () => ({
build_cache_config: async () => ({
data_version: await fs.promises.readFile('data/version.txt', 'utf-8'),
// hash large files instead of including content
schema_hash: await hash_secure(await fs.promises.readFile('schema.sql')),
schema_hash: hash_blake3(await fs.promises.readFile('schema.sql')),
})
```

Expand Down
12 changes: 6 additions & 6 deletions src/lib/build_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {Logger} from '@fuzdev/fuz_util/log.js';
import {git_current_commit_hash} from '@fuzdev/fuz_util/git.js';
import {fs_exists} from '@fuzdev/fuz_util/fs.js';
import {map_concurrent} from '@fuzdev/fuz_util/async.js';
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
import {hash_blake3} from '@fuzdev/fuz_util/hash_blake3.js';

import type {GroConfig} from './gro_config.ts';
import {paths} from './paths.ts';
Expand Down Expand Up @@ -172,7 +172,7 @@ export const validate_build_cache = async (metadata: BuildCacheMetadata): Promis
const results = await map_concurrent(metadata.outputs, 20, async (output) => {
try {
const contents = await readFile(output.path);
const actual_hash = await hash_secure(contents);
const actual_hash = hash_blake3(contents);
return actual_hash === output.hash;
} catch {
// File deleted/inaccessible between checks = cache invalid
Expand Down Expand Up @@ -245,7 +245,7 @@ export const collect_build_outputs = async (
cache_key: string;
}

const files_hash_secure: Array<FileEntry> = [];
const files_to_hash: Array<FileEntry> = [];

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

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

return {
path: cache_key,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/filer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {UnreachableError} from '@fuzdev/fuz_util/error.js';
import type {Logger} from '@fuzdev/fuz_util/log.js';
import type {PackageJson} from '@fuzdev/fuz_util/package_json.js';
import type {FileFilter, PathId} from '@fuzdev/fuz_util/path.js';
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
import {hash_blake3} from '@fuzdev/fuz_util/hash_blake3.js';

import {
watch_dir,
Expand Down Expand Up @@ -244,7 +244,7 @@ export class Filer {
}

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

file.ctime = stats?.ctimeMs ?? null;
file.mtime = stats?.mtimeMs ?? null;
Expand Down
8 changes: 4 additions & 4 deletions src/lib/gro_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {fs_exists} from '@fuzdev/fuz_util/fs.js';
import {identity} from '@fuzdev/fuz_util/function.js';
import type {PathFilter, PathId} from '@fuzdev/fuz_util/path.js';
import {json_stringify_deterministic} from '@fuzdev/fuz_util/json.js';
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
import {hash_blake3} from '@fuzdev/fuz_util/hash_blake3.js';

import {GRO_DIST_DIR, IS_THIS_GRO, paths} from './paths.ts';
import {
Expand All @@ -22,11 +22,11 @@ import type {ParsedSvelteConfig} from './svelte_config.ts';
import type {FilerOptions} from './filer.ts';

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

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

// Hash the JSON representation with deterministic key ordering
build_cache_config_hash = await hash_secure(json_stringify_deterministic(resolved));
build_cache_config_hash = hash_blake3(json_stringify_deterministic(resolved));
}

// Delete the raw value to ensure it doesn't persist in memory
Expand Down
4 changes: 2 additions & 2 deletions src/lib/gro_plugin_deno_compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import {spawn, spawn_result_to_message} from '@fuzdev/fuz_util/process.js';
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
import {hash_sha256} from '@fuzdev/fuz_util/hash.js';
import type {Plugin} from './plugin.js';
import {TaskError} from './task.js';
import {join, relative} from 'node:path';
Expand Down Expand Up @@ -128,7 +128,7 @@ export const gro_plugin_deno_compile = (options: GroPluginDenoCompileOptions): P
// Generate SHA-256 hash file for verification
if (generate_hash) {
const binary = await readFile(output_path);
const hash = await hash_secure(binary, 'SHA-256');
const hash = await hash_sha256(binary);
const hash_path = `${output_path}.sha256`;
// Use sha256sum format: "<hash> <filename>"
await writeFile(hash_path, `${hash} ${output_name}\n`);
Expand Down
10 changes: 6 additions & 4 deletions src/routes/library.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"zod": "^4.3.6"
},
"peerDependencies": {
"@fuzdev/blake3_wasm": "^0.1.0",
"@fuzdev/fuz_util": ">=0.52.1",
"@sveltejs/kit": "^2",
"esbuild": "^0.27.0",
Expand All @@ -91,10 +92,11 @@
"devDependencies": {
"@changesets/changelog-git": "^0.2.1",
"@changesets/types": "^6.1.0",
"@fuzdev/blake3_wasm": "^0.1.0",
"@fuzdev/fuz_code": "^0.45.1",
"@fuzdev/fuz_css": "^0.54.0",
"@fuzdev/fuz_css": "^0.55.0",
"@fuzdev/fuz_ui": "^0.185.2",
"@fuzdev/fuz_util": "^0.52.1",
"@fuzdev/fuz_util": "^0.53.0",
"@jridgewell/trace-mapping": "^0.3.31",
"@ryanatkn/eslint-config": "^0.9.0",
"@sveltejs/adapter-static": "^3.0.10",
Expand Down Expand Up @@ -2655,9 +2657,9 @@
{
"name": "EMPTY_BUILD_CACHE_CONFIG_HASH",
"kind": "variable",
"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.",
"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.",
"source_line": 28,
"type_signature": "\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\""
"type_signature": "\"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262\""
},
{
"name": "GroConfig",
Expand Down
12 changes: 6 additions & 6 deletions src/test/build_cache.cache_validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ vi.mock('@fuzdev/fuz_util/fs.js', () => ({
fs_exists: vi.fn(),
}));

vi.mock('@fuzdev/fuz_util/hash.js', () => ({
hash_secure: vi.fn(),
vi.mock('@fuzdev/fuz_util/hash_blake3.js', () => ({
hash_blake3: vi.fn(),
}));

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

const metadata = create_mock_build_cache_metadata({
git_commit: 'abc123',
Expand All @@ -60,7 +60,7 @@ describe('is_build_cache_valid', () => {
vi.mocked(fs_exists).mockResolvedValue(true);
vi.mocked(readFile).mockResolvedValue(JSON.stringify(metadata));
vi.mocked(git_current_commit_hash).mockResolvedValue('abc123');
vi.mocked(hash_secure).mockResolvedValue('jkl012');
vi.mocked(hash_blake3).mockReturnValue('jkl012');

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

const metadata = create_mock_build_cache_metadata({
git_commit: 'abc123',
Expand All @@ -122,7 +122,7 @@ describe('is_build_cache_valid', () => {
vi.mocked(fs_exists).mockResolvedValue(true);
vi.mocked(readFile).mockResolvedValue(JSON.stringify(metadata));
vi.mocked(git_current_commit_hash).mockResolvedValue('abc123');
vi.mocked(hash_secure).mockResolvedValue('new_config_hash');
vi.mocked(hash_blake3).mockReturnValue('new_config_hash');

const config = await create_mock_config({
build_cache_config: {changed: true},
Expand Down
12 changes: 6 additions & 6 deletions src/test/build_cache.concurrency.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ vi.mock('@fuzdev/fuz_util/fs.js', () => ({
fs_exists: vi.fn(),
}));

vi.mock('@fuzdev/fuz_util/hash.js', () => ({
hash_secure: vi.fn(),
vi.mock('@fuzdev/fuz_util/hash_blake3.js', () => ({
hash_blake3: vi.fn(),
}));

describe('race condition: cache file modification during validation', () => {
Expand All @@ -50,7 +50,7 @@ describe('race condition: cache file modification during validation', () => {
const {fs_exists} = vi.mocked(await import('@fuzdev/fuz_util/fs.js'));
const {readFile} = vi.mocked(await import('node:fs/promises'));
const {git_current_commit_hash} = await import('@fuzdev/fuz_util/git.js');
const {hash_secure} = await import('@fuzdev/fuz_util/hash.js');
const {hash_blake3} = await import('@fuzdev/fuz_util/hash_blake3.js');

const initial_metadata = create_mock_build_cache_metadata({git_commit: 'abc123'});
const modified_metadata = create_mock_build_cache_metadata({git_commit: 'def456'});
Expand All @@ -67,7 +67,7 @@ describe('race condition: cache file modification during validation', () => {

vi.mocked(fs_exists).mockResolvedValue(true);
vi.mocked(git_current_commit_hash).mockResolvedValue('abc123');
vi.mocked(hash_secure).mockResolvedValue('hash123');
vi.mocked(hash_blake3).mockReturnValue('hash123');

const config = await create_mock_config();
const log = create_mock_logger();
Expand Down Expand Up @@ -106,14 +106,14 @@ describe('race condition: cache file modification during validation', () => {
const {fs_exists} = vi.mocked(await import('@fuzdev/fuz_util/fs.js'));
const {readFile} = vi.mocked(await import('node:fs/promises'));
const {git_current_commit_hash} = await import('@fuzdev/fuz_util/git.js');
const {hash_secure} = await import('@fuzdev/fuz_util/hash.js');
const {hash_blake3} = await import('@fuzdev/fuz_util/hash_blake3.js');

const metadata = create_mock_build_cache_metadata({git_commit: 'abc123'});

vi.mocked(fs_exists).mockResolvedValue(true);
vi.mocked(readFile).mockResolvedValue(JSON.stringify(metadata));
vi.mocked(git_current_commit_hash).mockResolvedValue('abc123');
vi.mocked(hash_secure).mockResolvedValue('hash123');
vi.mocked(hash_blake3).mockReturnValue('hash123');

const config = await create_mock_config();
const log = create_mock_logger();
Expand Down
Loading