feat(overview-artifact): 產生可預覽總覽與截圖 fallback
This commit is contained in:
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Upload overview screenshots to a Gitea issue and update its attachment index.
|
||||
* The manifest is produced by the preview step and contains local PNG paths and hashes.
|
||||
*/
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import {
|
||||
ScriptError,
|
||||
expectOk,
|
||||
fetchIssue,
|
||||
giteaRequest,
|
||||
giteaUpload,
|
||||
main,
|
||||
parseFlags,
|
||||
parseIndex,
|
||||
parseRepo,
|
||||
preflight,
|
||||
resolveLogin,
|
||||
} from './lib.js';
|
||||
import { upsertLineInSection } from './issue-body.js';
|
||||
|
||||
const INDEX_LINE = '圖解版總覽附件:';
|
||||
|
||||
main(async () => {
|
||||
const flags = parseFlags(process.argv.slice(2), {
|
||||
required: ['repo', 'index', 'manifest'],
|
||||
optional: ['host'],
|
||||
booleans: ['dry-run'],
|
||||
});
|
||||
const repo = parseRepo(flags.repo);
|
||||
const index = parseIndex(flags.index);
|
||||
const manifest = JSON.parse(readFileSync(resolve(flags.manifest), 'utf8'));
|
||||
const screenshots = manifest.screenshots;
|
||||
if (!Array.isArray(screenshots) || screenshots.length === 0) {
|
||||
throw new ScriptError('MANIFEST_INVALID', 'manifest 必須包含至少一張 screenshots');
|
||||
}
|
||||
for (const item of screenshots) {
|
||||
if (!item.role || !item.path || !item.fileName || !item.sha256 || !existsSync(resolve(item.path))) {
|
||||
throw new ScriptError('MANIFEST_INVALID', '每張截圖都必須有 role、path、fileName、sha256 且檔案存在');
|
||||
}
|
||||
}
|
||||
const login = resolveLogin({ host: flags.host });
|
||||
const issuePath = `/repos/${repo}/issues/${index}`;
|
||||
const assetsPath = `${issuePath}/assets`;
|
||||
if (flags['dry-run']) {
|
||||
return {
|
||||
dryRun: true,
|
||||
repo,
|
||||
index,
|
||||
requests: [
|
||||
{ method: 'GET', path: issuePath },
|
||||
{ method: 'GET', path: assetsPath },
|
||||
...screenshots.map((item) => ({ method: 'POST', path: assetsPath, file: item.path })),
|
||||
],
|
||||
};
|
||||
}
|
||||
await preflight(login, repo);
|
||||
const issue = await fetchIssue(login, repo, index);
|
||||
const assets = expectOk(await giteaRequest(login, 'GET', assetsPath), `GET ${assetsPath}`);
|
||||
const existing = Array.isArray(assets) ? assets : [];
|
||||
const indexed = parseIndexedAssets(issue.body ?? '');
|
||||
const uploaded = [];
|
||||
for (const item of screenshots) {
|
||||
const indexedAsset = indexed.find((asset) => asset.fileName === item.fileName && asset.sha256 === item.sha256);
|
||||
const existingAsset = existing.find((asset) => asset.name === item.fileName && asset.sha256 === item.sha256);
|
||||
const asset = indexedAsset ?? existingAsset;
|
||||
if (asset) {
|
||||
uploaded.push({ ...item, url: asset.url ?? asset.browser_download_url ?? asset.download_url, reused: true });
|
||||
continue;
|
||||
}
|
||||
const response = expectOk(
|
||||
await giteaUpload(login, `${assetsPath}?name=${encodeURIComponent(item.fileName)}`, resolve(item.path), item.fileName),
|
||||
`POST ${assetsPath}`,
|
||||
);
|
||||
uploaded.push({ ...item, url: response.browser_download_url ?? response.download_url, reused: false });
|
||||
}
|
||||
const body = updateIndex(issue.body ?? '', uploaded);
|
||||
const update = body === issue.body ? null : expectOk(await giteaRequest(login, 'PATCH', issuePath, { body: { body } }), `PATCH ${issuePath}`);
|
||||
return { repo, index, uploaded, updated: update !== null, url: update?.html_url ?? issue.html_url };
|
||||
});
|
||||
|
||||
function parseIndexedAssets(body) {
|
||||
const line = body.split('\n').find((row) => row.startsWith(INDEX_LINE)) ?? '';
|
||||
return [...line.matchAll(/([^=;\s]+)=([^;\s]+)\s+sha256=([a-f0-9]{64})\s+(\S+)/g)].map((match) => ({
|
||||
role: match[1],
|
||||
fileName: match[2],
|
||||
sha256: match[3],
|
||||
url: match[4],
|
||||
}));
|
||||
}
|
||||
|
||||
function updateIndex(body, assets) {
|
||||
const lines = assets.map((asset) => `${asset.role}=${asset.fileName} sha256=${asset.sha256} ${asset.url ?? '(網址未回傳)'}`);
|
||||
return upsertLineInSection(body, '總覽', `${INDEX_LINE}${lines.join(';')}`);
|
||||
}
|
||||
Reference in New Issue
Block a user