// Pack the site's own source for the fifth title card. The fallback has to be
// more than a link to a repository that may not exist yet: this zip is built
// from the same working tree, so anyone can download the pond, unpack it and
// run `npm install && npm run build`.
//
// It deliberately includes the committed audio wasm and the Rust stage patch;
// it excludes node_modules, builds, scratch screenshots, the vendored Rust
// clone and every local credential file.
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { zipSync } from 'fflate';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const output = join(root, 'public', 'quack-source.zip');
const skipDirs = new Set(['node_modules', 'dist', 'temp', '.git', '.wrangler', 'duck-pond']);
const skipFiles = new Set(['quack-source.zip', '.credentials.local.md']);
const entries = [
'index.html',
'package.json',
'package-lock.json',
'tsconfig.json',
'vite.config.ts',
'README.md',
'LICENSE',
'.gitignore',
'src',
'public',
'scripts',
'docs',
'audio',
];
const files = {};
/** Add one file or directory, skipping generated output and local-only files. */
function add(path) {
const base = basename(path);
if (skipDirs.has(base) || skipFiles.has(base) || base.startsWith('.env') || base.startsWith('deploy-')) return;
const name = relative(root, path).split(sep).join('/');
if (statSync(path).isDirectory()) {
for (const child of readdirSync(path)) add(join(path, child));
return;
}
files[name] = readFileSync(path);
}
for (const entry of entries) {
const path = join(root, entry);
if (existsSync(path)) add(path);
}
mkdirSync(join(root, 'public'), { recursive: true });
const zip = zipSync(files, { level: 9 });
writeFileSync(output, zip);
console.log(`source zip ${relative(root, output)} (${(zip.length / 1024).toFixed(0)} KiB, ${Object.keys(files).length} files)`);