Files
mygit/ghost/core/bin/generate-golden-email.js
T
DuckQ1u 93d1b7c3d3
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
first commit
2026-04-22 19:51:20 +07:00

133 lines
4.0 KiB
JavaScript

#!/usr/bin/env node
// @ts-check
/* eslint-disable no-console */
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const jwt = require('jsonwebtoken');
const GHOST_URL = process.env.GHOST_URL || 'http://localhost:2368';
const GOLDEN_POST_PATH = path.join(__dirname, '..', 'test', 'utils', 'fixtures', 'email-service', 'golden-post.json');
function usage() {
console.error('Usage: GHOST_GOLDEN_POST_AUTH=id:secret pnpm generate-golden-email <segment> <output-path>');
console.error('');
console.error(' segment: Member segment filter, e.g. "status:free" or "status:-free"');
console.error(' output-path: Path to write the rendered email HTML');
console.error('');
console.error('GHOST_GOLDEN_POST_AUTH should be an Admin API key in id:secret format.');
console.error('Requires a running Ghost dev instance (pnpm dev).');
process.exit(1);
}
/**
* @returns {{id: string, secret: string}}
*/
function getAdminApiKey() {
const auth = process.env.GHOST_GOLDEN_POST_AUTH;
if (!auth) {
console.error('Error: GHOST_GOLDEN_POST_AUTH environment variable is required.');
console.error('Set it to an Admin API key.');
process.exit(1);
}
const [id, secret] = auth.split(':');
if (id && secret) {
return {id, secret};
}
console.error('Error: GHOST_GOLDEN_POST_AUTH environment variable is invalid.');
process.exit(1);
}
/**
* @param {{id: string, secret: string}} apiKey
* @returns {string}
*/
function signToken(apiKey) {
return jwt.sign(
{},
Buffer.from(apiKey.secret, 'hex'),
{
keyid: apiKey.id,
algorithm: 'HS256',
expiresIn: '5m',
audience: '/admin/'
}
);
}
/**
* @param {string} token
* @param {string} method
* @param {string} endpoint
* @param {object} [body]
* @returns {Promise<Record<string, unknown>>}
*/
async function apiRequest(token, method, endpoint, body) {
// god_mode bypasses the integration token endpoint allowlist in development
const separator = endpoint.includes('?') ? '&' : '?';
const url = `${GHOST_URL}/ghost/api/admin/${endpoint}${separator}god_mode=true`;
const res = await fetch(url, {
method,
headers: {
Authorization: `Ghost ${token}`,
'Content-Type': 'application/json'
},
...(body ? {body: JSON.stringify(body)} : {})
});
assert(res.ok, `API ${method} ${endpoint} failed (${res.status})`);
if (res.status === 204) {
return {};
}
return await res.json();
}
async function main() {
const args = process.argv.slice(2);
if (args.length !== 2) {
usage();
}
const [segment, outputPath] = args;
const apiKey = getAdminApiKey();
const token = signToken(apiKey);
const goldenPost = JSON.parse(fs.readFileSync(GOLDEN_POST_PATH, 'utf8'));
const createRes = await apiRequest(token, 'POST', 'posts/', {
posts: [{
title: 'Golden Email Preview (temp)',
status: 'draft',
lexical: JSON.stringify(goldenPost)
}]
});
const postId = createRes.posts[0].id;
try {
const previewRes = await apiRequest(
token,
'GET',
`email_previews/posts/${postId}/?memberSegment=${encodeURIComponent(segment)}`
);
const html = previewRes.email_previews[0].html;
const resolvedPath = path.resolve(outputPath);
fs.writeFileSync(resolvedPath, html, 'utf8');
console.log(`Golden email written to ${resolvedPath}`);
} finally {
try {
await apiRequest(token, 'DELETE', `posts/${postId}/`);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.error('Warning: failed to clean up draft post:', message);
}
}
}
main().catch((err) => {
const message = err instanceof Error ? err.message : String(err);
console.error('Error:', message);
process.exit(1);
});