SnowCat 雪猫底层全源码:NapCat 4.18.28 底座 + SnowLuma 能力移植 + V8 通道适配 9.9.37
26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
This commit is contained in:
commit
b9655a5ab1
1048 files changed
+130851
No files matched your search
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* PR Build - 发布构建产物到 napcat-pr-release 仓库
|
||||
*
|
||||
* 功能:
|
||||
* 1. 将 CI 构建产物(Framework / Shell)打包为 zip
|
||||
* 2. 从 NapCat-Installer 仓库拉取 install.sh 并替换下载链接
|
||||
* 3. 发布到 napcat-pr-release 仓库的 GitHub Release
|
||||
*
|
||||
* 环境变量:
|
||||
* RELEASE_PAT - 具备目标仓库 release 写权限的 PAT
|
||||
* PR_NUMBER - PR 编号
|
||||
* PR_SHA - PR HEAD 提交 SHA
|
||||
* NAPCAT_VERSION - 构建版本号
|
||||
* ARTIFACTS_DIR - 构建产物目录 (默认 ./artifacts)
|
||||
* RELEASE_REPO - 发布仓库 (默认 NapNeko/napcat-pr-release)
|
||||
* INSTALLER_REPO - 安装脚本仓库 (默认 NapNeko/NapCat-Installer)
|
||||
* INSTALLER_BRANCH - 安装脚本分支 (默认 main)
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { GitHubAPI, getEnv, setOutput } from './lib/github.ts';
|
||||
import { toProxyUrl } from './lib/comment.ts';
|
||||
|
||||
interface AssetEntry {
|
||||
name: string;
|
||||
path: string;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
async function main (): Promise<void> {
|
||||
console.log('📦 PR Release Publisher\n');
|
||||
|
||||
const token = getEnv('RELEASE_PAT', true);
|
||||
const prNumber = getEnv('PR_NUMBER', true);
|
||||
const prSha = getEnv('PR_SHA', true);
|
||||
const version = getEnv('NAPCAT_VERSION') || 'unknown';
|
||||
const artifactsDir = getEnv('ARTIFACTS_DIR') || './artifacts';
|
||||
const releaseRepo = getEnv('RELEASE_REPO') || 'NapNeko/napcat-pr-release';
|
||||
const installerRepo = getEnv('INSTALLER_REPO') || 'NapNeko/NapCat-Installer';
|
||||
const installerBranch = getEnv('INSTALLER_BRANCH') || 'main';
|
||||
|
||||
const [releaseOwner, releaseRepoName] = releaseRepo.split('/');
|
||||
const shortSha = prSha.substring(0, 7);
|
||||
const tag = `pr-${prNumber}-${shortSha}`;
|
||||
const releaseName = `PR #${prNumber} (${shortSha})`;
|
||||
const downloadBase = `https://github.com/${releaseRepo}/releases/download/${tag}`;
|
||||
|
||||
console.log(` Tag: ${tag}`);
|
||||
console.log(` Repo: ${releaseRepo}`);
|
||||
console.log(` Version: ${version}`);
|
||||
console.log(` SHA: ${shortSha}\n`);
|
||||
|
||||
const github = new GitHubAPI(token);
|
||||
const releaseDir = resolve('./release-assets'); // 使用绝对路径,避免 zip cwd 切换后相对路径解析错误
|
||||
mkdirSync(releaseDir, { recursive: true });
|
||||
|
||||
// ---- 打包构建产物 ----
|
||||
const artifacts = ['NapCat.Framework', 'NapCat.Shell'];
|
||||
for (const name of artifacts) {
|
||||
const dir = join(artifactsDir, name);
|
||||
if (existsSync(dir)) {
|
||||
const zipPath = join(releaseDir, `${name}.zip`);
|
||||
console.log(`📦 Packing ${name}...`);
|
||||
execSync(`zip -qr "${zipPath}" .`, { cwd: dir, stdio: 'inherit' });
|
||||
console.log(` ✓ ${name}.zip\n`);
|
||||
} else {
|
||||
console.log(` ⚠ ${name} 目录不存在,跳过\n`);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 拉取并修改安装脚本 ----
|
||||
console.log('📜 Fetching install.sh...');
|
||||
const scriptUrl = `https://raw.githubusercontent.com/${installerRepo}/${installerBranch}/script/install.sh`;
|
||||
try {
|
||||
const response = await fetch(scriptUrl);
|
||||
if (response.ok) {
|
||||
let script = await response.text();
|
||||
// 替换下载链接(保留代理前缀 ${target_proxy:+${target_proxy}/})
|
||||
const shellUrl = `${downloadBase}/NapCat.Shell.zip`;
|
||||
const originalScript = script;
|
||||
script = script.replace(
|
||||
/napcat_download_url="\$\{target_proxy:\+\$\{target_proxy\}\/\}https:\/\/github\.com\/NapNeko\/NapCatQQ\/releases\/latest\/download\/NapCat\.Shell\.zip"/g,
|
||||
`napcat_download_url="\${target_proxy:+\${target_proxy}/}${shellUrl}"`
|
||||
);
|
||||
if (script === originalScript) {
|
||||
console.log(' ⚠ Warning: URL replacement did not match - install.sh format may have changed');
|
||||
}
|
||||
const scriptPath = join(releaseDir, 'install.sh');
|
||||
writeFileSync(scriptPath, script, { mode: 0o755 });
|
||||
console.log(` ✓ Patched download URL -> ${shellUrl}\n`);
|
||||
} else {
|
||||
console.log(` ⚠ Failed to fetch install.sh: ${response.status}\n`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(` ⚠ Failed to fetch install.sh: ${(e as Error).message}\n`);
|
||||
}
|
||||
|
||||
// ---- 收集所有要上传的文件 ----
|
||||
const assets: AssetEntry[] = [];
|
||||
for (const file of readdirSync(releaseDir)) {
|
||||
const ext = file.split('.').pop()?.toLowerCase();
|
||||
const contentType = ext === 'zip' ? 'application/zip' : ext === 'sh' ? 'application/x-sh' : 'application/octet-stream';
|
||||
assets.push({ name: file, path: join(releaseDir, file), contentType });
|
||||
}
|
||||
console.log(`📋 Assets to upload: ${assets.map(a => a.name).join(', ')}\n`);
|
||||
|
||||
// ---- 删除旧 release ----
|
||||
console.log('🗑️ Checking for existing release...');
|
||||
const existing = await github.getReleaseByTag(releaseOwner, releaseRepoName, tag);
|
||||
if (existing) {
|
||||
console.log(` Deleting existing release (id=${existing.id})...`);
|
||||
await github.deleteRelease(releaseOwner, releaseRepoName, existing.id);
|
||||
await github.deleteRef(releaseOwner, releaseRepoName, `tags/${tag}`);
|
||||
console.log(' ✓ Deleted\n');
|
||||
} else {
|
||||
console.log(' No existing release\n');
|
||||
}
|
||||
|
||||
// ---- 创建 Release ----
|
||||
const directInstallUrl = `${downloadBase}/install.sh`;
|
||||
const proxyInstallUrl = toProxyUrl(directInstallUrl);
|
||||
const releaseBody = [
|
||||
`## NapCat PR #${prNumber} 测试版本`,
|
||||
'',
|
||||
`- **版本**: \`${version}\``,
|
||||
`- **提交**: \`${prSha}\``,
|
||||
`- **来源**: [NapCatQQ PR #${prNumber}](https://github.com/NapNeko/NapCatQQ/pull/${prNumber})`,
|
||||
'',
|
||||
'### 快速安装 (Linux)',
|
||||
'',
|
||||
'**直连(需要能访问 GitHub)**',
|
||||
'```bash',
|
||||
`curl -sSL ${directInstallUrl} | bash`,
|
||||
'```',
|
||||
'',
|
||||
'**加速(国内推荐,ghfast.top)**',
|
||||
'```bash',
|
||||
`curl -sSL ${proxyInstallUrl} | bash`,
|
||||
'```',
|
||||
'',
|
||||
'> ⚠️ 此为 PR 测试版本,约 5 天后会被自动清理。',
|
||||
].join('\n');
|
||||
|
||||
console.log('🚀 Creating release...');
|
||||
const release = await github.createRelease(
|
||||
releaseOwner, releaseRepoName, tag, releaseName, releaseBody, true
|
||||
);
|
||||
console.log(` ✓ Release created: ${release.html_url}\n`);
|
||||
|
||||
// ---- 上传资产 ----
|
||||
console.log('📤 Uploading assets...');
|
||||
for (const asset of assets) {
|
||||
const data = readFileSync(asset.path);
|
||||
const uploaded = await github.uploadReleaseAsset(
|
||||
release.upload_url, asset.name, data, asset.contentType
|
||||
);
|
||||
console.log(` ✓ ${asset.name} -> ${uploaded.browser_download_url}`);
|
||||
}
|
||||
|
||||
console.log(`\n✅ Release published: ${release.html_url}`);
|
||||
|
||||
// 输出供后续 job 使用
|
||||
setOutput('release_url', release.html_url);
|
||||
setOutput('release_tag', tag);
|
||||
setOutput('install_url', `${downloadBase}/install.sh`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('❌ Error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in new issue
Block a user