Bring the checklist app under version control
The project has lived only as a folder on the Mac Studio and, briefly, as a transfer tarball. This is the first commit: the Express server and its web front end, the Capacitor iOS and Android wrappers, and the release and backup scripts. The repo is the source, not a backup of the machine. Left out on purpose, by the .gitignore files that were already in the tree: node_modules, build output, mobile/www (regenerated from server/public on every build), and server/data, which holds the live checklists and belongs on the NAS rather than in git. Every credential is excluded too — server/.env, the Play upload keystore and its properties file, the App Store Connect key and ios-signing.config. The one secret that was not in an ignored file was the NAS SSH password, hard-coded in build-deploy.sh and grepped back out of it by backup-to-drive.sh. Both now read it from the login keychain (service checklist-nas, account Christian), falling back to NAS_PASS in the environment, so rotating it is one keychain entry rather than a history rewrite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const zlib = require('zlib');
|
||||
|
||||
const buildDir = path.join(__dirname, '..', 'build');
|
||||
const pngPath = path.join(buildDir, 'icon.png');
|
||||
const icoPath = path.join(buildDir, 'icon.ico');
|
||||
|
||||
function crc32(buffer) {
|
||||
let crc = 0xffffffff;
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
crc ^= buffer[i];
|
||||
for (let j = 0; j < 8; j++) {
|
||||
const mask = -(crc & 1);
|
||||
crc = (crc >>> 1) ^ (0xedb88320 & mask);
|
||||
}
|
||||
}
|
||||
return (crc ^ 0xffffffff) >>> 0;
|
||||
}
|
||||
|
||||
function makeChunk(type, data) {
|
||||
const length = Buffer.alloc(4);
|
||||
length.writeUInt32BE(data.length, 0);
|
||||
const typeBuffer = Buffer.from(type, 'ascii');
|
||||
const crcBuffer = Buffer.alloc(4);
|
||||
const crc = crc32(Buffer.concat([typeBuffer, data]));
|
||||
crcBuffer.writeUInt32BE(crc, 0);
|
||||
return Buffer.concat([length, typeBuffer, data, crcBuffer]);
|
||||
}
|
||||
|
||||
function drawLine(raw, rowStride, color, x0, y0, x1, y1, thickness = 2) {
|
||||
const dx = Math.abs(x1 - x0);
|
||||
const dy = Math.abs(y1 - y0);
|
||||
const sx = x0 < x1 ? 1 : -1;
|
||||
const sy = y0 < y1 ? 1 : -1;
|
||||
let err = dx - dy;
|
||||
|
||||
const setPixel = (x, y) => {
|
||||
if (x < 0 || y < 0 || x >= 256 || y >= 256) return;
|
||||
for (let ty = -Math.floor(thickness / 2); ty <= Math.floor(thickness / 2); ty++) {
|
||||
for (let tx = -Math.floor(thickness / 2); tx <= Math.floor(thickness / 2); tx++) {
|
||||
const xx = x + tx;
|
||||
const yy = y + ty;
|
||||
if (xx < 0 || yy < 0 || xx >= 256 || yy >= 256) continue;
|
||||
const rowStart = yy * rowStride;
|
||||
const idx = rowStart + 1 + xx * 4;
|
||||
raw[idx] = color[0];
|
||||
raw[idx + 1] = color[1];
|
||||
raw[idx + 2] = color[2];
|
||||
raw[idx + 3] = color[3];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
setPixel(x0, y0);
|
||||
if (x0 === x1 && y0 === y1) break;
|
||||
const e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x0 += sx; }
|
||||
if (e2 < dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
function createIconPng(width, height) {
|
||||
const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
||||
|
||||
const ihdrData = Buffer.alloc(13);
|
||||
ihdrData.writeUInt32BE(width, 0);
|
||||
ihdrData.writeUInt32BE(height, 4);
|
||||
ihdrData[8] = 8; // Bit depth
|
||||
ihdrData[9] = 6; // Color type RGBA
|
||||
const ihdrChunk = makeChunk('IHDR', ihdrData);
|
||||
|
||||
const rowStride = width * 4 + 1;
|
||||
const raw = Buffer.alloc(rowStride * height);
|
||||
const bg = [79, 70, 229, 255]; // blue/indigo
|
||||
for (let y = 0; y < height; y++) {
|
||||
const rowStart = y * rowStride;
|
||||
raw[rowStart] = 0; // No filter
|
||||
for (let x = 0; x < width; x++) {
|
||||
const idx = rowStart + 1 + x * 4;
|
||||
raw[idx] = bg[0];
|
||||
raw[idx + 1] = bg[1];
|
||||
raw[idx + 2] = bg[2];
|
||||
raw[idx + 3] = bg[3];
|
||||
}
|
||||
}
|
||||
|
||||
// White checkmark
|
||||
const check = [255, 255, 255, 255];
|
||||
drawLine(raw, rowStride, check, Math.floor(width * 0.22), Math.floor(height * 0.55), Math.floor(width * 0.42), Math.floor(height * 0.72), 10);
|
||||
drawLine(raw, rowStride, check, Math.floor(width * 0.42), Math.floor(height * 0.72), Math.floor(width * 0.78), Math.floor(height * 0.32), 10);
|
||||
|
||||
const idatData = zlib.deflateSync(raw);
|
||||
const idatChunk = makeChunk('IDAT', idatData);
|
||||
const iendChunk = makeChunk('IEND', Buffer.alloc(0));
|
||||
|
||||
return Buffer.concat([signature, ihdrChunk, idatChunk, iendChunk]);
|
||||
}
|
||||
|
||||
function buildIcoFromPng(pngBuffer) {
|
||||
const header = Buffer.alloc(6);
|
||||
header.writeUInt16LE(0, 0); // Reserved
|
||||
header.writeUInt16LE(1, 2); // Type = icon
|
||||
header.writeUInt16LE(1, 4); // Image count
|
||||
|
||||
const entry = Buffer.alloc(16);
|
||||
entry[0] = 0; // Width 256 (0 means 256 in ICO format)
|
||||
entry[1] = 0; // Height 256
|
||||
entry[2] = 0; // Palette
|
||||
entry[3] = 0; // Reserved
|
||||
entry.writeUInt16LE(1, 4); // Color planes
|
||||
entry.writeUInt16LE(32, 6); // Bits per pixel
|
||||
entry.writeUInt32LE(pngBuffer.length, 8); // PNG byte size
|
||||
entry.writeUInt32LE(header.length + entry.length, 12); // Offset to PNG
|
||||
|
||||
return Buffer.concat([header, entry, pngBuffer]);
|
||||
}
|
||||
|
||||
function ensureBuildAssets() {
|
||||
fs.mkdirSync(buildDir, { recursive: true });
|
||||
|
||||
// Always regenerate to ensure latest icon style
|
||||
const pngBuffer = createIconPng(256, 256);
|
||||
fs.writeFileSync(pngPath, pngBuffer);
|
||||
console.log(`Wrote PNG icon at ${pngPath}`);
|
||||
|
||||
const icoBuffer = buildIcoFromPng(pngBuffer);
|
||||
fs.writeFileSync(icoPath, icoBuffer);
|
||||
console.log(`Wrote ICO icon at ${icoPath}`);
|
||||
}
|
||||
|
||||
ensureBuildAssets();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user