Files
tutti/apps/desktop/scripts/upsert-release-download-links.mjs
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

161 lines
4.0 KiB
JavaScript

#!/usr/bin/env node
import { readdir, readFile, writeFile } from "node:fs/promises";
import { basename } from "node:path";
import { pathToFileURL } from "node:url";
import { buildLimitedGithubReleaseBody } from "./lib/githubReleaseBody.mjs";
const SECTION_START = "<!-- tutti-desktop-download-links:start -->";
const SECTION_END = "<!-- tutti-desktop-download-links:end -->";
function normalizeBaseUrl(value) {
return value.replace(/\/+$/, "");
}
function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function removeManagedSection(body) {
const managedSectionPattern = new RegExp(
`\\n*${escapeRegex(SECTION_START)}[\\s\\S]*?${escapeRegex(SECTION_END)}\\n*`,
"g"
);
return body.replace(managedSectionPattern, "\n").trimEnd();
}
function findAssetName(assetNames, pattern) {
return assetNames.find((name) => {
pattern.lastIndex = 0;
return pattern.test(name);
});
}
function resolveDesktopDownloadLinks(
assetNames,
releaseAssetPath,
releaseAssetBaseUrl
) {
const desktopAssets = [
{
label: "macOS Apple Silicon (arm64)",
pattern: /-mac-arm64\.dmg$/i
},
{
label: "macOS Intel (x64)",
pattern: /-mac-x64\.dmg$/i
},
{
label: "macOS Universal",
pattern: /-mac-universal\.dmg$/i
},
{
label: "Windows (x64, signed)",
pattern: /-win-x64\.exe$/i
}
];
const normalizedBaseUrl = normalizeBaseUrl(releaseAssetBaseUrl);
const normalizedAssetPath = releaseAssetPath
.split("/")
.filter(Boolean)
.map(encodeURIComponent)
.join("/");
return desktopAssets
.map(({ label, pattern }) => {
const assetName = findAssetName(assetNames, pattern);
if (!assetName) {
return null;
}
const assetUrl = `${normalizedBaseUrl}/${normalizedAssetPath}/${encodeURIComponent(assetName)}`;
return `- [${label}](${assetUrl})`;
})
.filter(Boolean);
}
function buildUpdatedReleaseBody({
assetNames,
existingBody,
releaseAssetBaseUrl,
releaseTag,
releaseAssetPath = releaseTag
}) {
const cleanedBody = removeManagedSection(existingBody);
if (!releaseAssetPath || !releaseAssetBaseUrl) {
return buildLimitedGithubReleaseBody({ existingBody: cleanedBody });
}
const directDownloadLinks = resolveDesktopDownloadLinks(
[...assetNames].sort((left, right) => left.localeCompare(right)),
releaseAssetPath,
releaseAssetBaseUrl
);
if (directDownloadLinks.length === 0) {
return buildLimitedGithubReleaseBody({ existingBody: cleanedBody });
}
const downloadSection = [
SECTION_START,
"### Direct Downloads",
...directDownloadLinks,
SECTION_END
].join("\n");
return buildLimitedGithubReleaseBody({
existingBody: cleanedBody,
trailingSections: [downloadSection]
});
}
async function main() {
const [existingBodyPath, assetDirPath, outputBodyPath] =
process.argv.slice(2);
if (!existingBodyPath || !assetDirPath || !outputBodyPath) {
throw new Error(
"Usage: node apps/desktop/scripts/upsert-release-download-links.mjs <existing-body-path> <asset-dir> <output-body-path>"
);
}
const releaseTag = (process.env.RELEASE_TAG ?? "").trim();
const releaseAssetPath = (
process.env.RELEASE_ASSET_PATH ?? releaseTag
).trim();
const releaseAssetBaseUrl = (process.env.RELEASE_ASSET_BASE_URL ?? "").trim();
const existingBody = await readFile(existingBodyPath, "utf8");
const assetNames = (await readdir(assetDirPath)).map((name) =>
basename(name)
);
const nextBody = buildUpdatedReleaseBody({
assetNames,
existingBody,
releaseAssetBaseUrl,
releaseAssetPath,
releaseTag
});
await writeFile(outputBodyPath, nextBody, "utf8");
}
if (
process.argv[1] &&
import.meta.url === pathToFileURL(process.argv[1]).href
) {
void main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
}
export {
SECTION_END,
SECTION_START,
buildUpdatedReleaseBody,
findAssetName,
removeManagedSection,
resolveDesktopDownloadLinks
};