Add one-command dev-to-prod release with clear SUCCESS/FAILED banners.
Introduce devtoprod:release orchestrator, config file, CLI result footers on deploy scripts, auto-thumb regeneration on curator fixes, and updated deploy documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
313666a4ab
commit
21e3e41e48
@@ -9,6 +9,7 @@ const {
|
||||
loadProdPgConfig,
|
||||
rootDir,
|
||||
} = require('./db-env');
|
||||
const { printCliResult } = require('./lib/cli-result');
|
||||
|
||||
const { Client } = pg;
|
||||
|
||||
@@ -124,12 +125,27 @@ async function main() {
|
||||
{ stdio: 'inherit' },
|
||||
);
|
||||
|
||||
const relTxtPath = path.relative(rootDir, txtPath).replace(/\\/g, '/');
|
||||
console.log(`Backup written: ${txtPath}`);
|
||||
console.log(`Archive written: ${zipPath}`);
|
||||
console.log(`Tables: ${tablesRes.rowCount}, rows: ${totalRows}`);
|
||||
|
||||
printCliResult({
|
||||
script: 'backup-db-data',
|
||||
ok: true,
|
||||
summary: `Backup complete for "${dbName}".`,
|
||||
details: [
|
||||
`File: ${relTxtPath}`,
|
||||
`Tables: ${tablesRes.rowCount}, rows: ${totalRows}`,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
printCliResult({
|
||||
script: 'backup-db-data',
|
||||
ok: false,
|
||||
summary: error.message || String(error),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Print a standardized final SUCCESS/FAILED banner and exit.
|
||||
* Always the last stdout before process exit in deploy scripts.
|
||||
*/
|
||||
const WIDTH = 72;
|
||||
|
||||
function printCliResult({ script, ok, summary, details = [] }) {
|
||||
const label = ok ? 'SUCCESS' : 'FAILED';
|
||||
const banner = `===== ${label}: ${script} =====`;
|
||||
const pad = Math.max(0, WIDTH - banner.length);
|
||||
const line = banner + '='.repeat(pad);
|
||||
|
||||
console.log('');
|
||||
console.log(line);
|
||||
if (summary) console.log(summary);
|
||||
for (const detail of details) {
|
||||
if (detail) console.log(detail);
|
||||
}
|
||||
console.log('='.repeat(WIDTH));
|
||||
|
||||
process.exit(ok ? 0 : 1);
|
||||
}
|
||||
|
||||
module.exports = { printCliResult };
|
||||
@@ -7,6 +7,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const pool = require('../server/db');
|
||||
const { generateThumbnailFromFull } = require('./image-fetcher');
|
||||
const { printCliResult } = require('./lib/cli-result');
|
||||
|
||||
const IMAGE_DIR = path.resolve(process.env.IMAGE_DIR || './data/images');
|
||||
const PORTRAIT_THUMB_WIDTH = 256;
|
||||
@@ -70,7 +71,32 @@ async function generatePortraitThumb(row) {
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
|
||||
if (errors.length) {
|
||||
printCliResult({
|
||||
script: 'regenerate-portrait-thumbs',
|
||||
ok: false,
|
||||
summary: `Portrait thumbnail regeneration finished with ${errors.length} error(s).`,
|
||||
details: [
|
||||
`Generated: ${generated}, skipped: ${skipped}`,
|
||||
...errors.slice(0, 5).map((e) => ` #${e.id} ${e.name}: ${e.error}`),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
printCliResult({
|
||||
script: 'regenerate-portrait-thumbs',
|
||||
ok: true,
|
||||
summary: 'Portrait thumbnail regeneration complete.',
|
||||
details: [`Generated: ${generated}, skipped: ${skipped}`],
|
||||
});
|
||||
})().catch((err) => {
|
||||
console.error(err);
|
||||
pool.end().finally(() => process.exit(1));
|
||||
pool.end().finally(() => {
|
||||
printCliResult({
|
||||
script: 'regenerate-portrait-thumbs',
|
||||
ok: false,
|
||||
summary: err.message || String(err),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ const path = require('path');
|
||||
const sharp = require('sharp');
|
||||
const pool = require('../server/db');
|
||||
const { generateThumbnailFromFull } = require('./image-fetcher');
|
||||
const { printCliResult } = require('./lib/cli-result');
|
||||
|
||||
const IMAGE_DIR = path.resolve(process.env.IMAGE_DIR || './data/images');
|
||||
|
||||
@@ -91,7 +92,33 @@ async function aspectRatio(filePath) {
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
|
||||
if (errors.length) {
|
||||
printCliResult({
|
||||
script: 'regenerate-thumbnails',
|
||||
ok: false,
|
||||
summary: `Painting thumbnail regeneration finished with ${errors.length} error(s).`,
|
||||
details: [
|
||||
`Regenerated: ${regenerated}, skipped: ${skipped}`,
|
||||
...errors.slice(0, 5).map((e) => ` #${e.id} ${e.title}: ${e.error}`),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
printCliResult({
|
||||
script: 'regenerate-thumbnails',
|
||||
ok: true,
|
||||
summary: 'Painting thumbnail regeneration complete.',
|
||||
details: [
|
||||
`Regenerated: ${regenerated}, skipped: ${skipped}`,
|
||||
`Aspect-ratio mismatches before fix: ${mismatchesBefore}`,
|
||||
],
|
||||
});
|
||||
})().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
printCliResult({
|
||||
script: 'regenerate-thumbnails',
|
||||
ok: false,
|
||||
summary: err.message || String(err),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ const {
|
||||
loadDevPgConfig,
|
||||
loadProdPgConfig,
|
||||
} = require('./db-env');
|
||||
const { printCliResult } = require('./lib/cli-result');
|
||||
|
||||
const { Client } = pg;
|
||||
|
||||
@@ -185,9 +186,20 @@ async function main() {
|
||||
|
||||
await client.end();
|
||||
console.log(`Restore complete: ${restored} statements into "${dbName}".`);
|
||||
|
||||
printCliResult({
|
||||
script: 'restore-db-data',
|
||||
ok: true,
|
||||
summary: `Restore complete into "${dbName}".`,
|
||||
details: [`Statements restored: ${restored}`],
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
printCliResult({
|
||||
script: 'restore-db-data',
|
||||
ok: false,
|
||||
summary: error.message || String(error),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Run painting + portrait thumbnail regeneration and print one combined result.
|
||||
*/
|
||||
const { spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const { printCliResult } = require('./lib/cli-result');
|
||||
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
const scripts = [
|
||||
{ name: 'regenerate-thumbnails', file: 'regenerate-thumbnails.js' },
|
||||
{ name: 'regenerate-portrait-thumbs', file: 'regenerate-portrait-thumbs.js' },
|
||||
];
|
||||
|
||||
function runScript(script) {
|
||||
const result = spawnSync(process.execPath, [path.join(__dirname, script.file)], {
|
||||
cwd: rootDir,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
return result.status ?? 1;
|
||||
}
|
||||
|
||||
let failed = null;
|
||||
for (const script of scripts) {
|
||||
const code = runScript(script);
|
||||
if (code !== 0) {
|
||||
failed = script.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
printCliResult({
|
||||
script: 'devtoprod-thumbnails',
|
||||
ok: false,
|
||||
summary: `Thumbnail regeneration failed at ${failed}.`,
|
||||
});
|
||||
}
|
||||
|
||||
printCliResult({
|
||||
script: 'devtoprod-thumbnails',
|
||||
ok: true,
|
||||
summary: 'Painting and portrait thumbnail regeneration complete.',
|
||||
details: ['Both regenerate-thumbnails and regenerate-portrait-thumbs succeeded.'],
|
||||
});
|
||||
Reference in New Issue
Block a user