/ 内容 -> zip 根 * - public/static// 静态资源 -> zip 的 static/ 前缀 * * 每个插件自动从 addon//info.php 读取 title/author/intro/version, * 输出到 build/addons/,并生成 wxapp_appmall_addon_list 测试记录 SQL(build/sql/--appmall_addon_list.sql)。 * * 用法: * php scripts/package_all_addons.php [bump] [skip:name1,name2,...] * * 例: * php scripts/package_all_addons.php # 按 info.php 当前版本打包(不改版本号) * php scripts/package_all_addons.php bump # 每个插件版本按 patch +1 并写回 info.php * php scripts/package_all_addons.php bump skip:market # 同上但跳过 market * php scripts/package_all_addons.php skip:market # 跳过 market,版本号不变 */ declare(strict_types=1); $root = dirname(__DIR__) . DIRECTORY_SEPARATOR; // d:/wwwroot/ywxapp_dev/ // 参数:[bump] [skip:name1,name2,...] // bump 可选:每个插件版本按 patch +1 并写回 addons//info.php // skip: 可选:跳过指定插件(逗号分隔) $bumpArg = $argv[1] ?? ''; $bump = false; if ($bumpArg === 'bump') { $bump = true; $skipArg = $argv[2] ?? ''; } else { $skipArg = $bumpArg; } $skip = []; if (str_starts_with($skipArg, 'skip:')) { $skip = array_filter(explode(',', substr($skipArg, 5)), static fn($s) => $s !== ''); } $addonsDir = $root . 'addon' . DIRECTORY_SEPARATOR; if (!is_dir($addonsDir)) { fwrite(STDERR, "addons 目录不存在: {$addonsDir}\n"); exit(1); } if (!class_exists('ZipArchive')) { fwrite(STDERR, "ZipArchive 扩展不可用\n"); exit(1); } $marketDir = $root . 'build' . DIRECTORY_SEPARATOR . 'addons' . DIRECTORY_SEPARATOR; if (!is_dir($marketDir)) { mkdir($marketDir, 0755, true); } $rows = []; $ok = []; $fail = []; foreach (array_diff(scandir($addonsDir), ['.', '..']) as $name) { $addonDir = $addonsDir . $name . DIRECTORY_SEPARATOR; if (!is_dir($addonDir)) { continue; } $infoFile = $addonDir . 'info.php'; if (!is_file($infoFile)) { $fail[] = $name . ' (缺 info.php)'; continue; } if (in_array($name, $skip, true)) { echo "[skip] {$name}\n"; continue; } $info = include $infoFile; $title = (string) ($info['title'] ?? $name); $author = (string) ($info['author'] ?? ''); $intro = (string) ($info['intro'] ?? ''); $version = (string) ($info['version'] ?? ''); if (!$version) { $fail[] = $name . ' (info.php 缺 version)'; continue; } // 自动递增版本号(bump 模式):先记录旧值,+1 后写回 info.php $oldVersion = $version; if ($bump) { $version = bumpVersion($oldVersion, 'patch'); writeAddonVersion($infoFile, $version); } $zipFile = $marketDir . $name . '-' . $version . '.zip'; $staticDir = $root . 'public' . DIRECTORY_SEPARATOR . 'static' . DIRECTORY_SEPARATOR . $name . DIRECTORY_SEPARATOR; $zip = new ZipArchive(); if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { $fail[] = $name . ' (无法创建 zip)'; continue; } addDirToZip($zip, $addonDir, ''); if (is_dir($staticDir)) { addDirToZip($zip, $staticDir, 'static/'); } $zip->close(); $hash = hash_file('sha256', $zipFile); $size = filesize($zipFile); $absPath = realpath($zipFile); $esc = static function (string $v): string { return str_replace(["\\", "'"], ["\\\\", "\\'"], $v); }; $sql = "INSERT INTO `wxapp_appmall_addon_list` " . "(`name`,`title`,`version`,`price`,`author`,`description`,`logo`,`file_path`,`file_hash`,`status`,`create_at`,`update_at`) VALUES (" . "'" . $esc($name) . "'," . "'" . $esc($title) . "'," . "'" . $esc($version) . "'," . "0.00," . "'" . $esc($author) . "'," . "'" . $esc($intro) . "'," . "''," . "'" . $esc($absPath) . "'," . "'" . $hash . "'," . "1,UNIX_TIMESTAMP(),UNIX_TIMESTAMP());"; $sqlDir = $root . 'build' . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR; if (!is_dir($sqlDir)) { mkdir($sqlDir, 0755, true); } $sqlFile = $sqlDir . $name . '-' . $version . '-appmall_addon_list.sql'; file_put_contents($sqlFile, "-- 插件市场测试记录(由 scripts/package_all_addons.php 生成)\n-- zip: {$absPath}\n-- sha256: {$hash}\n" . $sql . "\n"); $verLabel = $bump ? ($oldVersion . ' -> ' . $version) : $version; $rows[] = [$name, $version, $size, $absPath]; $ok[] = $name; printf(" [ok] %-12s v%s %8d bytes\n", $name, $verLabel, $size); } echo "\n=== 打包汇总 ===\n"; echo "成功: " . count($ok) . " 失败: " . count($fail) . "\n"; if ($fail) { echo "失败列表:\n - " . implode("\n - ", $fail) . "\n"; } echo "输出目录: {$marketDir}\n"; /** * 在 x.y.z 基础上递增指定段(major/minor/patch) */ function bumpVersion(string $ver, string $level = 'patch'): string { if (!preg_match('/^(\d+)\.(\d+)\.(\d+)$/', $ver, $m)) { return $ver; } $major = (int) $m[1]; $minor = (int) $m[2]; $patch = (int) $m[3]; switch ($level) { case 'major': $major++; $minor = 0; $patch = 0; break; case 'minor': $minor++; $patch = 0; break; case 'patch': default: $patch++; break; } return $major . '.' . $minor . '.' . $patch; } /** * 将新版本号写回插件 info.php */ function writeAddonVersion(string $infoFile, string $ver): void { if (!is_file($infoFile)) { return; } $c = file_get_contents($infoFile); $c = preg_replace( "/(['\"])version['\"]\s*=>\s*['\"][^'\"]*['\"]/", "'version' => '" . $ver . "'", $c ); file_put_contents($infoFile, $c); } /** * 将目录递归加入 zip(结构对齐 AddonService::addDirectoryToZip) */ function addDirToZip(ZipArchive $zip, string $dir, string $prefix): void { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($files as $file) { $filePath = $file->getRealPath(); $rel = ltrim(str_replace(DIRECTORY_SEPARATOR, '/', substr($filePath, strlen($dir))), '/'); if ($file->isDir()) { $zip->addEmptyDir($prefix . $rel); } else { $zip->addFile($filePath, $prefix . $rel); } } }