83edaf5975
build-and-deploy / build (push) Failing after 15m10s
scripts/new-post.mjs writes schema-valid posts from flags or a JSON event (the IaC publish seam). Gitea Actions workflow: ci check, audit-ci gate, build, dist scan, CycloneDX SBOM, buildah build+push, and a least-privilege digest-bump PR to home-ops (never auto-merged). Renovate + audit allowlist.
116 lines
3.6 KiB
JavaScript
Executable File
116 lines
3.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
// Publish seam: write a schema-valid blog post into src/content/blog/.
|
|
// This is what an IaC/CI step calls to turn an event into a post + a commit.
|
|
//
|
|
// Usage (flags):
|
|
// node scripts/new-post.mjs --title "My post" --summary "..." \
|
|
// --tags "kubernetes,gpu" [--date 2026-06-18] [--draft] \
|
|
// [--slug custom-slug] [--body "markdown..."] [--bodyFile notes.md] [--force]
|
|
//
|
|
// Usage (event JSON — for pipelines):
|
|
// node scripts/new-post.mjs --json '{"title":"...","summary":"...","tags":["x"],"body":"..."}'
|
|
// cat event.json | node scripts/new-post.mjs --stdin
|
|
//
|
|
// Exits non-zero on invalid input so a malformed pipeline event fails the build.
|
|
import { parseArgs } from "node:util";
|
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const BLOG_DIR = fileURLToPath(new URL("../src/content/blog/", import.meta.url));
|
|
|
|
const { values } = parseArgs({
|
|
options: {
|
|
title: { type: "string" },
|
|
summary: { type: "string" },
|
|
tags: { type: "string" },
|
|
date: { type: "string" },
|
|
slug: { type: "string" },
|
|
body: { type: "string" },
|
|
bodyFile: { type: "string" },
|
|
draft: { type: "boolean", default: false },
|
|
force: { type: "boolean", default: false },
|
|
json: { type: "string" },
|
|
stdin: { type: "boolean", default: false },
|
|
},
|
|
allowPositionals: false,
|
|
});
|
|
|
|
function die(msg) {
|
|
console.error(`new-post: ${msg}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// --- Resolve the post fields from flags and/or a JSON event ----------------
|
|
let event = {};
|
|
if (values.stdin) {
|
|
try {
|
|
event = JSON.parse(readFileSync(0, "utf8"));
|
|
} catch (e) {
|
|
die(`could not parse JSON from stdin: ${e.message}`);
|
|
}
|
|
} else if (values.json) {
|
|
try {
|
|
event = JSON.parse(values.json);
|
|
} catch (e) {
|
|
die(`could not parse --json: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
const title = values.title ?? event.title;
|
|
const summary = values.summary ?? event.summary;
|
|
const rawTags = values.tags ?? event.tags;
|
|
const date = values.date ?? event.date ?? new Date().toISOString().slice(0, 10);
|
|
const draft = values.draft || Boolean(event.draft);
|
|
|
|
let body = values.body ?? event.body;
|
|
if (values.bodyFile) body = readFileSync(values.bodyFile, "utf8");
|
|
|
|
// --- Validate (mirror the zod schema; fail fast) ---------------------------
|
|
if (!title || typeof title !== "string") die("a --title is required");
|
|
if (!summary || typeof summary !== "string") die("a --summary is required");
|
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) die(`date must be YYYY-MM-DD (got "${date}")`);
|
|
|
|
const tags = Array.isArray(rawTags)
|
|
? rawTags
|
|
: typeof rawTags === "string"
|
|
? rawTags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
: [];
|
|
|
|
const slug =
|
|
values.slug ??
|
|
event.slug ??
|
|
title
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, "-")
|
|
.replace(/^-+|-+$/g, "")
|
|
.slice(0, 80);
|
|
if (!slug) die("could not derive a slug from the title; pass --slug");
|
|
|
|
// --- Compose the file ------------------------------------------------------
|
|
const yaml = (s) => JSON.stringify(String(s)); // safe double-quoted YAML scalar
|
|
const frontmatter = [
|
|
"---",
|
|
`title: ${yaml(title)}`,
|
|
`date: ${date}`,
|
|
`summary: ${yaml(summary)}`,
|
|
`tags: [${tags.map(yaml).join(", ")}]`,
|
|
`draft: ${draft}`,
|
|
"---",
|
|
"",
|
|
].join("\n");
|
|
|
|
const content =
|
|
frontmatter +
|
|
(body?.trim()
|
|
? `${body.trim()}\n`
|
|
: `Write the post here.\n`);
|
|
|
|
mkdirSync(BLOG_DIR, { recursive: true });
|
|
const file = `${BLOG_DIR}${slug}.md`;
|
|
if (existsSync(file) && !values.force) {
|
|
die(`${slug}.md already exists (use --force to overwrite)`);
|
|
}
|
|
|
|
writeFileSync(file, content, "utf8");
|
|
console.log(`wrote src/content/blog/${slug}.md${draft ? " (draft)" : ""}`);
|