- Created `admin-head.php` for consistent <head> markup across admin pages. - Created `admin-nav.php` for shared navigation bar, reducing duplication. - Updated all admin pages (account, hours, index, login, menu, users) to utilize the new components. - Added security headers to `auth.php` and `index.php` for improved security. - Introduced `security-headers.php` to manage security headers across the application. - Moved inline styles from admin pages to SCSS files to comply with Content Security Policy (CSP). - Normalized file permissions during build process to prevent permission issues on deployment.
101 lines
3.0 KiB
JavaScript
101 lines
3.0 KiB
JavaScript
#!/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)}/`);
|