#!/usr/bin/env node 'use strict'; /** * Assembles a clean, production-only copy of the site into dist/. * * This is an allowlist by design: only the files and folders listed below * are copied. Dev-only stuff (node_modules/, src/, package*.json, .git/, * .claude/, docker-compose.yaml, dev.sh, .hintrc, config.example.php, the * live db/fitz.sqlite, ...) never ends up in dist/ — including anything * added later, since it has to be added here explicitly first. * * Run with: npm run dist */ const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const DIST = path.join(ROOT, 'dist'); const FILES = [ 'index.php', 'menu.php', 'main.js', 'styles.css', 'robots.txt', 'config.php', // holds the live FB_PAGE_ACCESS_TOKEN — see README before sharing dist/ anywhere but the server ]; const DIRS = ['admin', 'assets', 'bin', 'inc']; function copyDir(src, dest) { fs.cpSync(src, dest, { recursive: true, filter: (source) => path.basename(source) !== '.DS_Store', }); } // Fresh start every run so a file removed from the source tree doesn't // linger in dist/ from a previous build. fs.rmSync(DIST, { recursive: true, force: true }); fs.mkdirSync(DIST, { recursive: true }); const missing = []; for (const file of FILES) { const src = path.join(ROOT, file); if (!fs.existsSync(src)) { missing.push(file); continue; } fs.copyFileSync(src, path.join(DIST, file)); } for (const dir of DIRS) { const src = path.join(ROOT, dir); if (!fs.existsSync(src)) { missing.push(dir + '/'); continue; } copyDir(src, path.join(DIST, dir)); } // db/ ships with just .htaccess (denies direct web access to the sqlite // file) — never the live fitz.sqlite itself. inc/db.php creates the // schema on first run if the file isn't there, so shipping the dev // database would just overwrite whatever is already live on the server. const dbHtaccess = path.join(ROOT, 'db', '.htaccess'); if (fs.existsSync(dbHtaccess)) { fs.mkdirSync(path.join(DIST, 'db'), { recursive: true }); fs.copyFileSync(dbHtaccess, path.join(DIST, 'db', '.htaccess')); } else { missing.push('db/.htaccess'); } // Normalize permissions: 755 for directories, 644 for files. Copies keep // the source file's mode, so a file that's owner-only on the dev machine // (e.g. after an in-place edit) would ship unreadable to Apache's // www-data and 500 the page. Forcing the modes here means local // permissions can never break a deploy. function normalizePermissions(dir) { fs.chmodSync(dir, 0o755); for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) { normalizePermissions(full); } else { fs.chmodSync(full, 0o644); } } } normalizePermissions(DIST); if (missing.length) { console.warn('Warning: expected but not found, skipped:\n ' + missing.join('\n ')); } console.log(`Deploy folder built at ${path.relative(ROOT, DIST)}/`);