Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions plugins/image-batch-studio/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Changelog

## v0.1.1 - 2026-07-29
## v0.1.1 - 2026-07-28

- 新增 Windows x64 与 ARM64 支持,首次使用时按当前系统架构安装 Sharp 运行组件
- 新增 Windows x64 与 ARM64 支持,发布包内置 macOS/Windows 四套 Sharp 原生运行时
- 修复 EXIF 方向图片的尺寸计算、裁剪和缩放结果,避免多帧 GIF 在普通处理流程中静默丢帧。
- 修复图片水印缺少文件、超出画布、模块开关失效及命名模板二次替换问题。
- 升级 Sharp 与 Vite,使用固定 URL、SHA-512 和原子安装固化动态运行组件
- 将插件压缩包缩减至约 8MB,并增加 15MB 体积门禁、动态运行时 smoke Windows CI 验证。
- 升级 Sharp 与 Vite,使用独立 lockfile 和 `npm ci` 固化发布运行时依赖
- 增加发布目录 smoke test、跨平台原生工件校验和 Windows CI 验证。

## v0.1.0 - 2026-06-02

Expand Down
6 changes: 3 additions & 3 deletions plugins/image-batch-studio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ZTools 图片批处理插件,提供面向图片文件、PDF 文件和文件夹
## 平台支持

- macOS:当前版本已支持 Apple Silicon M 系列(arm64)和 Intel(x64)。
- Windows:支持 x64 与 ARM64,首次使用图片能力时按当前架构安装 Sharp 运行组件
- Windows:支持 x64 与 ARM64,发布包内置对应 Sharp 运行时

## 功能

Expand Down Expand Up @@ -42,7 +42,7 @@ ZTools 图片批处理插件,提供面向图片文件、PDF 文件和文件夹

- React + Vite 构建界面。
- TypeScript 编写 UI、预加载层和处理器。
- Sharp 处理图片压缩、转换、裁剪、水印、拼图、圆角等能力;运行组件安装后可离线使用
- Sharp 处理图片压缩、转换、裁剪、水印、拼图、圆角等能力。
- pdf-lib 处理 PDF 合并。
- gifenc 处理 GIF 合成。
- 所有处理在 ZTools 插件本地预加载进程内完成。
Expand All @@ -67,4 +67,4 @@ npm run install:local
npm run smoke:installed
```

`verify:runtime` 会校验 macOS arm64/x64 与 Windows x64/ARM64 的固定下载地址、版本和 SHA-512,确认发布包未混入原生运行时。`verify:size` 会检查压缩产物不超过 EdgeOne 的 15MB 限制。`smoke:installed` 会在当前系统生成测试图片和 PDF,验证图片处理、拼图、GIF 合成、PDF 合并以及 ZTools 安装记录。
`verify:runtime` 会检测打包产物是否包含 macOS arm64/x64 与 Windows x64/ARM64 的 Sharp 运行时,以及各平台原生文件的 ASAR 解包配置。`smoke:installed` 会在当前系统生成测试图片和 PDF,验证图片处理、拼图、GIF 合成、PDF 合并以及 ZTools 安装记录。
3 changes: 1 addition & 2 deletions plugins/image-batch-studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "tsc --noEmit && tsc -p tsconfig.preload.json --noEmit",
"build": "vite build && tsc -p tsconfig.preload.json && node scripts/prepare-dist.mjs && node scripts/verify-dist-size.mjs",
"build": "vite build && tsc -p tsconfig.preload.json && node scripts/prepare-dist.mjs",
"install:local": "node scripts/install-local.mjs",
"verify:runtime": "node scripts/verify-sharp-runtimes.mjs",
"verify:size": "node scripts/verify-dist-size.mjs",
"smoke:dist": "node scripts/smoke-dist-runtime.mjs",
"smoke:installed": "node scripts/smoke-installed.mjs"
},
Expand Down
1 change: 1 addition & 0 deletions plugins/image-batch-studio/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"version": "0.1.1",
"main": "index.html",
"preload": "preload/services.js",
"unpack": "@(preload/node_modules/**/*.node|preload/node_modules/**/*.dll|preload/node_modules/**/*.dylib)",
"logo": "logo.svg",
"platform": ["darwin", "win32"],
"categories": ["media"],
Expand Down
47 changes: 38 additions & 9 deletions plugins/image-batch-studio/scripts/prepare-dist.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
Expand All @@ -11,24 +12,26 @@ const runtimeDir = path.join(root, "scripts", "runtime");
const runtimeConfig = JSON.parse(
await fs.readFile(path.join(root, "scripts", "sharp-runtime-targets.json"), "utf8")
);
const [primaryTarget, ...additionalTargets] = runtimeConfig.targets;
const runtimePackage = JSON.parse(await fs.readFile(path.join(runtimeDir, "package.json"), "utf8"));
const runtimeDependencies = runtimePackage.dependencies;
if (runtimeDependencies.sharp !== runtimeConfig.sharpVersion) {
throw new Error("Sharp runtime version must match scripts/sharp-runtime-targets.json");
}
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";

function npmCi(directory) {
function npmCi(directory, target) {
execFileSync(npmCommand, [
"ci",
"--omit=dev",
"--omit=optional",
"--include=optional",
`--os=${target.os}`,
`--cpu=${target.cpu}`,
"--no-audit",
"--no-fund"
], {
cwd: directory,
stdio: "inherit",
shell: process.platform === "win32"
stdio: "inherit"
});
}

Expand All @@ -37,6 +40,35 @@ async function copyRuntimeManifest(directory) {
await fs.copyFile(path.join(runtimeDir, "package-lock.json"), path.join(directory, "package-lock.json"));
}

async function installSharpRuntime(target) {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), `ztools-sharp-${target.os}-${target.cpu}-`));
try {
await copyRuntimeManifest(tempDir);
npmCi(tempDir, target);

const sourceImgDir = path.join(tempDir, "node_modules", "@img");
const targetImgDir = path.join(preloadDir, "node_modules", "@img");

await fs.mkdir(targetImgDir, { recursive: true });
for (const runtimePackage of target.packages) {
const sourcePackage = path.join(sourceImgDir, runtimePackage.name);
await fs.access(sourcePackage);
await fs.cp(sourcePackage, path.join(targetImgDir, runtimePackage.name), {
recursive: true,
force: true
});
}
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
}

async function installAdditionalSharpRuntimes() {
for (const target of additionalTargets) {
await installSharpRuntime(target);
}
}

await fs.mkdir(preloadDir, { recursive: true });
await fs.mkdir(sharedDir, { recursive: true });
const pluginConfig = JSON.parse(await fs.readFile(path.join(root, "plugin.json"), "utf8"));
Expand All @@ -45,10 +77,7 @@ await fs.writeFile(path.join(dist, "plugin.json"), `${JSON.stringify(pluginConfi
await fs.copyFile(path.join(root, "logo.svg"), path.join(dist, "logo.svg"));
await fs.copyFile(path.join(root, "cover.png"), path.join(dist, "cover.png"));
await copyRuntimeManifest(preloadDir);
await fs.copyFile(
path.join(root, "scripts", "sharp-runtime-targets.json"),
path.join(preloadDir, "sharp-runtime-targets.json")
);
await fs.writeFile(path.join(sharedDir, "package.json"), '{"type":"commonjs"}\n');

npmCi(preloadDir);
npmCi(preloadDir, primaryTarget);
await installAdditionalSharpRuntimes();
76 changes: 14 additions & 62 deletions plugins/image-batch-studio/scripts/sharp-runtime-targets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,33 @@
"sharpVersion": "0.35.3",
"targets": [
{
"platform": "darwin",
"arch": "arm64",
"os": "darwin",
"cpu": "arm64",
"packages": [
{
"name": "@img/sharp-darwin-arm64",
"version": "0.35.3",
"url": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
"fallbackUrl": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
"integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
"size": 110055,
"artifacts": [".node"]
},
{
"name": "@img/sharp-libvips-darwin-arm64",
"version": "1.3.2",
"url": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
"fallbackUrl": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
"integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
"size": 8254556,
"artifacts": [".dylib"]
}
{ "name": "sharp-darwin-arm64", "artifacts": [".node"] },
{ "name": "sharp-libvips-darwin-arm64", "artifacts": [".dylib"] }
]
},
{
"platform": "darwin",
"arch": "x64",
"os": "darwin",
"cpu": "x64",
"packages": [
{
"name": "@img/sharp-darwin-x64",
"version": "0.35.3",
"url": "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
"fallbackUrl": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
"integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
"size": 114713,
"artifacts": [".node"]
},
{
"name": "@img/sharp-libvips-darwin-x64",
"version": "1.3.2",
"url": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
"fallbackUrl": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
"integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
"size": 8838419,
"artifacts": [".dylib"]
}
{ "name": "sharp-darwin-x64", "artifacts": [".node"] },
{ "name": "sharp-libvips-darwin-x64", "artifacts": [".dylib"] }
]
},
{
"platform": "win32",
"arch": "x64",
"os": "win32",
"cpu": "x64",
"packages": [
{
"name": "@img/sharp-win32-x64",
"version": "0.35.3",
"url": "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
"fallbackUrl": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
"integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
"size": 8434051,
"artifacts": [".node", ".dll"]
}
{ "name": "sharp-win32-x64", "artifacts": [".node", ".dll"] }
]
},
{
"platform": "win32",
"arch": "arm64",
"os": "win32",
"cpu": "arm64",
"packages": [
{
"name": "@img/sharp-win32-arm64",
"version": "0.35.3",
"url": "https://registry.npmmirror.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
"fallbackUrl": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
"integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
"size": 7785250,
"artifacts": [".node", ".dll"]
}
{ "name": "sharp-win32-arm64", "artifacts": [".node", ".dll"] }
]
}
]
Expand Down
19 changes: 4 additions & 15 deletions plugins/image-batch-studio/scripts/smoke-dist-runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@ import { fileURLToPath } from "node:url";
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const processorPath = path.join(root, "dist", "preload", "processor.js");
const requireDist = createRequire(processorPath);
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "image-batch-dist-smoke-"));
process.env.IMAGE_BATCH_RUNTIME_ROOT = path.join(directory, "runtime");
const runtime = requireDist("./sharp-runtime.js");
const runtimeStatus = await runtime.installSharpRuntime();
if (runtimeStatus.state !== "ready") throw new Error(runtimeStatus.error || "Dynamic Sharp runtime installation failed");
const processor = requireDist(processorPath);
const sharp = runtime.getSharp();
const sharp = requireDist("sharp");
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "image-batch-dist-smoke-"));

try {
const inputPath = path.join(directory, "input.png");
Expand All @@ -40,14 +36,7 @@ try {
throw new Error(`Unexpected dist output metadata: ${JSON.stringify(metadata)}`);
}

console.log(
JSON.stringify(
{ ok: true, platform: process.platform, arch: process.arch, runtime: runtimeStatus.version },
null,
2
)
);
console.log(JSON.stringify({ ok: true, platform: process.platform, arch: process.arch }, null, 2));
} finally {
sharp.cache(false);
await fs.rm(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
await fs.rm(directory, { recursive: true, force: true });
}
10 changes: 2 additions & 8 deletions plugins/image-batch-studio/scripts/smoke-installed.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ const installedPlugin = path.join(dataRoot, "plugins", "image-batch-studio");
const dbPath = modernLayout
? path.join(dataRoot, "lmdb", "device")
: path.join(dataRoot, "lmdb");
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "ztools-image-batch-installed-"));
process.env.IMAGE_BATCH_RUNTIME_ROOT = path.join(dir, "runtime");
const runtime = require(path.join(installedPlugin, "preload", "sharp-runtime.js"));
const runtimeStatus = await runtime.installSharpRuntime();
if (runtimeStatus.state !== "ready") {
throw new Error(runtimeStatus.error || "Installed dynamic Sharp runtime failed");
}
const processor = require(path.join(installedPlugin, "preload", "processor.js"));

function readInstalledPluginRecord() {
Expand Down Expand Up @@ -80,6 +73,7 @@ async function makePdf(filePath, text) {
await fs.writeFile(filePath, await doc.save());
}

const dir = await fs.mkdtemp(path.join(os.tmpdir(), "ztools-image-batch-installed-"));
const out = path.join(dir, "out");
const first = path.join(dir, "first.png");
const second = path.join(dir, "second.png");
Expand Down Expand Up @@ -170,4 +164,4 @@ if (!installedRecord.features?.some((feature) => feature.code === "image-batch")
throw new Error(`Installed plugin record is missing image-batch feature: ${JSON.stringify(installedRecord)}`);
}

console.log(JSON.stringify({ ok: true, dir, runtime: runtimeStatus.version }, null, 2));
console.log(JSON.stringify({ ok: true, dir }, null, 2));
38 changes: 0 additions & 38 deletions plugins/image-batch-studio/scripts/verify-dist-size.mjs

This file was deleted.

Loading
Loading