mirror of
https://github.com/hanzo-apps/templates.git
synced 2026-10-30 14:48:30 +00:00
Aggregates five OSS one-click/self-hosting catalogs into the Dokploy blueprint
shape the Hanzo PaaS consumes (meta.json array + blueprints/<id>/{template.toml,
docker-compose.yml,meta.json,logo}):
Dokploy 500 (MIT, verbatim) · Coolify 174 (Apache-2.0) · CapRover 161
(Apache-2.0) · CasaOS 85 (Apache-2.0) · Runtipi 110 (GPL-3.0)
Deduped by app identity; each source + app keeps its upstream license
(ATTRIBUTION.md). Deterministic aggregators + a hard validator (every
template.toml parses with the platform's toml lib, every compose parses as YAML,
meta.json is the expected array, and an over-HTTP fetch probe exercises all
1030) — 0 hard failures. Served natively by hanzoai/static on DOKS (no CDN/Pages/
Vercel); CI builds on the hanzo-build-linux-amd64 arc pool; operator App CR in
crs/ serves it at templates.hanzo.ai.
51 lines
2.5 KiB
JavaScript
51 lines
2.5 KiB
JavaScript
// Aggregate every blueprints/<id>/meta.json into the single root meta.json array
|
|
// the platform serves at {baseUrl}/meta.json. Ported from Dokploy's
|
|
// generate-meta.js; the array shape is identical (id must equal the dir name).
|
|
//
|
|
// Usage: node generate-meta.mjs <blueprintsDir> <outMetaJson>
|
|
import { readdirSync, readFileSync, existsSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const [, , BP, OUT] = process.argv;
|
|
const REQUIRED = ["id", "name", "version", "description", "links", "logo", "tags"];
|
|
|
|
const errors = [];
|
|
const entries = [];
|
|
const dirs = readdirSync(BP, { withFileTypes: true })
|
|
.filter((d) => d.isDirectory())
|
|
.map((d) => d.name)
|
|
.sort((a, b) => a.localeCompare(b));
|
|
|
|
for (const dir of dirs) {
|
|
const metaFile = join(BP, dir, "meta.json");
|
|
if (!existsSync(metaFile)) { errors.push(`${dir}: missing meta.json`); continue; }
|
|
let entry;
|
|
try { entry = JSON.parse(readFileSync(metaFile, "utf8")); }
|
|
catch (e) { errors.push(`${dir}/meta.json: invalid JSON (${e.message})`); continue; }
|
|
if (Array.isArray(entry) || typeof entry !== "object" || entry === null) {
|
|
errors.push(`${dir}/meta.json: must be a single object`); continue;
|
|
}
|
|
if (entry.id !== dir) { errors.push(`${dir}/meta.json: id "${entry.id}" != dir`); continue; }
|
|
let ok = true;
|
|
for (const f of REQUIRED) {
|
|
if (entry[f] === undefined || entry[f] === null || entry[f] === "") { errors.push(`${dir}: missing "${f}"`); ok = false; }
|
|
}
|
|
if (!entry.links || typeof entry.links !== "object") { errors.push(`${dir}: links must be object`); ok = false; }
|
|
if (!Array.isArray(entry.tags) || !entry.tags.length) { errors.push(`${dir}: tags must be non-empty array`); ok = false; }
|
|
// The blueprint's referenced files must exist alongside it.
|
|
for (const f of ["template.toml", "docker-compose.yml"]) {
|
|
if (!existsSync(join(BP, dir, f))) { errors.push(`${dir}: missing ${f}`); ok = false; }
|
|
}
|
|
if (entry.logo && !existsSync(join(BP, dir, entry.logo))) { errors.push(`${dir}: logo "${entry.logo}" not found`); ok = false; }
|
|
if (ok) entries.push(entry);
|
|
}
|
|
|
|
if (errors.length) {
|
|
console.error(`generate-meta: ${errors.length} problem(s):`);
|
|
for (const e of errors.slice(0, 40)) console.error(" - " + e);
|
|
if (errors.length > 40) console.error(` … +${errors.length - 40} more`);
|
|
}
|
|
writeFileSync(OUT, JSON.stringify(entries, null, 2) + "\n");
|
|
console.log(`generate-meta: wrote ${entries.length} entries -> ${OUT} (${errors.length} dropped)`);
|
|
process.exit(errors.length ? 2 : 0);
|