Files
opencode-skill/skills/website-creator/creative/picture-it/scripts/thai-font-patch.ts
Kunthawat Greethong b26c8199a5 Update skills: add website-creator, mql-developer, ecommerce-astro
Changes:
- Add FAL_KEY and GEMINI_API_KEY to .env.example
- Update picture-it to use ~/.config/opencode/.env (unified creds)
- Remove shodh-memory skill (no longer used)
- Remove alphaear-* skills (deprecated)
- Remove thai-frontend-dev skill (replaced by website-creator)
- Remove theme-factory skill
- Add mql-developer skill (MQL5 trading)
- Add ecommerce-astro skill (Astro e-commerce)
- Add website-creator skill (Next.js + Payload CMS)
- Update install script for new skills
2026-04-16 17:40:27 +07:00

269 lines
8.2 KiB
TypeScript

#!/usr/bin/env bun
/**
* Thai Font Patcher for picture-it
*
* Detects if Thai font entries are missing from picture-it's FONT_FILES array
* in dist/index.js, and patches them if needed.
*
* This is idempotent — safe to run multiple times.
* Run this after every `picture-it` update.
*
* Usage:
* bun thai-font-patch.ts # patch if needed
* bun thai-font-patch.ts --check # just check, don't patch
* bun thai-font-patch.ts --force # re-patch even if already patched
*/
import { readFileSync, writeFileSync, existsSync, cpSync, mkdirSync } from "fs";
import { join, dirname } from "path";
import { execSync } from "child_process";
const THAI_FONT_FILES_SIGNATURE = "thai/NotoSansThai-Regular.ttf";
const THAI_FONTS_ENTRY = ` },
// Thai fonts
{
name: "Noto Sans Thai",
file: "thai/NotoSansThai-Regular.ttf",
weight: 400,
style: "normal",
url: "https://fonts.gstatic.com/s/notosansthai/v29/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU5RtpzE.ttf"
},
{
name: "Noto Sans Thai",
file: "thai/NotoSansThai-Bold.ttf",
weight: 700,
style: "normal",
url: "https://fonts.gstatic.com/s/notosansthai/v29/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU3NqpzE.ttf"
},
{
name: "Kanit",
file: "thai/Kanit-Regular.ttf",
weight: 400,
style: "normal",
url: "https://fonts.gstatic.com/s/kanit/v17/nKKZ-Go6G5tXcoaS.ttf"
},
{
name: "Kanit",
file: "thai/Kanit-Bold.ttf",
weight: 700,
style: "normal",
url: "https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr4uPiWg.ttf"
}
];`
// Thai font download URLs
const THAI_FONTS_TO_DOWNLOAD = [
{
name: "NotoSansThai-Regular.ttf",
url: "https://fonts.gstatic.com/s/notosansthai/v29/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU5RtpzE.ttf",
},
{
name: "NotoSansThai-Bold.ttf",
url: "https://fonts.gstatic.com/s/notosansthai/v29/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU3NqpzE.ttf",
},
{
name: "Kanit-Regular.ttf",
url: "https://fonts.gstatic.com/s/kanit/v17/nKKZ-Go6G5tXcoaS.ttf",
},
{
name: "Kanit-Bold.ttf",
url: "https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr4uPiWg.ttf",
},
];
function findPictureItDist(): string | null {
const home = process.env.HOME || "";
const searchPaths = [
// bun-js snap (primary)
join(home, "snap/bun-js/87/.bun/install/global/node_modules/picture-it/dist/index.js"),
// npm global
join(home, ".npm-global/lib/node_modules/picture-it/dist/index.js"),
];
for (const p of searchPaths) {
if (existsSync(p)) return p;
}
return null;
}
function getPictureItVersion(installDir: string): string {
try {
const pkgPath = join(installDir, "package.json");
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
return pkg.version || "unknown";
} catch {
return "unknown";
}
}
function getInstallDir(distPath: string): string {
return dirname(dirname(distPath));
}
function isAlreadyPatched(distPath: string): boolean {
const content = readFileSync(distPath, "utf-8");
return content.includes(THAI_FONT_FILES_SIGNATURE);
}
async function downloadThaiFonts(fontDir: string): Promise<string[]> {
mkdirSync(fontDir, { recursive: true });
const downloaded: string[] = [];
for (const font of THAI_FONTS_TO_DOWNLOAD) {
const outPath = join(fontDir, font.name);
if (existsSync(outPath)) {
const stat = readFileSync(outPath);
if (stat.length > 1000) {
console.log(` Already exists: ${font.name}`);
downloaded.push(font.name);
continue;
}
}
console.log(` Downloading: ${font.name}`);
const res = await fetch(font.url);
if (!res.ok) throw new Error(`Failed to download ${font.name}: ${res.status}`);
const buf = await res.arrayBuffer();
writeFileSync(outPath, Buffer.from(buf));
downloaded.push(font.name);
}
return downloaded;
}
function patchDist(distPath: string): boolean {
const content = readFileSync(distPath, "utf-8");
// Find DM Serif Display closing brace in FONT_FILES array
// Pattern: the last entry in FONT_FILES ends with:
// url: "https://fonts.gstatic.com/s/dmserifdisplay/v17/..."
// }
// ];
// We need to replace the closing `}` with Thai fonts entries + ];
const dmSerifEntryEnd = `name: "DM Serif Display",
file: "DMSerifDisplay-Regular.ttf",
weight: 400,
style: "normal",
url: "https://fonts.gstatic.com/s/dmserifdisplay/v17/-nFnOHM81r4j6k0gjAW3mujVU2B2K_c.ttf"
}`;
// Find the closing of the array after DM Serif Display
// We look for the pattern: DM Serif Display entry ... }; var cachedFonts
// The `];` after the closing `}` of DM Serif Display is the end of FONT_FILES
const fontFilesEndPattern = /(\n\s+url: "https:\/\/fonts\.gstatic\.com\/s\/dmserifdisplay\/v17\/[^"]+"\n\s+}\n)\];/;
const match = content.match(fontFilesEndPattern);
if (!match) {
// Try alternate pattern (might differ in minified versions)
const altPattern = /(\}\n)\];/;
const altMatch = content.match(altPattern);
if (!altMatch) {
throw new Error("Could not find end of FONT_FILES array. picture-it format may have changed.");
}
// Just replace the first occurrence of ]; after a }
const newContent = content.replace(altPattern, THAI_FONTS_ENTRY.replace(" },", match?.[1] || " }").endsWith("}") ? THAI_FONTS_ENTRY : ` },${THAI_FONTS_ENTRY.split(" },")[1]}`);
writeFileSync(distPath, newContent);
return true;
}
const patched = content.replace(
fontFilesEndPattern,
THAI_FONTS_ENTRY
);
if (patched === content) {
return false;
}
writeFileSync(distPath, patched);
return true;
}
async function main() {
const args = process.argv.slice(2);
const checkOnly = args.includes("--check");
const force = args.includes("--force");
console.log("[Thai Font Patcher for picture-it]");
console.log("");
const distPath = findPictureItDist();
if (!distPath) {
console.log("ERROR: picture-it not found.");
console.log("Is picture-it installed? Run: bun install -g picture-it");
process.exit(1);
}
const installDir = getInstallDir(distPath);
const version = getPictureItVersion(installDir);
console.log(`picture-it found: v${version}`);
console.log(`Install dir: ${installDir}`);
console.log(`dist path: ${distPath}`);
console.log("");
const alreadyPatched = isAlreadyPatched(distPath);
if (alreadyPatched && !force) {
console.log("Thai fonts: ALREADY PATCHED ✓");
console.log("No action needed.");
process.exit(0);
}
if (checkOnly) {
console.log("Thai fonts: NOT PATCHED ✗");
console.log("Run without --check to apply patch.");
process.exit(1);
}
if (force && alreadyPatched) {
console.log("Force mode: re-patching...");
}
// Step 1: Download Thai fonts
console.log("Step 1: Ensuring Thai fonts are downloaded...");
const fontDir = join(process.env.HOME || "", ".picture-it/fonts/thai");
try {
await downloadThaiFonts(fontDir);
} catch (e: any) {
console.error(` Failed to download fonts: ${e.message}`);
console.error("You can download them manually or try again.");
}
console.log("");
// Step 2: Patch dist/index.js
console.log("Step 2: Patching dist/index.js...");
try {
const success = patchDist(distPath);
if (success) {
console.log(" Patched dist/index.js ✓");
} else {
console.log(" No changes needed (or patch failed silently)");
}
} catch (e: any) {
console.error(` Patch failed: ${e.message}`);
console.error("Please report this issue.");
process.exit(1);
}
console.log("");
// Verify
const verifyPatched = isAlreadyPatched(distPath);
if (verifyPatched) {
console.log("RESULT: Thai font patch applied successfully ✓");
console.log("");
console.log("You can now use Thai fonts:");
console.log(' picture-it text -i input.png --title "ทดสอบ" --font "Noto Sans Thai" -o out.png');
console.log(' picture-it text -i input.png --title "ทดสอบ" --font "Kanit" -o out.png');
} else {
console.error("RESULT: Patch verification failed ✗");
console.error("The patch may not have been applied correctly.");
process.exit(1);
}
}
main().catch((e: Error) => {
console.error(`Error: ${e.message}`);
process.exit(1);
});