diff --git a/.github/scripts/sync-atomgit-release.mjs b/.github/scripts/sync-atomgit-release.mjs deleted file mode 100644 index 489b4e174f..0000000000 --- a/.github/scripts/sync-atomgit-release.mjs +++ /dev/null @@ -1,405 +0,0 @@ -#!/usr/bin/env node - -import { basename, join } from "node:path"; -import { readdirSync, readFileSync, statSync } from "node:fs"; -import { spawn } from "node:child_process"; -import { pathToFileURL } from "node:url"; - -const DEFAULT_API_BASE = "https://api.atomgit.com/api/v5"; -const DEFAULT_REPOSITORY = "t8y2/dbx"; -const DEFAULT_CONCURRENCY = 3; - -function parseArgs(argv) { - const args = { - apiBase: process.env.ATOMGIT_API_BASE || DEFAULT_API_BASE, - repository: process.env.ATOMGIT_REPOSITORY || DEFAULT_REPOSITORY, - token: process.env.ATOMGIT_TOKEN || "", - githubReleasePath: "", - assetsDir: "", - skipExistingAssets: true, - concurrency: Number.parseInt(process.env.ATOMGIT_UPLOAD_CONCURRENCY || `${DEFAULT_CONCURRENCY}`, 10), - }; - - for (let i = 0; i < argv.length; i++) { - const arg = argv[i]; - if (arg === "--github-release") { - args.githubReleasePath = argv[++i]; - } else if (arg === "--assets-dir") { - args.assetsDir = argv[++i]; - } else if (arg === "--repository") { - args.repository = argv[++i]; - } else if (arg === "--api-base") { - args.apiBase = argv[++i]; - } else if (arg === "--replace-assets") { - args.skipExistingAssets = false; - } else { - throw new Error(`Unknown argument: ${arg}`); - } - } - - if (!args.token) { - throw new Error("ATOMGIT_TOKEN is required."); - } - if (!args.githubReleasePath || !args.assetsDir) { - throw new Error("Usage: sync-atomgit-release.mjs --github-release --assets-dir "); - } - if (!args.repository.includes("/")) { - throw new Error(`Invalid AtomGit repository: ${args.repository}`); - } - if (!Number.isInteger(args.concurrency) || args.concurrency < 1) { - throw new Error("ATOMGIT_UPLOAD_CONCURRENCY must be a positive integer."); - } - return args; -} - -async function main() { - const args = parseArgs(process.argv.slice(2)); - const [owner, repo] = args.repository.split("/", 2); - const release = JSON.parse(readFileSync(args.githubReleasePath, "utf8")); - const tag = release.tagName || release.tag_name; - if (!tag) { - throw new Error("GitHub release JSON is missing tagName."); - } - - const client = new AtomGitClient({ apiBase: args.apiBase, token: args.token, owner, repo }); - const atomRelease = await ensureRelease(client, release); - // PATCH responses may omit attachments, so re-read before deciding what must be replaced. - const existingAssets = atomGitAssets((await client.getRelease(tag)) || atomRelease); - const assets = localAssets(args.assetsDir); - if (assets.length === 0) { - console.log("No release assets found to sync."); - return; - } - - const pendingAssets = assets.filter((assetPath) => { - const name = basename(assetPath); - if (args.skipExistingAssets && existingAssets.has(name)) { - console.log(`Skipping existing AtomGit asset: ${name}`); - return false; - } - return true; - }); - - if (!args.skipExistingAssets) { - for (const assetPath of pendingAssets) { - const name = basename(assetPath); - const existingAsset = existingAssets.get(name); - if (!existingAsset) continue; - if (existingAsset.id === null) { - throw new Error(`Cannot replace AtomGit asset without an attachment id: ${name}`); - } - console.log(`Deleting existing AtomGit asset: ${name}`); - await client.deleteAsset(tag, existingAsset.id); - } - } - - console.log(`Uploading ${pendingAssets.length} asset(s) with concurrency ${args.concurrency}.`); - await mapWithConcurrency(pendingAssets, args.concurrency, (assetPath) => uploadAsset(client, tag, assetPath)); - - const refreshedRelease = await client.getRelease(tag); - const uploadedAssets = atomGitAssets(refreshedRelease); - const missingAssets = pendingAssets.map(basename).filter((name) => !uploadedAssets.has(name)); - if (missingAssets.length > 0) { - throw new Error(`AtomGit did not register uploaded assets: ${missingAssets.join(", ")}`); - } -} - -async function ensureRelease(client, githubRelease) { - const tag = githubRelease.tagName || githubRelease.tag_name; - const payload = { - tag_name: tag, - name: githubRelease.name || tag, - body: githubRelease.body || "", - target_commitish: githubRelease.targetCommitish || githubRelease.target_commitish || "", - prerelease: Boolean(githubRelease.isPrerelease || githubRelease.prerelease), - }; - - const existing = await client.getRelease(tag); - if (existing) { - console.log(`Updating existing AtomGit release: ${tag}`); - return client.updateRelease(tag, payload); - } - - console.log(`Creating AtomGit release: ${tag}`); - return client.createRelease(payload); -} - -async function uploadAsset(client, tag, filePath) { - const name = basename(filePath); - const size = statSync(filePath).size; - console.log(`Uploading ${name} (${size} bytes)`); - const uploadTarget = await client.getUploadTarget(tag, name); - const contentType = contentTypeFor(name); - const uploadHeaders = uploadTarget.headers.length > 0 - ? uploadTarget.headers - : [ - ["Authorization", `Bearer ${client.token}`], - ["X-Api-Version", "2023-02-21"], - ]; - const contentTypeHeader = headerValue(uploadHeaders, "Content-Type") || contentType; - const curlHeaders = uploadHeaders - .filter(([key]) => key.toLowerCase() !== "content-type") - .flatMap(([key, value]) => ["--header", `${key}: ${value}`]); - - // AtomGit returns a per-file upload URL plus required upload headers. - // Try raw PUT first and fall back to multipart POST for API-compatible deployments. - const putResult = await runCurl([ - "--fail-with-body", - "--location", - "--silent", - "--show-error", - "--retry", - "3", - "--retry-delay", - "5", - "--request", - "PUT", - ...curlHeaders, - "--header", - `Content-Type: ${contentTypeHeader}`, - "--upload-file", - filePath, - uploadTarget.url, - ]); - if (uploadResultSucceeded(putResult)) { - return; - } - - console.warn(`PUT upload failed for ${name}: ${uploadFailureMessage(putResult)}; retrying as multipart POST.`); - const postResult = await runCurl([ - "--fail-with-body", - "--location", - "--silent", - "--show-error", - "--retry", - "3", - "--retry-delay", - "5", - "--request", - "POST", - ...curlHeaders, - "--form", - `file=@${filePath};filename=${name};type=${contentTypeHeader}`, - uploadTarget.url, - ]); - if (!uploadResultSucceeded(postResult)) { - throw new Error(`Failed to upload ${name} to AtomGit: ${uploadFailureMessage(postResult)}`); - } -} - -function runCurl(args) { - return new Promise((resolve, reject) => { - const child = spawn("curl", args, { stdio: ["ignore", "pipe", "pipe"] }); - let stdout = ""; - let stderr = ""; - child.stdout.setEncoding("utf8"); - child.stderr.setEncoding("utf8"); - child.stdout.on("data", (chunk) => { - stdout += chunk; - }); - child.stderr.on("data", (chunk) => { - stderr += chunk; - }); - child.once("error", reject); - child.once("close", (code) => resolve({ status: code ?? 1, stdout, stderr })); - }); -} - -export function uploadResultSucceeded(result) { - if (result.status !== 0) return false; - const responseBody = result.stdout.trim(); - if (!responseBody || responseBody.toLowerCase() === "success") return true; - try { - const response = JSON.parse(responseBody); - return response.success === true || response.status === "success"; - } catch { - return true; - } -} - -function uploadFailureMessage(result) { - return result.stderr.trim() || result.stdout.trim() || `curl exited with status ${result.status}`; -} - -async function mapWithConcurrency(items, concurrency, worker) { - let nextIndex = 0; - async function runWorker() { - while (nextIndex < items.length) { - const index = nextIndex++; - await worker(items[index]); - } - } - await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, runWorker)); -} - -function localAssets(dir) { - return readdirSync(dir) - .map((name) => join(dir, name)) - .filter((path) => statSync(path).isFile()) - .sort((a, b) => statSync(a).size - statSync(b).size || basename(a).localeCompare(basename(b))); -} - -export function atomGitAssets(release) { - const assets = new Map(); - const groups = [ - release?.assets, - release?.attach_files, - release?.attachFiles, - release?.files, - release?.attachments, - ]; - for (const group of groups) { - if (!Array.isArray(group)) { - continue; - } - for (const item of group) { - const name = item?.name || item?.file_name || item?.filename || item?.title; - if (name) { - assets.set(name, { - id: Number.isInteger(item?.id) ? item.id : null, - name, - }); - } - } - } - return assets; -} - -function contentTypeFor(name) { - if (name.endsWith(".json")) return "application/json"; - if (name.endsWith(".zip")) return "application/zip"; - if (name.endsWith(".gz") || name.endsWith(".tgz")) return "application/gzip"; - if (name.endsWith(".dmg")) return "application/x-apple-diskimage"; - if (name.endsWith(".exe") || name.endsWith(".msi")) return "application/octet-stream"; - if (name.endsWith(".deb")) return "application/vnd.debian.binary-package"; - if (name.endsWith(".rpm")) return "application/x-rpm"; - if (name.endsWith(".AppImage")) return "application/octet-stream"; - return "application/octet-stream"; -} - -function headerValue(headers, name) { - const normalizedName = name.toLowerCase(); - const entry = headers.find(([key]) => key.toLowerCase() === normalizedName); - return entry ? entry[1] : ""; -} - -export class AtomGitClient { - constructor({ apiBase, token, owner, repo }) { - this.apiBase = apiBase.replace(/\/+$/, ""); - this.token = token; - this.owner = owner; - this.repo = repo; - } - - async getRelease(tag) { - const res = await this.request("GET", `/repos/${this.owner}/${this.repo}/releases/${encodeURIComponent(tag)}`, null, { - allow404: true, - }); - if (res.status === 404) { - return null; - } - return parseJsonResponse(res); - } - - async createRelease(payload) { - const res = await this.request("POST", `/repos/${this.owner}/${this.repo}/releases`, payload, { - acceptStatuses: [409, 422], - }); - if (res.status === 409 || res.status === 422) { - return this.updateRelease(payload.tag_name, payload); - } - return parseJsonResponse(res); - } - - async updateRelease(tag, payload) { - const res = await this.request("PATCH", `/repos/${this.owner}/${this.repo}/releases/${encodeURIComponent(tag)}`, payload); - return parseJsonResponse(res); - } - - async getUploadTarget(tag, fileName) { - const params = new URLSearchParams({ file_name: fileName }); - const res = await this.request("GET", `/repos/${this.owner}/${this.repo}/releases/${encodeURIComponent(tag)}/upload_url?${params}`); - const body = await parseJsonResponse(res); - if (body && typeof body === "object" && !Array.isArray(body)) { - console.log(`AtomGit upload_url response keys: ${Object.keys(body).sort().join(", ")}`); - if (body.headers && typeof body.headers === "object") { - console.log(`AtomGit upload header keys: ${Object.keys(body.headers).sort().join(", ")}`); - } - } - const uploadUrl = typeof body === "string" ? body : body.url || body.upload_url || body.uploadUrl; - if (!uploadUrl) { - throw new Error(`AtomGit upload_url response did not include an upload URL: ${JSON.stringify(body)}`); - } - console.log(`AtomGit upload target: ${sanitizeUrlForLog(uploadUrl)}`); - return { - url: uploadUrl, - headers: normalizeUploadHeaders(typeof body === "string" ? null : body.headers), - }; - } - - async deleteAsset(tag, assetId) { - await this.request( - "DELETE", - `/repos/${this.owner}/${this.repo}/releases/${encodeURIComponent(tag)}/attach_files/${assetId}`, - ); - } - - async request(method, path, body = null, requestOptions = {}) { - const headers = { - Accept: "application/json", - Authorization: `Bearer ${this.token}`, - "X-Api-Version": "2023-02-21", - }; - const fetchOptions = { method, headers }; - if (body) { - const cleanBody = Object.fromEntries(Object.entries(body).filter(([, value]) => value !== "")); - headers["Content-Type"] = "application/json"; - fetchOptions.body = JSON.stringify(cleanBody); - } - - const res = await fetch(`${this.apiBase}${path}`, fetchOptions); - const acceptedStatus = requestOptions.acceptStatuses?.includes(res.status); - if (!res.ok && !(requestOptions.allow404 && res.status === 404) && !acceptedStatus) { - const text = await res.text(); - throw new Error(`AtomGit API ${method} ${path} failed with ${res.status}: ${text}`); - } - return res; - } -} - -async function parseJsonResponse(res) { - const text = await res.text(); - if (!text.trim()) { - return {}; - } - try { - return JSON.parse(text); - } catch { - return text; - } -} - -function normalizeUploadHeaders(headers) { - if (!headers || typeof headers !== "object" || Array.isArray(headers)) { - return []; - } - return Object.entries(headers) - .filter(([key, value]) => key && value !== null && value !== undefined && value !== "") - .map(([key, value]) => [key, String(value)]); -} - -function sanitizeUrlForLog(value) { - try { - const url = new URL(value); - return `${url.origin}${url.pathname}`; - } catch { - return ""; - } -} - -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - main().catch((error) => { - console.error(error); - process.exit(1); - }); -} diff --git a/.github/scripts/sync-atomgit-release.test.mjs b/.github/scripts/sync-atomgit-release.test.mjs deleted file mode 100644 index 048d3995b5..0000000000 --- a/.github/scripts/sync-atomgit-release.test.mjs +++ /dev/null @@ -1,52 +0,0 @@ -import assert from "node:assert/strict"; -import { createServer } from "node:http"; -import test from "node:test"; - -import { AtomGitClient, atomGitAssets, uploadResultSucceeded } from "./sync-atomgit-release.mjs"; - -test("uploadResultSucceeded rejects AtomGit callback errors returned with HTTP success", () => { - assert.equal(uploadResultSucceeded({ status: 0, stdout: "success", stderr: "" }), true); - assert.equal( - uploadResultSucceeded({ - status: 0, - stdout: JSON.stringify({ message: "Fail to read response body", code: "CallBack.0002" }), - stderr: "", - }), - false, - ); -}); - -test("atomGitAssets preserves attachment ids required for replacement", () => { - assert.deepEqual( - atomGitAssets({ assets: [{ id: 118266, name: "agent-registry.json" }] }), - new Map([["agent-registry.json", { id: 118266, name: "agent-registry.json" }]]), - ); -}); - -test("deleteAsset removes an existing release attachment by id", async (t) => { - const requests = []; - const server = createServer((request, response) => { - requests.push({ method: request.method, url: request.url }); - response.statusCode = 204; - response.end(); - }); - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); - t.after(() => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())))); - - const address = server.address(); - assert.ok(address && typeof address !== "string"); - const client = new AtomGitClient({ - apiBase: `http://127.0.0.1:${address.port}`, - token: "test-token", - owner: "t8y2", - repo: "dbx", - }); - - await client.deleteAsset("agents-latest", 118266); - assert.deepEqual(requests, [ - { - method: "DELETE", - url: "/repos/t8y2/dbx/releases/agents-latest/attach_files/118266", - }, - ]); -}); diff --git a/.github/workflows/agents-release.yml b/.github/workflows/agents-release.yml index 3bcd9cfb7f..e0ab737166 100644 --- a/.github/workflows/agents-release.yml +++ b/.github/workflows/agents-release.yml @@ -599,40 +599,3 @@ jobs: args+=(--overwrite-existing) fi node .github/scripts/sync-cnb-release.mjs "${args[@]}" - - sync-release-to-atomgit: - name: Sync agents releases to AtomGit - needs: release - if: ${{ always() && needs.release.result == 'success' }} - runs-on: ubuntu-latest - continue-on-error: true - strategy: - fail-fast: false - matrix: - tag: ["${{ github.ref_name }}", "agents-latest"] - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - name: Sync release assets to AtomGit - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ matrix.tag }} - GITHUB_REPOSITORY: ${{ github.repository }} - ATOMGIT_TOKEN: ${{ secrets.ATOMGIT_TOKEN }} - ATOMGIT_REPOSITORY: t8y2/dbx - run: | - set -euo pipefail - git fetch origin "refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}" --force - git remote add atomgit "https://t8y2:${ATOMGIT_TOKEN}@atomgit.com/t8y2/dbx.git" - git push atomgit "refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}" --force - mkdir -p "$RUNNER_TEMP/github-release" "$RUNNER_TEMP/release-assets" - gh release view "$TAG_NAME" --repo "$GITHUB_REPOSITORY" \ - --json tagName,name,body,targetCommitish,isPrerelease,isDraft,assets \ - > "$RUNNER_TEMP/github-release/release.json" - gh release download "$TAG_NAME" --repo "$GITHUB_REPOSITORY" \ - --dir "$RUNNER_TEMP/release-assets" --clobber - node .github/scripts/sync-atomgit-release.mjs \ - --github-release "$RUNNER_TEMP/github-release/release.json" \ - --assets-dir "$RUNNER_TEMP/release-assets" \ - ${{ matrix.tag == 'agents-latest' && '--replace-assets' || '' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a7159eeaf..b0a23ed1ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -423,55 +423,6 @@ jobs: --github-release "$RUNNER_TEMP/github-release/release.json" \ --assets-dir "$RUNNER_TEMP/release-assets" - sync-release-to-atomgit: - name: Sync release to AtomGit - needs: publish - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Ensure AtomGit tag exists - env: - TAG_NAME: ${{ github.ref_name }} - ATOMGIT_TOKEN: ${{ secrets.ATOMGIT_TOKEN }} - run: | - set -euo pipefail - git fetch origin "refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}" --force - git remote add atomgit "https://t8y2:${ATOMGIT_TOKEN}@atomgit.com/t8y2/dbx.git" - git push atomgit "refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}" --force - - - name: Download GitHub release assets - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ github.ref_name }} - GITHUB_REPOSITORY: ${{ github.repository }} - run: | - set -euo pipefail - mkdir -p "$RUNNER_TEMP/github-release" "$RUNNER_TEMP/release-assets" - gh release view "$TAG_NAME" \ - --repo "$GITHUB_REPOSITORY" \ - --json tagName,name,body,targetCommitish,isPrerelease,isDraft,assets \ - > "$RUNNER_TEMP/github-release/release.json" - gh release download "$TAG_NAME" \ - --repo "$GITHUB_REPOSITORY" \ - --dir "$RUNNER_TEMP/release-assets" \ - --clobber - - - name: Sync release assets to AtomGit - env: - ATOMGIT_TOKEN: ${{ secrets.ATOMGIT_TOKEN }} - ATOMGIT_REPOSITORY: "t8y2/dbx" - # AtomGit throttles concurrent large-asset uploads, which can expire their temporary upload URLs. - ATOMGIT_UPLOAD_CONCURRENCY: "1" - run: | - set -euo pipefail - node .github/scripts/sync-atomgit-release.mjs \ - --github-release "$RUNNER_TEMP/github-release/release.json" \ - --assets-dir "$RUNNER_TEMP/release-assets" - docker: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/sync-atomgit-release-assets.yml b/.github/workflows/sync-atomgit-release-assets.yml deleted file mode 100644 index 4c6e87c589..0000000000 --- a/.github/workflows/sync-atomgit-release-assets.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Sync AtomGit Release Assets - -on: - workflow_dispatch: - inputs: - tag: - description: "Release tag to sync" - required: true - type: string - -permissions: - contents: read - -env: - ATOMGIT_REPOSITORY: "t8y2/dbx" - -jobs: - sync-release-to-atomgit: - name: Sync release to AtomGit - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Ensure AtomGit tag exists - env: - TAG_NAME: ${{ inputs.tag }} - ATOMGIT_TOKEN: ${{ secrets.ATOMGIT_TOKEN }} - run: | - set -euo pipefail - git fetch origin "refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}" --force - git remote add atomgit "https://t8y2:${ATOMGIT_TOKEN}@atomgit.com/t8y2/dbx.git" - git push atomgit "refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}" --force - - - name: Download GitHub release assets - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ inputs.tag }} - GITHUB_REPOSITORY: ${{ github.repository }} - run: | - set -euo pipefail - mkdir -p "$RUNNER_TEMP/github-release" "$RUNNER_TEMP/release-assets" - gh release view "$TAG_NAME" \ - --repo "$GITHUB_REPOSITORY" \ - --json tagName,name,body,targetCommitish,isPrerelease,isDraft,assets \ - > "$RUNNER_TEMP/github-release/release.json" - gh release download "$TAG_NAME" \ - --repo "$GITHUB_REPOSITORY" \ - --dir "$RUNNER_TEMP/release-assets" \ - --clobber - - - name: Sync release assets to AtomGit - env: - ATOMGIT_TOKEN: ${{ secrets.ATOMGIT_TOKEN }} - ATOMGIT_REPOSITORY: ${{ env.ATOMGIT_REPOSITORY }} - run: | - set -euo pipefail - node .github/scripts/sync-atomgit-release.mjs \ - --github-release "$RUNNER_TEMP/github-release/release.json" \ - --assets-dir "$RUNNER_TEMP/release-assets" diff --git a/README.md b/README.md index 6792e61bee..d3262613e0 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,6 @@ - AtomGit Stars CNB

diff --git a/README.zh-CN.md b/README.zh-CN.md index 6b7f549c5e..4a4b0745f2 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -45,7 +45,6 @@ - AtomGit Stars CNB

diff --git a/apps/desktop/src/components/config/DriverStoreDialog.vue b/apps/desktop/src/components/config/DriverStoreDialog.vue index f35e1a3eca..56c731ed3b 100644 --- a/apps/desktop/src/components/config/DriverStoreDialog.vue +++ b/apps/desktop/src/components/config/DriverStoreDialog.vue @@ -332,7 +332,7 @@ async function forceRefresh() { } function setUpdateDownloadSource(value: unknown) { - if (value !== "official" && value !== "cnb" && value !== "atomgit") return; + if (value !== "official" && value !== "cnb") return; if (value === settingsStore.editorSettings.updateDownloadSource) return; settingsStore.updateEditorSettings({ updateDownloadSource: value }); void forceRefresh().catch(() => undefined); @@ -1161,7 +1161,6 @@ watch(driverStoreTab, (tab) => { {{ t("settings.updateDownloadSourceOfficial") }} {{ t("settings.updateDownloadSourceCnb") }} - {{ t("settings.updateDownloadSourceAtomgit") }} diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 357cf839f0..262c399eb4 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -1210,7 +1210,7 @@ function onLocaleChange(v: any) { } function onUpdateDownloadSourceChange(v: any) { - if (v === "official" || v === "cnb" || v === "atomgit") editUpdateDownloadSource.value = v; + if (v === "official" || v === "cnb") editUpdateDownloadSource.value = v; } function setSidebarObjectDisplay(value: "grouped" | "simple") { @@ -5007,7 +5007,6 @@ onUnmounted(cleanupPreviewEditor); {{ t("settings.updateDownloadSourceOfficial") }} {{ t("settings.updateDownloadSourceCnb") }} - {{ t("settings.updateDownloadSourceAtomgit") }} diff --git a/apps/desktop/src/composables/useAppUpdater.ts b/apps/desktop/src/composables/useAppUpdater.ts index 130a676bcb..385ff7819d 100644 --- a/apps/desktop/src/composables/useAppUpdater.ts +++ b/apps/desktop/src/composables/useAppUpdater.ts @@ -17,7 +17,8 @@ export function canDownloadAndInstallUpdate(info: api.UpdateInfo | null, isDeskt } export function normalizeUpdateDownloadSource(value: unknown): SettingsUpdateDownloadSource { - if (value === "atomgit") return "atomgit"; + // Old persisted AtomGit preferences should retain their mainland mirror behavior. + if (value === "atomgit") return "cnb"; return value === "cnb" ? "cnb" : "official"; } @@ -31,9 +32,6 @@ export function resolveUpdateReleaseUrl(info: api.UpdateInfo | null, source: unk if (normalizedSource === "cnb" && info?.latest_version) { return `https://cnb.cool/dbxio.com/dbx/-/releases/tag/${tagVersion(info.latest_version)}`; } - if (normalizedSource === "atomgit" && info?.latest_version) { - return `https://atomgit.com/t8y2/dbx/releases/${tagVersion(info.latest_version)}`; - } return info?.release_url || fallbackUrl; } diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 748e6ea5e8..e44c098441 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -3513,10 +3513,9 @@ export default { updateNotificationsEnabled: "Enable update reminders", updateNotificationsEnabledDescription: "When disabled, DBX will not automatically check app or driver updates or show update badges. Manual checks are still available.", updateDownloadSource: "Update download source", - updateDownloadSourceDescription: "Choose where app updates, database agents, drivers, and managed JREs are downloaded from. The official source is recommended; CNB or AtomGit can be faster on mainland China networks.", + updateDownloadSourceDescription: "Choose where app updates, database agents, drivers, and managed JREs are downloaded from. The official source is recommended; CNB can be faster on mainland China networks.", updateDownloadSourceOfficial: "Official source (recommended)", updateDownloadSourceCnb: "CNB", - updateDownloadSourceAtomgit: "AtomGit", debugLoggingEnabled: "Enable debug logs", debugLoggingEnabledDescription: "Record more detailed local user-side diagnostic logs while troubleshooting reports. Disabled by default.", duckDbWorkerProcessIsolation: "DuckDB process isolation mode (Experimental)", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index db92f054d0..5e33626512 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -3291,10 +3291,9 @@ export default withEnglishFallback({ updateNotificationsEnabled: "Activar recordatorios de actualización", updateNotificationsEnabledDescription: "Al desactivarlo, DBX no comprobará automáticamente actualizaciones de la app ni de los controladores, ni mostrará indicadores. Las comprobaciones manuales siguen disponibles.", updateDownloadSource: "Origen de descarga de actualizaciones", - updateDownloadSourceDescription: "Elige de dónde se descargan los instaladores de actualización. Se recomienda la fuente oficial; CNB o AtomGit pueden ser más rápidos en redes de China continental.", + updateDownloadSourceDescription: "Elige de dónde se descargan los instaladores de actualización. Se recomienda la fuente oficial; CNB puede ser más rápido en redes de China continental.", updateDownloadSourceOfficial: "Fuente oficial (recomendada)", updateDownloadSourceCnb: "CNB", - updateDownloadSourceAtomgit: "AtomGit", debugLoggingEnabled: "Activar logs de depuración", debugLoggingEnabledDescription: "Registra logs de diagnóstico locales del lado del usuario al investigar reportes. Desactivado por defecto.", duckDbWorkerProcessIsolation: "Modo de aislamiento de proceso de DuckDB (experimental)", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index dec4d76de6..d719dd0f72 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -3289,10 +3289,9 @@ export default withEnglishFallback({ updateNotificationsEnabled: "Abilita promemoria aggiornamenti", updateNotificationsEnabledDescription: "Se disattivato, DBX non controllerà automaticamente gli aggiornamenti dell'app o dei driver e non mostrerà badge. I controlli manuali restano disponibili.", updateDownloadSource: "Fonte download aggiornamenti", - updateDownloadSourceDescription: "Scegli da dove scaricare gli installer degli aggiornamenti. La fonte ufficiale è consigliata; CNB o AtomGit possono essere più veloci sulle reti della Cina continentale.", + updateDownloadSourceDescription: "Scegli da dove scaricare gli installer degli aggiornamenti. La fonte ufficiale è consigliata; CNB può essere più veloce sulle reti della Cina continentale.", updateDownloadSourceOfficial: "Fonte ufficiale (consigliata)", updateDownloadSourceCnb: "CNB", - updateDownloadSourceAtomgit: "AtomGit", debugLoggingEnabled: "Abilita log di debug", debugLoggingEnabledDescription: "Registra log diagnostici locali lato utente durante l'analisi delle segnalazioni. Disattivato per impostazione predefinita.", duckDbWorkerProcessIsolation: "Modalità di isolamento del processo DuckDB (sperimentale)", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index fd8b7a45fe..e75175851b 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -3615,10 +3615,9 @@ export default withEnglishFallback({ mcpRefresh: "再確認", mcpGuide: "MCPガイド", updateDownloadSource: "アップデートのダウンロード元", - updateDownloadSourceDescription: "アプリ内アップデートのインストーラーのダウンロード元を選択します。公式ソース推奨。中国本土ではCNBまたはAtomGitの方が高速な場合があります。", + updateDownloadSourceDescription: "アプリ内アップデートのインストーラーのダウンロード元を選択します。公式ソース推奨。中国本土ではCNBの方が高速な場合があります。", updateDownloadSourceOfficial: "公式ソース(推奨)", updateDownloadSourceCnb: "CNB", - updateDownloadSourceAtomgit: "AtomGit", infiniteScroll: "無限スクロール読み込み", autoCalculateTotalRows: "総行数を自動計算", autoCalculateTotalRowsDescription: "クエリごとに COUNT(*) を自動実行し、一致する総行数を表示します。大きなクエリを高速に保つため既定はオフです。結果フッターから必要に応じて計算できます。", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 61e843c944..dc17668165 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -3291,10 +3291,9 @@ export default withEnglishFallback({ updateNotificationsEnabled: "Ativar lembretes de atualização", updateNotificationsEnabledDescription: "Quando desativado, o DBX não verificará automaticamente atualizações do app ou dos drivers nem mostrará indicadores. Verificações manuais continuam disponíveis.", updateDownloadSource: "Fonte de download de atualização", - updateDownloadSourceDescription: "Escolha de onde os instaladores de atualização no aplicativo são baixados. A fonte oficial é recomendada; CNB ou AtomGit podem ser mais rápidos em redes da China continental.", + updateDownloadSourceDescription: "Escolha de onde os instaladores de atualização no aplicativo são baixados. A fonte oficial é recomendada; CNB pode ser mais rápido em redes da China continental.", updateDownloadSourceOfficial: "Fonte oficial (recomendada)", updateDownloadSourceCnb: "CNB", - updateDownloadSourceAtomgit: "AtomGit", debugLoggingEnabled: "Ativar logs de depuração", debugLoggingEnabledDescription: "Registra logs locais de diagnóstico do lado do usuário ao investigar relatos. Desativado por padrão.", duckDbWorkerProcessIsolation: "Modo de isolamento de processo do DuckDB (experimental)", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index db0f7a3bba..eb050f6690 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -3483,10 +3483,9 @@ export default withEnglishFallback({ updateNotificationsEnabled: "启用更新提醒", updateNotificationsEnabledDescription: "关闭后,DBX 不会自动检查应用和驱动更新,也不会显示更新红点;仍可手动检查更新。", updateDownloadSource: "更新下载源", - updateDownloadSourceDescription: "选择应用更新、数据库 Agent、驱动和托管 JRE 的下载来源。官方源为推荐选项,CNB 或 AtomGit 适合国内网络环境。", + updateDownloadSourceDescription: "选择应用更新、数据库 Agent、驱动和托管 JRE 的下载来源。官方源为推荐选项,CNB 适合国内网络环境。", updateDownloadSourceOfficial: "官方源(推荐)", updateDownloadSourceCnb: "CNB", - updateDownloadSourceAtomgit: "AtomGit", debugLoggingEnabled: "启用调试日志", debugLoggingEnabledDescription: "开启后在本机记录更详细的用户侧诊断日志,反馈问题时可临时开启;默认关闭。", duckDbWorkerProcessIsolation: "DuckDB 进程隔离模式(实验性)", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 1f53c6be7f..790c032552 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -3473,10 +3473,9 @@ export default withEnglishFallback({ mcpRefresh: "重新檢查", mcpGuide: "MCP 指南", updateDownloadSource: "更新下載來源", - updateDownloadSourceDescription: "選擇應用程式內更新安裝程式的下載來源。建議使用官方來源;CNB 或 AtomGit 在中國大陸網路環境下可能更快。", + updateDownloadSourceDescription: "選擇應用程式內更新安裝程式的下載來源。建議使用官方來源;CNB 在中國大陸網路環境下可能更快。", updateDownloadSourceOfficial: "官方來源(建議)", updateDownloadSourceCnb: "CNB", - updateDownloadSourceAtomgit: "AtomGit", exportSection: "匯出", exportBatchSize: "匯出批次大小", dateTimeSection: "日期與時間", diff --git a/apps/desktop/src/lib/backend/tauri.ts b/apps/desktop/src/lib/backend/tauri.ts index 83018f7706..614089e0fb 100644 --- a/apps/desktop/src/lib/backend/tauri.ts +++ b/apps/desktop/src/lib/backend/tauri.ts @@ -1406,7 +1406,7 @@ export interface UpdateInfo { release_notes: string; } -export type UpdateDownloadSource = "official" | "cnb" | "atomgit"; +export type UpdateDownloadSource = "official" | "cnb"; export interface UpdateDownloadProgress { downloaded: number; diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index d2f9567a8a..68ed00af72 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -69,9 +69,9 @@ describe("normalizeEditorSettings", () => { expect(normalizeEditorSettings({ restoreOpenTabsOnLaunch: true } as any).openTabsRestoreMode).toBe("all"); }); - it("preserves mirror update download sources and rejects invalid values", () => { + it("preserves CNB, migrates AtomGit to CNB, and rejects invalid values", () => { expect(normalizeEditorSettings({ updateDownloadSource: "cnb" }).updateDownloadSource).toBe("cnb"); - expect(normalizeEditorSettings({ updateDownloadSource: "atomgit" }).updateDownloadSource).toBe("atomgit"); + expect(normalizeEditorSettings({ updateDownloadSource: "atomgit" as any }).updateDownloadSource).toBe("cnb"); expect(normalizeEditorSettings({ updateDownloadSource: "mirror" as any }).updateDownloadSource).toBe("official"); }); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index b78e710bb3..8bfa876336 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -38,7 +38,7 @@ export type DesktopIconTheme = "default" | "black"; export type InterfaceLayout = "separated" | "classic"; -export type UpdateDownloadSource = "official" | "cnb" | "atomgit"; +export type UpdateDownloadSource = "official" | "cnb"; export type SqlSemanticDiagnosticsMode = "auto" | "enabled" | "disabled"; export type OpenTabsRestoreMode = "all" | "pinned" | "none"; @@ -626,7 +626,8 @@ function normalizeTableFontSize(value: unknown): number { } function normalizeUpdateDownloadSource(value: unknown): UpdateDownloadSource { - if (value === "atomgit") return "atomgit"; + // Preserve the intent of users who previously selected the mainland-China mirror. + if (value === "atomgit") return "cnb"; return value === "cnb" ? "cnb" : DEFAULT_EDITOR_SETTINGS.updateDownloadSource; } @@ -887,7 +888,12 @@ export const useSettingsStore = defineStore("settings", () => { if (isEditorSettingsLoaded.value) return; const saved = await api.loadEditorSettings().catch(() => null); if (saved && typeof saved === "object" && !Array.isArray(saved)) { - editorSettings.value = normalizeEditorSettings(saved as Partial); + const normalized = normalizeEditorSettings(saved as Partial); + editorSettings.value = normalized; + if ((saved as { updateDownloadSource?: unknown }).updateDownloadSource === "atomgit") { + // Persist the channel migration so the removed source cannot reappear in older settings data. + await api.saveEditorSettings(normalized).catch(() => {}); + } isEditorSettingsLoaded.value = true; return; } diff --git a/crates/dbx-core/src/lib.rs b/crates/dbx-core/src/lib.rs index e84039bca6..15da4a7c97 100644 --- a/crates/dbx-core/src/lib.rs +++ b/crates/dbx-core/src/lib.rs @@ -74,7 +74,6 @@ pub mod xlsx_export; pub const R2_CDN_BASE: &str = "https://dl.dbxio.com/"; pub const GITHUB_RELEASE_DOWNLOAD_PREFIX: &str = "https://github.com/t8y2/dbx/releases/download/"; pub const CNB_RELEASE_DOWNLOAD_PREFIX: &str = "https://cnb.cool/dbxio.com/dbx/-/releases/download/"; -pub const ATOMGIT_RELEASE_API_PREFIX: &str = "https://api.atomgit.com/api/v5/repos/t8y2/dbx/releases/"; #[derive(Clone, Copy, Debug, Default, serde::Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "lowercase")] @@ -82,7 +81,6 @@ pub enum DownloadSource { #[default] Official, Cnb, - Atomgit, } impl DownloadSource { @@ -94,11 +92,6 @@ impl DownloadSource { r2_path, rewrite_github_release_url(github_url, CNB_RELEASE_DOWNLOAD_PREFIX)?, )), - Self::Atomgit => Ok(mirror_download_candidate_urls( - github_url, - r2_path, - rewrite_github_release_url_for_atomgit(github_url)?, - )), } } } @@ -122,20 +115,6 @@ fn rewrite_github_release_url(url: &str, target_prefix: &str) -> Result Result { - if url.starts_with(ATOMGIT_RELEASE_API_PREFIX) { - return Ok(url.to_string()); - } - let path = url - .strip_prefix(GITHUB_RELEASE_DOWNLOAD_PREFIX) - .ok_or_else(|| format!("Unsupported DBX release download URL: {url}"))?; - let (tag, file_name) = path - .split_once('/') - .filter(|(_, file_name)| !file_name.contains('/')) - .ok_or_else(|| format!("Unsupported DBX release asset path: {path}"))?; - Ok(format!("{ATOMGIT_RELEASE_API_PREFIX}{tag}/attach_files/{file_name}/download")) -} - pub fn download_candidate_urls(github_url: &str, r2_path: &str) -> Vec { vec![format!("{R2_CDN_BASE}{r2_path}"), github_url.to_string()] } @@ -206,24 +185,5 @@ mod tests { "https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/agent-registry.json", ] ); - assert_eq!( - DownloadSource::Atomgit.download_candidate_urls(github_url, "agents/agent-registry.json").unwrap(), - vec![ - "https://dl.dbxio.com/agents/agent-registry.json", - "https://api.atomgit.com/api/v5/repos/t8y2/dbx/releases/agents-latest/attach_files/agent-registry.json/download", - ] - ); - } - - #[test] - fn atomgit_versioned_assets_use_attachment_download_api_first() { - let github_url = "https://github.com/t8y2/dbx/releases/download/agents-v0.2.55/dbx-agent-h2.jar"; - assert_eq!( - DownloadSource::Atomgit.download_candidate_urls(github_url, "agents/dbx-agent-h2.jar").unwrap(), - vec![ - "https://api.atomgit.com/api/v5/repos/t8y2/dbx/releases/agents-v0.2.55/attach_files/dbx-agent-h2.jar/download", - "https://dl.dbxio.com/agents/dbx-agent-h2.jar", - ] - ); } } diff --git a/docs/app/[lang]/drivers/DriversClient.tsx b/docs/app/[lang]/drivers/DriversClient.tsx index 07545968ec..5612da53b7 100644 --- a/docs/app/[lang]/drivers/DriversClient.tsx +++ b/docs/app/[lang]/drivers/DriversClient.tsx @@ -37,12 +37,11 @@ const i18n = { showing: "Showing", of: "of", clearSearch: "Clear search", - mirrorHint: "CNB and AtomGit mirrors are available for GitHub release assets on mainland China networks.", + mirrorHint: "The CNB mirror is available for GitHub release assets on mainland China networks.", downloadSources: "Download source", sources: { github: "GitHub", cnb: "CNB", - atomgit: "AtomGit", official: "Official", }, downloadHint: "For air-gapped environments: download the bundle for your platform on an internet-connected machine, then transfer it to the offline machine and import it in DBX from Settings > Driver Manager. Use the driver and JRE tabs only when you need individual artifacts.", @@ -77,12 +76,11 @@ const i18n = { showing: "显示", of: "/", clearSearch: "清空搜索", - mirrorHint: "中国大陆网络可选择 CNB 或 AtomGit 镜像下载,GitHub Release 资源保持同步。", + mirrorHint: "中国大陆网络可选择 CNB 镜像下载,GitHub Release 资源保持同步。", downloadSources: "下载来源", sources: { github: "GitHub", cnb: "CNB", - atomgit: "AtomGit", official: "官方下载", }, downloadHint: "内网环境使用说明:在有网的电脑上下载对应平台的整包,然后传输到内网机器,在 DBX 的“设置 > 驱动管理”中导入。只有需要单个产物时再使用驱动和 JRE 标签页。", @@ -120,7 +118,7 @@ function DownloadLinks({ url, t }: { url: string; t: DriverTranslations }) { key={link.source} href={link.url} download - className={`landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border px-2 text-xs font-medium transition-colors hover:border-landing-blue ${link.source === "cnb" || link.source === "atomgit" ? "border-landing-blue/45 bg-landing-blue/10 text-landing-sky" : "border-landing-line"}`} + className={`landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border px-2 text-xs font-medium transition-colors hover:border-landing-blue ${link.source === "cnb" ? "border-landing-blue/45 bg-landing-blue/10 text-landing-sky" : "border-landing-line"}`} aria-label={`${t.download}: ${t.sources[link.source as DownloadSource]}`} > diff --git a/docs/lib/agentRegistry.test.ts b/docs/lib/agentRegistry.test.ts index 9370d41ff4..7231f2655f 100644 --- a/docs/lib/agentRegistry.test.ts +++ b/docs/lib/agentRegistry.test.ts @@ -16,11 +16,10 @@ test("offline download catalog includes the JDBC plugin ZIP", () => { }); }); -test("release assets expose GitHub, CNB, and AtomGit download links", () => { +test("release assets expose GitHub and CNB download links", () => { assert.deepEqual(downloadLinksFor("https://github.com/t8y2/dbx/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip"), [ { source: "github", url: "https://github.com/t8y2/dbx/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip" }, { source: "cnb", url: "https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip" }, - { source: "atomgit", url: "https://atomgit.com/t8y2/dbx/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip" }, ]); }); @@ -30,7 +29,7 @@ test("non-release assets retain their official download link", () => { ]); }); -test("catalog falls back from GitHub to CNB and then AtomGit", async () => { +test("catalog falls back from GitHub to CNB", async () => { const requestedUrls: string[] = []; vi.stubGlobal( "fetch", @@ -38,14 +37,17 @@ test("catalog falls back from GitHub to CNB and then AtomGit", async () => { const url = String(input); requestedUrls.push(url); if (url.includes("api.github.com")) return new Response("rate limited", { status: 403 }); - if (url.includes("cnb.cool")) throw new TypeError("CORS blocked"); return Response.json({ - tag_name: "agents-latest", - assets: [ - { name: "dbx-agent-access.jar", type: "attach" }, - { name: "dbx-jre-21-macos-aarch64.tar.gz", type: "attach" }, - { name: "dbx-agents-offline-macos-aarch64.zip", type: "attach" }, - ], + drivers: { + access: { jar: { url: "https://dl.dbxio.com/agents/dbx-agent-access.jar", size: 1 } }, + }, + jres: { + "21": { + platforms: { + "macos-aarch64": { url: "https://dl.dbxio.com/jres/dbx-jre-21-macos-aarch64.tar.gz", size: 1 }, + }, + }, + }, }); }), ); @@ -55,7 +57,6 @@ test("catalog falls back from GitHub to CNB and then AtomGit", async () => { assert.deepEqual(requestedUrls, [ "https://api.github.com/repos/t8y2/dbx/releases/tags/agents-latest", "https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/agent-registry.json", - "https://api.atomgit.com/api/v5/repos/t8y2/dbx/releases/agents-latest", ]); assert.equal(catalog?.drivers[0]?.key, "access"); assert.equal(catalog?.jres[0]?.platformKey, "macos-aarch64"); diff --git a/docs/lib/agentRegistry.ts b/docs/lib/agentRegistry.ts index 78fcf01bde..32101aff8e 100644 --- a/docs/lib/agentRegistry.ts +++ b/docs/lib/agentRegistry.ts @@ -5,7 +5,7 @@ export interface ArtifactInfo { size: number; } -export type DownloadSource = "github" | "cnb" | "atomgit" | "official"; +export type DownloadSource = "github" | "cnb" | "official"; export interface DownloadLink { source: DownloadSource; @@ -37,11 +37,6 @@ interface AgentRegistry { drivers?: Record; } -interface AtomGitRelease { - tag_name?: string; - assets?: Array<{ name?: string; type?: string }>; -} - export interface OfflineBundleEntry { platformKey: string; platformLabel: string; @@ -93,11 +88,9 @@ export interface AgentDownloadCatalog { const AGENTS_LATEST_RELEASE_API_URL = "https://api.github.com/repos/t8y2/dbx/releases/tags/agents-latest"; const CNB_AGENT_REGISTRY_URL = "https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/agent-registry.json"; -const ATOMGIT_LATEST_RELEASE_API_URL = "https://api.atomgit.com/api/v5/repos/t8y2/dbx/releases/agents-latest"; const JDBC_PLUGIN_DOWNLOAD_URL = "https://dl.dbxio.com/releases/latest/dbx-jdbc-plugin-latest.zip"; const GITHUB_RELEASE_DOWNLOAD_PREFIX = "https://github.com/t8y2/dbx/releases/download/"; const CNB_RELEASE_DOWNLOAD_PREFIX = "https://cnb.cool/dbxio.com/dbx/-/releases/download/"; -const ATOMGIT_RELEASE_DOWNLOAD_PREFIX = "https://atomgit.com/t8y2/dbx/releases/download/"; const MIN_APP_VERSION = "0.6.0"; const driverVersionMap = driverVersions as Record; const nativeDriverKeys = new Set(["oracle", "xugu"]); @@ -213,13 +206,6 @@ function registryReleaseAssets(registry: AgentRegistry): GitHubReleaseAsset[] { return Array.from(assets.values()); } -function atomGitReleaseAssets(release: AtomGitRelease): GitHubReleaseAsset[] { - const tag = release.tag_name || "agents-latest"; - return (release.assets ?? []) - .filter((asset): asset is { name: string; type?: string } => Boolean(asset.name) && asset.type !== "source") - .map((asset) => githubReleaseAsset(asset.name, 0, tag)); -} - function hasDownloadAssets(catalog: AgentDownloadCatalog): boolean { return catalog.bundles.length > 0 || catalog.drivers.length > 0 || catalog.jres.length > 0 || catalog.nativeAgents.length > 0; } @@ -237,7 +223,6 @@ export function downloadLinksFor(url: string): DownloadLink[] { return [ { source: "github", url }, { source: "cnb", url: `${CNB_RELEASE_DOWNLOAD_PREFIX}${releasePath}` }, - { source: "atomgit", url: `${ATOMGIT_RELEASE_DOWNLOAD_PREFIX}${releasePath}` }, ]; } @@ -249,7 +234,6 @@ export async function fetchAgentDownloadCatalog(): Promise Promise> = [ async () => (await fetchJson(AGENTS_LATEST_RELEASE_API_URL, { Accept: "application/vnd.github+json" })).assets ?? [], async () => registryReleaseAssets(await fetchJson(CNB_AGENT_REGISTRY_URL)), - async () => atomGitReleaseAssets(await fetchJson(ATOMGIT_LATEST_RELEASE_API_URL, { Accept: "application/json" })), ]; for (const loadAssets of loaders) { diff --git a/packages/app-tests/appUpdater.test.ts b/packages/app-tests/appUpdater.test.ts index 74f25dea3a..767fb79ef8 100644 --- a/packages/app-tests/appUpdater.test.ts +++ b/packages/app-tests/appUpdater.test.ts @@ -34,7 +34,7 @@ test("blocks in-app update installation outside desktop runtime or without an up test("normalizes update download source", () => { assert.equal(normalizeUpdateDownloadSource("official"), "official"); assert.equal(normalizeUpdateDownloadSource("cnb"), "cnb"); - assert.equal(normalizeUpdateDownloadSource("atomgit"), "atomgit"); + assert.equal(normalizeUpdateDownloadSource("atomgit"), "cnb"); assert.equal(normalizeUpdateDownloadSource("unknown"), "official"); }); @@ -46,7 +46,6 @@ test("normalizes release tag versions", () => { test("resolves release page URL from update download source", () => { const fallbackUrl = "https://github.com/t8y2/dbx/releases/latest"; assert.equal(resolveUpdateReleaseUrl(updateInfo({ latest_version: "0.5.39" }), "cnb", fallbackUrl), "https://cnb.cool/dbxio.com/dbx/-/releases/tag/v0.5.39"); - assert.equal(resolveUpdateReleaseUrl(updateInfo({ latest_version: "0.5.44" }), "atomgit", fallbackUrl), "https://atomgit.com/t8y2/dbx/releases/v0.5.44"); assert.equal(resolveUpdateReleaseUrl(updateInfo({ release_url: "https://github.com/t8y2/dbx/releases/tag/v0.5.39" }), "official", fallbackUrl), "https://github.com/t8y2/dbx/releases/tag/v0.5.39"); assert.equal(resolveUpdateReleaseUrl(null, "cnb", fallbackUrl), fallbackUrl); }); diff --git a/src-tauri/src/commands/update.rs b/src-tauri/src/commands/update.rs index 223394daa0..36308b6889 100644 --- a/src-tauri/src/commands/update.rs +++ b/src-tauri/src/commands/update.rs @@ -15,7 +15,6 @@ const OFFICIAL_UPDATE_ENDPOINTS: [&str; 2] = [ const R2_LATEST_RELEASE_DOWNLOAD_PREFIX: &str = "https://dl.dbxio.com/releases/latest/"; const CNB_RELEASE_DOWNLOAD_PREFIX: &str = "https://cnb.cool/dbxio.com/dbx/-/releases/download/"; const GITHUB_RELEASE_DOWNLOAD_PREFIX: &str = "https://github.com/t8y2/dbx/releases/download/"; -const ATOMGIT_RELEASE_DOWNLOAD_PREFIX: &str = "https://atomgit.com/t8y2/dbx/releases/download/"; const UPDATE_DOWNLOAD_PROGRESS_EVENT: &str = "update-download-progress"; #[derive(Debug, Deserialize)] @@ -23,7 +22,6 @@ const UPDATE_DOWNLOAD_PROGRESS_EVENT: &str = "update-download-progress"; pub enum UpdateDownloadSource { Official, Cnb, - Atomgit, } #[derive(Clone, Debug, Serialize)] @@ -37,7 +35,6 @@ impl UpdateDownloadSource { match self { Self::Official => "official", Self::Cnb => "cnb", - Self::Atomgit => "atomgit", } } @@ -52,14 +49,6 @@ impl UpdateDownloadSource { OFFICIAL_UPDATE_ENDPOINTS[0].to_string(), ]) } - Self::Atomgit => { - let version = - latest_version.ok_or_else(|| "Latest version is required for AtomGit updates.".to_string())?; - Ok(vec![ - format!("{ATOMGIT_RELEASE_DOWNLOAD_PREFIX}{}/latest.json", tag_version(version)), - OFFICIAL_UPDATE_ENDPOINTS[0].to_string(), - ]) - } } } @@ -81,7 +70,6 @@ impl UpdateDownloadSource { fn mirror_download_prefix(&self) -> Option<&'static str> { match self { Self::Cnb => Some(CNB_RELEASE_DOWNLOAD_PREFIX), - Self::Atomgit => Some(ATOMGIT_RELEASE_DOWNLOAD_PREFIX), Self::Official => None, } } @@ -207,8 +195,8 @@ async fn update_url_is_available(url: &str) -> bool { #[cfg(test)] mod tests { use super::{ - tag_version, UpdateDownloadSource, ATOMGIT_RELEASE_DOWNLOAD_PREFIX, CNB_RELEASE_DOWNLOAD_PREFIX, - OFFICIAL_UPDATE_ENDPOINTS, R2_LATEST_RELEASE_DOWNLOAD_PREFIX, + tag_version, UpdateDownloadSource, CNB_RELEASE_DOWNLOAD_PREFIX, OFFICIAL_UPDATE_ENDPOINTS, + R2_LATEST_RELEASE_DOWNLOAD_PREFIX, }; #[test] @@ -232,18 +220,6 @@ mod tests { ); } - #[test] - fn builds_atomgit_update_endpoint_for_tag() { - let endpoints = UpdateDownloadSource::Atomgit.endpoints(Some("0.5.44")).unwrap(); - assert_eq!( - endpoints, - vec![ - format!("{ATOMGIT_RELEASE_DOWNLOAD_PREFIX}v0.5.44/latest.json"), - OFFICIAL_UPDATE_ENDPOINTS[0].to_string(), - ] - ); - } - #[test] fn rewrites_github_asset_url_to_cnb() { let download_url = UpdateDownloadSource::Cnb @@ -261,27 +237,10 @@ mod tests { assert_eq!(download_url, None); } - #[test] - fn rewrites_github_asset_url_to_atomgit() { - let download_url = UpdateDownloadSource::Atomgit - .rewrite_download_url("https://github.com/t8y2/dbx/releases/download/v0.5.44/DBX_0.5.44_x64.dmg") - .unwrap() - .unwrap(); - assert_eq!(download_url, "https://atomgit.com/t8y2/dbx/releases/download/v0.5.44/DBX_0.5.44_x64.dmg"); - } - - #[test] - fn accepts_existing_atomgit_asset_url() { - let download_url = UpdateDownloadSource::Atomgit - .rewrite_download_url("https://atomgit.com/t8y2/dbx/releases/download/v0.5.44/DBX_0.5.44_x64.dmg") - .unwrap(); - assert_eq!(download_url, None); - } - #[test] fn builds_r2_fallback_for_mirror_asset() { - let fallback = UpdateDownloadSource::Atomgit - .r2_fallback_url("https://atomgit.com/t8y2/dbx/releases/download/v0.5.44/DBX_0.5.44_x64.dmg") + let fallback = UpdateDownloadSource::Cnb + .r2_fallback_url("https://cnb.cool/dbxio.com/dbx/-/releases/download/v0.5.44/DBX_0.5.44_x64.dmg") .unwrap(); assert_eq!(fallback, Some(format!("{R2_LATEST_RELEASE_DOWNLOAD_PREFIX}DBX_0.5.44_x64.dmg"))); }