[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
187
scripts/resolve-et-bin-release.cjs
Normal file
187
scripts/resolve-et-bin-release.cjs
Normal file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
//
|
||||
// Resolve the EternalTerminal `et` client binary release used by
|
||||
// build-packages.
|
||||
//
|
||||
// Priority:
|
||||
// 1. ET_BIN_RELEASE from workflow input / repository variable.
|
||||
// 2. Latest non-draft, non-prerelease GitHub Release whose tag is
|
||||
// et-bin-* in ET_BIN_OWNER/ET_BIN_REPO. By default this is a
|
||||
// dedicated sibling binary repository named Netcatty-et-bin.
|
||||
//
|
||||
// In GitHub Actions, the resolved tag is written back to $GITHUB_ENV so
|
||||
// later steps can run scripts/fetch-et-binaries.cjs without duplicating
|
||||
// release discovery logic.
|
||||
|
||||
const fs = require("node:fs");
|
||||
const https = require("node:https");
|
||||
|
||||
const TAG_RE = /^et-bin-[A-Za-z0-9._-]+$/;
|
||||
|
||||
function log(msg) {
|
||||
console.log(`[resolve-et-bin-release] ${msg}`);
|
||||
}
|
||||
|
||||
function validateReleaseTag(tag) {
|
||||
const value = String(tag || "").trim();
|
||||
if (!TAG_RE.test(value)) {
|
||||
throw new Error(`invalid et binary release tag: ${tag}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseRepository(env) {
|
||||
const owner = env.ET_BIN_OWNER || (env.GITHUB_REPOSITORY || "").split("/")[0] || "binaricat";
|
||||
const repo = env.ET_BIN_REPO || "Netcatty-et-bin";
|
||||
return { owner, repo };
|
||||
}
|
||||
|
||||
function releaseTimestamp(release) {
|
||||
const raw = release.published_at || release.created_at || "";
|
||||
const value = Date.parse(raw);
|
||||
return Number.isNaN(value) ? 0 : value;
|
||||
}
|
||||
|
||||
function pickLatestEtBinRelease(releases) {
|
||||
return releases
|
||||
.map((release, index) => ({ release, index }))
|
||||
.filter(({ release }) => {
|
||||
return release
|
||||
&& TAG_RE.test(String(release.tag_name || ""))
|
||||
&& release.draft !== true
|
||||
&& release.prerelease !== true;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const diff = releaseTimestamp(b.release) - releaseTimestamp(a.release);
|
||||
return diff || a.index - b.index;
|
||||
})[0]?.release.tag_name;
|
||||
}
|
||||
|
||||
function parseNextLink(linkHeader) {
|
||||
if (!linkHeader) return null;
|
||||
for (const part of String(linkHeader).split(",")) {
|
||||
const match = part.match(/^\s*<([^>]+)>\s*;\s*(.+)\s*$/);
|
||||
if (!match) continue;
|
||||
const rel = match[2].split(";").some((attr) => attr.trim() === 'rel="next"');
|
||||
if (rel) return match[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function requestJsonWithHeaders(url, env, depth = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (depth > 5) {
|
||||
reject(new Error("too many redirects while looking up et binary releases"));
|
||||
return;
|
||||
}
|
||||
|
||||
const headers = {
|
||||
Accept: "application/vnd.github+json",
|
||||
"User-Agent": "netcatty-et-release-resolver",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
};
|
||||
const token = env.GITHUB_TOKEN || env.GH_TOKEN;
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
|
||||
https.get(url, { headers }, (res) => {
|
||||
const location = res.headers.location;
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && location) {
|
||||
res.resume();
|
||||
resolve(requestJsonWithHeaders(new URL(location, url).toString(), env, depth + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
const chunks = [];
|
||||
res.on("data", (chunk) => chunks.push(chunk));
|
||||
res.on("end", () => {
|
||||
const body = Buffer.concat(chunks).toString("utf8");
|
||||
if (res.statusCode !== 200) {
|
||||
reject(new Error(`GitHub API returned HTTP ${res.statusCode}: ${body.slice(0, 300)}`));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
resolve({ json: JSON.parse(body), headers: res.headers });
|
||||
} catch (err) {
|
||||
reject(new Error(`GitHub API returned invalid JSON: ${err.message}`));
|
||||
}
|
||||
});
|
||||
res.on("error", reject);
|
||||
}).on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadReleases(env, request = requestJsonWithHeaders) {
|
||||
if (env.ET_BIN_RELEASES_JSON) {
|
||||
const parsed = JSON.parse(env.ET_BIN_RELEASES_JSON);
|
||||
if (!Array.isArray(parsed)) {
|
||||
throw new Error("ET_BIN_RELEASES_JSON must be a JSON array");
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
const { owner, repo } = parseRepository(env);
|
||||
const apiBase = (env.GITHUB_API_URL || "https://api.github.com").replace(/\/+$/, "");
|
||||
let url = `${apiBase}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases?per_page=100`;
|
||||
log(`looking up latest et-bin-* release in ${owner}/${repo}`);
|
||||
const releases = [];
|
||||
const seen = new Set();
|
||||
while (url) {
|
||||
if (seen.has(url)) {
|
||||
throw new Error(`GitHub API pagination looped while looking up releases: ${url}`);
|
||||
}
|
||||
seen.add(url);
|
||||
|
||||
const { json, headers = {} } = await request(url, env);
|
||||
if (!Array.isArray(json)) {
|
||||
throw new Error("GitHub API releases response was not an array");
|
||||
}
|
||||
releases.push(...json);
|
||||
url = parseNextLink(headers.link);
|
||||
}
|
||||
return releases;
|
||||
}
|
||||
|
||||
function exportRelease(release, env) {
|
||||
if (env.GITHUB_ENV) {
|
||||
fs.appendFileSync(env.GITHUB_ENV, `ET_BIN_RELEASE=${release}\n`, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
async function main(env = process.env) {
|
||||
if (String(env.ET_BIN_RELEASE || "").trim()) {
|
||||
const release = validateReleaseTag(env.ET_BIN_RELEASE);
|
||||
exportRelease(release, env);
|
||||
log(`using ET_BIN_RELEASE=${release}`);
|
||||
return release;
|
||||
}
|
||||
|
||||
const releases = await loadReleases(env);
|
||||
const release = pickLatestEtBinRelease(releases);
|
||||
if (!release) {
|
||||
throw new Error(
|
||||
"could not find a non-draft et-bin-* release in the et binary repository. Publish build-et-binaries artifacts with release_tag (for example et-bin-6.2.10-1) before packaging.",
|
||||
);
|
||||
}
|
||||
|
||||
const validated = validateReleaseTag(release);
|
||||
exportRelease(validated, env);
|
||||
log(`resolved ET_BIN_RELEASE=${validated}`);
|
||||
return validated;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch((err) => {
|
||||
console.error(`[resolve-et-bin-release] FATAL ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadReleases,
|
||||
parseNextLink,
|
||||
validateReleaseTag,
|
||||
parseRepository,
|
||||
pickLatestEtBinRelease,
|
||||
main,
|
||||
};
|
||||
Reference in New Issue
Block a user