- Relocate seed.sql to scripts/ so wrangler d1 migrations apply doesn't treat it as a migration; update package.json, setup.mjs, README, and all AGENTS.md references. - Add src/lib/schema-org.ts builders (LocalBusiness, FAQPage, BlogPosting, BreadcrumbList) and emit JSON-LD via Base.astro. - Add lastmod to sitemap entries. - Set real site domain, worker route, and D1/KV ids. - Improve image loading hints and heading semantics across site components; switch BlogIndex to client:idle.
177 lines
6.2 KiB
JavaScript
177 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
||
import { execSync } from "node:child_process";
|
||
import { readFileSync, writeFileSync } from "node:fs";
|
||
import { dirname, join } from "node:path";
|
||
import { createInterface } from "node:readline/promises";
|
||
import { stdin as input, stdout as output } from "node:process";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
||
const ROOT = dirname(SCRIPT_DIR);
|
||
|
||
const D1_PLACEHOLDER = "PASTE_D1_DATABASE_ID_HERE";
|
||
const KV_PLACEHOLDER = "PASTE_KV_NAMESPACE_ID_HERE";
|
||
const R2_BUCKET = "yingfung-solar-media";
|
||
const WRANGLER = join(ROOT, "wrangler.jsonc");
|
||
const SEED_FILE = join(ROOT, "scripts", "seed.sql");
|
||
const ASTRO_CONFIG = join(ROOT, "astro.config.mjs");
|
||
const DB_NAME = "yingfung-solar-db";
|
||
|
||
const rl = createInterface({ input, output });
|
||
|
||
function run(cmd) {
|
||
return execSync(cmd, { encoding: "utf8", stdio: ["inherit", "pipe", "inherit"] });
|
||
}
|
||
|
||
function runSoft(cmd) {
|
||
try {
|
||
return { ok: true, out: run(cmd), err: "" };
|
||
} catch (err) {
|
||
return { ok: false, out: err.stdout ?? err.stderr ?? "", err: err.stderr ?? "" };
|
||
}
|
||
}
|
||
|
||
/** 由 wrangler 輸出抽 id;同時支援 JSON("key": "value")同 TOML(key = "value")。 */
|
||
function extractId(out, keys) {
|
||
for (const key of keys) {
|
||
const re = new RegExp(`(?<!\\w)"?${key}"?\\s*[=:]\\s*"([0-9a-f-]+)"`, "i");
|
||
const m = out.match(re);
|
||
if (m) return m[1];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/** 由 wrangler 嘅 JSON 陣列輸出搵第一個符合條件嘅項目。 */
|
||
function findInJsonArray(text, predicate) {
|
||
try {
|
||
const arr = JSON.parse(text);
|
||
if (Array.isArray(arr)) return arr.find(predicate) ?? null;
|
||
} catch {
|
||
// 唔係 JSON 就當搵唔到。
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function main() {
|
||
console.log("\n=== 盈豐太陽能 — Cloudflare 一鍵設定 ===\n");
|
||
|
||
console.log("檢查 wrangler 登入狀態…");
|
||
const who = runSoft("npx wrangler whoami --json");
|
||
const whoText = `${who.out}\n${who.err}`;
|
||
if (!who.ok || /not authenticated|not logged in/i.test(whoText)) {
|
||
console.error("未登入 Cloudflare。請先執行: npm run login");
|
||
process.exit(1);
|
||
}
|
||
|
||
let config = readFileSync(WRANGLER, "utf8");
|
||
|
||
if (config.includes(D1_PLACEHOLDER)) {
|
||
console.log(`建立 D1 database「${DB_NAME}」…`);
|
||
const res = runSoft(`npx wrangler d1 create ${DB_NAME}`);
|
||
let out = res.out || res.err || "";
|
||
let id = res.ok ? extractId(out, ["database_id"]) : null;
|
||
|
||
if (!res.ok) {
|
||
console.log(" 建立失敗,可能已經存在;嘗試查出現有 database_id…");
|
||
const list = runSoft("npx wrangler d1 list --json");
|
||
out = `${out}\n${list.out || list.err || ""}`;
|
||
const found = findInJsonArray(list.out, (r) => r.name === DB_NAME);
|
||
id = found?.uuid ?? null;
|
||
}
|
||
|
||
if (!id) {
|
||
console.error("建立 / 查詢 D1 database 失敗。以下係 wrangler 輸出:");
|
||
console.error(out);
|
||
process.exit(1);
|
||
}
|
||
config = config.replace(D1_PLACEHOLDER, () => id);
|
||
writeFileSync(WRANGLER, config, "utf8");
|
||
console.log(` database_id = ${id}(已寫入 wrangler.jsonc)`);
|
||
} else {
|
||
console.log("D1 id 已設定,略過。");
|
||
}
|
||
|
||
if (config.includes(KV_PLACEHOLDER)) {
|
||
console.log("建立 KV namespace「CACHE」…");
|
||
const res = runSoft("npx wrangler kv namespace create CACHE");
|
||
let out = res.out || res.err || "";
|
||
let id = res.ok ? extractId(out, ["id"]) : null;
|
||
|
||
if (!res.ok) {
|
||
console.log(" 建立失敗,可能已經存在;嘗試查出現有 KV id…");
|
||
const list = runSoft("npx wrangler kv namespace list");
|
||
out = `${out}\n${list.out || list.err || ""}`;
|
||
const found = findInJsonArray(list.out, (r) => r.title === "CACHE");
|
||
id = found?.id ?? null;
|
||
}
|
||
|
||
if (!id) {
|
||
console.error("建立 / 查詢 KV namespace 失敗。以下係 wrangler 輸出:");
|
||
console.error(out);
|
||
process.exit(1);
|
||
}
|
||
config = config.replace(KV_PLACEHOLDER, () => id);
|
||
writeFileSync(WRANGLER, config, "utf8");
|
||
console.log(` kv id = ${id}(已寫入 wrangler.jsonc)`);
|
||
} else {
|
||
console.log("KV id 已設定,略過。");
|
||
}
|
||
|
||
console.log(`檢查 / 建立 R2 bucket「${R2_BUCKET}」…`);
|
||
const r2List = runSoft("npx wrangler r2 bucket list");
|
||
const r2Text = `${r2List.out || ""}\n${r2List.err || ""}`;
|
||
if (new RegExp(`\\b${R2_BUCKET}\\b`).test(r2Text)) {
|
||
console.log(" R2 bucket 已存在,略過。");
|
||
} else {
|
||
const r2 = runSoft(`npx wrangler r2 bucket create ${R2_BUCKET}`);
|
||
if (r2.ok) {
|
||
console.log(" R2 bucket 已建立。");
|
||
} else {
|
||
console.log(" 建立 R2 bucket 失敗(可能未開通 R2),請稍後手動執行: npm run r2:create");
|
||
console.log(r2.out || r2.err || "");
|
||
}
|
||
}
|
||
|
||
const setPassword = await rl.question("而家設定後台密碼 ADMIN_PASSWORD?(y/N):");
|
||
if (setPassword.trim().toLowerCase() === "y") {
|
||
execSync("npx wrangler secret put ADMIN_PASSWORD", { stdio: "inherit" });
|
||
console.log("ADMIN_PASSWORD 已處理。");
|
||
} else {
|
||
console.log("略過 ADMIN_PASSWORD。");
|
||
}
|
||
|
||
const migrate = await rl.question("而家套用雲端 migration + seed?(y/N):");
|
||
if (migrate.trim().toLowerCase() === "y") {
|
||
execSync(`npx wrangler d1 migrations apply ${DB_NAME} --remote`, { stdio: "inherit" });
|
||
execSync(`npx wrangler d1 execute ${DB_NAME} --remote --file="${SEED_FILE}"`, { stdio: "inherit" });
|
||
}
|
||
|
||
let siteReady = true;
|
||
try {
|
||
if (readFileSync(ASTRO_CONFIG, "utf8").includes("https://example.com")) {
|
||
siteReady = false;
|
||
console.log("\n⚠️ 警告:astro.config.mjs 嘅 site 仍然係 https://example.com。");
|
||
console.log(" 上線前必須改成真域名,否則 sitemap / canonical / OG 會出錯。");
|
||
console.log(" 已跳過 build + deploy;更新 site 後請自行執行: npm run deploy\n");
|
||
}
|
||
} catch {
|
||
// 讀唔到 astro.config.mjs 就當冇問題,繼續。
|
||
}
|
||
|
||
if (siteReady) {
|
||
const deploy = await rl.question("而家 build + deploy?(y/N):");
|
||
if (deploy.trim().toLowerCase() === "y") {
|
||
execSync("npm run build", { stdio: "inherit" });
|
||
execSync("npx wrangler deploy", { stdio: "inherit" });
|
||
}
|
||
}
|
||
|
||
console.log("\n完成。\n");
|
||
rl.close();
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error(err);
|
||
process.exit(1);
|
||
});
|