Skip to content
Open
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
3 changes: 3 additions & 0 deletions config/lockfile-allowlist.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"allowlist": []
}
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
}
},
"scripts": {
"preinstall": "node scripts/check-lockfile-sources.mjs",
"install:check": "node scripts/check-lockfile-sources.mjs",
"env:check": "node scripts/check-env-examples.mjs",
"env:test": "node --test scripts/check-env-examples.test.mjs",
"logging:test": "pnpm --filter @sub-rosa/logging test && node --test scripts/check-logging.test.mjs",
Expand Down Expand Up @@ -87,6 +89,13 @@
"errors:normalize:check": "node scripts/check-error-normalization.mjs",
"errors:normalize:test": "pnpm --filter @sub-rosa/logging test && node --test scripts/check-error-normalization.test.mjs",
"line-lengths:check": "node scripts/check-line-lengths.mjs",
"line-lengths:test": "node --test scripts/check-line-lengths.test.mjs"
"line-lengths:test": "node --test scripts/check-line-lengths.test.mjs",
"lockfile:check": "node scripts/check-lockfile-sources.mjs",
"lockfile:test": "node --test scripts/check-lockfile-sources.test.mjs",
"font:check": "node scripts/check-font-magic.mjs",
"font:test": "node --test scripts/check-font-magic.test.mjs"
},
"devDependencies": {
"yaml": "^2.9.1"
}
}
24 changes: 18 additions & 6 deletions pnpm-lock.yaml

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

131 changes: 131 additions & 0 deletions scripts/check-font-magic.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) 2026 Sub Rosa contributors
import { openSync, readSync, closeSync, readdirSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
import { fileURLToPath } from 'node:url';

const ROOT = fileURLToPath(new URL('..', import.meta.url));

// Magic bytes as Uint8Array for comparison
const MAGIC_WOFF = new Uint8Array([0x77, 0x4F, 0x46, 0x46]); // 'wOFF'
const MAGIC_WOFF2 = new Uint8Array([0x77, 0x4F, 0x46, 0x32]); // 'wOF2'

const MAGIC_NAMES = {
woff: 'wOFF',
woff2: 'wOF2',
};

/**
* Read exactly 4 bytes from the start of a file.
* Returns Uint8Array of length 4, or null if file too small.
*/
function readMagicBytes(filePath) {
let fd;
try {
fd = openSync(filePath, 'r');
const buffer = Buffer.alloc(4);
const bytesRead = readSync(fd, buffer, 0, 4, 0);
if (bytesRead < 4) return null;
return new Uint8Array(buffer);
} catch {
return null;
} finally {
if (fd !== undefined) closeSync(fd);
}
}

/**
* Check if a file has the correct magic bytes for its extension.
* Returns null if valid, or an error message string if invalid.
*/
function checkFontMagic(filePath, relPath) {
try {
const stat = statSync(filePath);
if (!stat.isFile()) return null;
if (stat.size < 4) {
return `${relPath}: file too small (${stat.size} bytes) to contain magic bytes`;
}

const bytes = readMagicBytes(filePath);
if (!bytes) {
return `${relPath}: unable to read magic bytes`;
}

if (relPath.endsWith('.woff2')) {
if (!bytes.every((b, i) => b === MAGIC_WOFF2[i])) {
return `${relPath}: invalid woff2 magic bytes (expected ${MAGIC_NAMES.woff2}, got ${bytesToHex(bytes)})`;
}
} else if (relPath.endsWith('.woff')) {
if (!bytes.every((b, i) => b === MAGIC_WOFF[i])) {
return `${relPath}: invalid woff magic bytes (expected ${MAGIC_NAMES.woff}, got ${bytesToHex(bytes)})`;
}
}
return null;
} catch (err) {
return `${relPath}: error reading file: ${err.message}`;
}
}

function bytesToHex(bytes) {
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ');
}

/**
* Recursively find all .woff and .woff2 files in the repo.
*/
function findFontFiles(dir, base = ROOT) {
const results = [];
try {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (['node_modules', 'dist', 'target', '.git', 'artifacts', 'tmp-bindings'].includes(entry.name)) {
continue;
}
const full = join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...findFontFiles(full, base));
} else if (entry.isFile() && (entry.name.endsWith('.woff') || entry.name.endsWith('.woff2'))) {
results.push({ full, rel: relative(base, full).replaceAll('\\', '/') });
}
}
} catch {
// Ignore unreadable directories
}
return results;
}

/**
* Main check function. Returns array of error strings (empty if all valid).
*/
export function checkFontAssets(root = ROOT) {
const fontFiles = findFontFiles(root);
const errors = [];

for (const { full, rel } of fontFiles) {
const error = checkFontMagic(full, rel);
if (error) errors.push(error);
}

return errors;
}

/**
* Format errors for CLI output.
*/
export function formatErrors(errors) {
if (errors.length === 0) {
return '✅ All WOFF/WOFF2 assets have valid magic bytes.';
}
return '❌ Invalid font assets:\n' + errors.map(e => ` ${e}`).join('\n');
}

if (process.argv[1] && process.argv[1].endsWith('check-font-magic.mjs')) {
try {
const errors = checkFontAssets();
console.log(formatErrors(errors));
if (errors.length > 0) {
process.exitCode = 1;
}
} catch (err) {
console.error(`Error: ${err.message}`);
process.exitCode = 1;
}
}
112 changes: 112 additions & 0 deletions scripts/check-font-magic.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) 2026 Sub Rosa contributors
import assert from 'node:assert/strict';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { writeFileSync, mkdtempSync, rmSync, mkdirSync } from 'node:fs';
import { test, describe, beforeEach, afterEach } from 'node:test';
import { fileURLToPath } from 'node:url';
import { checkFontAssets, formatErrors } from './check-font-magic.mjs';

let TEST_DIR;

function createFontFile(name, content) {
const path = join(TEST_DIR, name);
writeFileSync(path, content);
return path;
}

describe('checkFontAssets', () => {
beforeEach(() => {
TEST_DIR = mkdtempSync(join(tmpdir(), 'font-magic-check-'));
mkdirSync(join(TEST_DIR, 'subdir'), { recursive: true });
});

afterEach(() => {
try {
rmSync(TEST_DIR, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
});

test('valid woff2 passes', () => {
createFontFile('valid.woff2', Buffer.from([0x77, 0x4F, 0x46, 0x32, 0x00, 0x01, 0x00, 0x00]));
const errors = checkFontAssets(TEST_DIR);
assert.deepEqual(errors, []);
});

test('valid woff passes', () => {
createFontFile('valid.woff', Buffer.from([0x77, 0x4F, 0x46, 0x46, 0x00, 0x01, 0x00, 0x00]));
const errors = checkFontAssets(TEST_DIR);
assert.deepEqual(errors, []);
});

test('text file as woff2 fails', () => {
createFontFile('fake.woff2', 'this is not a font');
const errors = checkFontAssets(TEST_DIR);
assert.equal(errors.length, 1);
assert.match(errors[0], /invalid woff2 magic bytes/);
assert.match(errors[0], /fake\.woff2/);
});

test('text file as woff fails', () => {
createFontFile('fake.woff', 'also not a font');
const errors = checkFontAssets(TEST_DIR);
assert.equal(errors.length, 1);
assert.match(errors[0], /invalid woff magic bytes/);
assert.match(errors[0], /fake\.woff/);
});

test('too small file fails', () => {
createFontFile('tiny.woff2', 'wof');
const errors = checkFontAssets(TEST_DIR);
assert.equal(errors.length, 1);
assert.match(errors[0], /file too small/);
});

test('valid files in subdirectory pass', () => {
createFontFile('subdir/nested.woff2', Buffer.from([0x77, 0x4F, 0x46, 0x32]));
createFontFile('subdir/nested.woff', Buffer.from([0x77, 0x4F, 0x46, 0x46]));
const errors = checkFontAssets(TEST_DIR);
assert.deepEqual(errors, []);
});

test('invalid file in subdirectory fails with relative path', () => {
createFontFile('subdir/bad.woff2', 'not a font');
const errors = checkFontAssets(TEST_DIR);
assert.equal(errors.length, 1);
assert.match(errors[0], /subdir\/bad\.woff2/);
});

test('ignores node_modules directory', () => {
mkdirSync(join(TEST_DIR, 'node_modules', 'pkg'), { recursive: true });
createFontFile('node_modules/pkg/ignored.woff2', 'not a font');
const errors = checkFontAssets(TEST_DIR);
assert.deepEqual(errors, []);
});

test('ignores .git directory', () => {
mkdirSync(join(TEST_DIR, '.git', 'objects'), { recursive: true });
createFontFile('.git/objects/ignored.woff', 'not a font');
const errors = checkFontAssets(TEST_DIR);
assert.deepEqual(errors, []);
});

test('formatErrors success', () => {
const formatted = formatErrors([]);
assert.equal(formatted, '✅ All WOFF/WOFF2 assets have valid magic bytes.');
});

test('formatErrors failure', () => {
const errors = ['path/to/bad.woff2: invalid woff2 magic bytes'];
const formatted = formatErrors(errors);
assert.match(formatted, /Invalid font assets/);
assert.match(formatted, /path\/to\/bad\.woff2/);
});

test('repository has no committed font assets', () => {
const repoRoot = fileURLToPath(new URL('..', import.meta.url));
const errors = checkFontAssets(repoRoot);
assert.deepEqual(errors, []);
});
});
Loading