Files
halo-theme-WarmIsland/scripts/package.mjs
T
nxxy335 125d0592c9 fix: 修复升级后整站无样式与点击特效失效
- 旧哈希静态资源随版本升级被删除,被缓存的 HTML 仍请求旧文件名导致
  FileNotFoundException 500,补 compat/assets 历史资源并在构建时合并
- postbuild 增加静态资源引用校验,缺失即构建失败
- ClickEffect 改为常驻配置节点 + data 属性驱动,避免配置未落库时静默失效;
  强制回流修正 transition 不触发;特效层挂到 body 下避免被导航栏遮挡
- 新增 scripts: postbuild/snapshot-assets/package/smoke-clickfx
- 版本 1.2.1
2026-09-27 16:27:51 +08:00

173 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 打包主题安装包:dist/warm-island-<version>.zip
*
* 安装包结构与 Halo 主题目录一致(根目录直接放 theme.yaml / settings.yaml /
* templates),与历史版本安装包保持相同布局,可直接上传安装。
*
* 仅依赖 Node 内置模块,无需额外依赖。
*/
import fs from "node:fs";
import path from "node:path";
import zlib from "node:zlib";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
/** 读取 theme.yaml 中的版本号(避免手抄版本号不一致) */
function readThemeVersion() {
const yaml = fs.readFileSync(path.join(root, "theme.yaml"), "utf8");
const match = yaml.match(/^\s*version:\s*["']?([^"'\n]+)["']?\s*$/m);
if (!match) {
throw new Error("theme.yaml 中未找到 version 字段");
}
return match[1].trim();
}
/* ---------------- CRC32 ---------------- */
const CRC_TABLE = (() => {
const table = new Int32Array(256);
for (let i = 0; i < 256; i++) {
let c = i;
for (let k = 0; k < 8; k++) {
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
}
table[i] = c;
}
return table;
})();
function crc32(buf) {
let c = 0xffffffff;
for (let i = 0; i < buf.length; i++) {
c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
}
return (c ^ 0xffffffff) >>> 0;
}
/* ---------------- ZIP 写入 ---------------- */
function dosDateTime(date = new Date()) {
const time =
((date.getHours() & 0x1f) << 11) |
((date.getMinutes() & 0x3f) << 5) |
((date.getSeconds() / 2) & 0x1f);
const day =
(((date.getFullYear() - 1980) & 0x7f) << 9) |
(((date.getMonth() + 1) & 0xf) << 5) |
(date.getDate() & 0x1f);
return { time, day };
}
function buildZip(entries, outFile) {
const chunks = [];
const central = [];
let offset = 0;
for (const entry of entries) {
const nameBuf = Buffer.from(entry.name, "utf8");
const raw = entry.data;
const crc = crc32(raw);
const compressed = zlib.deflateRawSync(raw, { level: 9 });
const useDeflate = compressed.length < raw.length;
const body = useDeflate ? compressed : raw;
const method = useDeflate ? 8 : 0;
const { time, day } = dosDateTime(entry.date);
const local = Buffer.alloc(30);
local.writeUInt32LE(0x04034b50, 0);
local.writeUInt16LE(20, 4); // version needed
local.writeUInt16LE(0x0800, 6); // UTF-8 名称
local.writeUInt16LE(method, 8);
local.writeUInt16LE(time, 10);
local.writeUInt16LE(day, 12);
local.writeUInt32LE(crc, 14);
local.writeUInt32LE(body.length, 18);
local.writeUInt32LE(raw.length, 22);
local.writeUInt16LE(nameBuf.length, 26);
local.writeUInt16LE(0, 28);
chunks.push(local, nameBuf, body);
const cd = Buffer.alloc(46);
cd.writeUInt32LE(0x02014b50, 0);
cd.writeUInt16LE(20, 4); // version made by
cd.writeUInt16LE(20, 6); // version needed
cd.writeUInt16LE(0x0800, 8);
cd.writeUInt16LE(method, 10);
cd.writeUInt16LE(time, 12);
cd.writeUInt16LE(day, 14);
cd.writeUInt32LE(crc, 16);
cd.writeUInt32LE(body.length, 20);
cd.writeUInt32LE(raw.length, 24);
cd.writeUInt16LE(nameBuf.length, 28);
cd.writeUInt16LE(0, 30); // extra
cd.writeUInt16LE(0, 32); // comment
cd.writeUInt16LE(0, 34); // disk start
cd.writeUInt16LE(0, 36); // internal attrs
cd.writeUInt32LE(0, 38); // external attrs
cd.writeUInt32LE(offset, 42);
central.push(Buffer.concat([cd, nameBuf]));
offset += local.length + nameBuf.length + body.length;
}
const centralBuf = Buffer.concat(central);
const eocd = Buffer.alloc(22);
eocd.writeUInt32LE(0x06054b50, 0);
eocd.writeUInt16LE(0, 4);
eocd.writeUInt16LE(0, 6);
eocd.writeUInt16LE(entries.length, 8);
eocd.writeUInt16LE(entries.length, 10);
eocd.writeUInt32LE(centralBuf.length, 12);
eocd.writeUInt32LE(offset, 16);
eocd.writeUInt16LE(0, 20);
fs.mkdirSync(path.dirname(outFile), { recursive: true });
fs.writeFileSync(outFile, Buffer.concat([...chunks, centralBuf, eocd]));
}
/* ---------------- 收集文件 ---------------- */
const INCLUDE_ROOT = [
"theme.yaml",
"settings.yaml",
"LICENSE",
"README.md",
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
];
const INCLUDE_DIRS = ["templates"];
const EXCLUDE_DIRS = new Set([".git", "node_modules", "src", "dist", "compat", ".astro"]);
const entries = [];
for (const name of INCLUDE_ROOT) {
const full = path.join(root, name);
if (fs.existsSync(full)) {
entries.push({ name, data: fs.readFileSync(full), date: fs.statSync(full).mtime });
}
}
function walk(dir, prefix) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const abs = path.join(dir, entry.name);
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
if (EXCLUDE_DIRS.has(entry.name)) continue;
walk(abs, rel);
} else {
entries.push({ name: rel, data: fs.readFileSync(abs), date: fs.statSync(abs).mtime });
}
}
}
for (const dir of INCLUDE_DIRS) {
const full = path.join(root, dir);
if (fs.existsSync(full)) walk(full, dir);
}
const version = readThemeVersion();
const outFile = path.join(root, "dist", `warm-island-${version}.zip`);
buildZip(entries, outFile);
const sizeMB = (fs.statSync(outFile).size / 1024 / 1024).toFixed(2);
console.log(`[package] dist/warm-island-${version}.zip(${entries.length} 个文件,${sizeMB}MB)`);