mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-10-18 05:57:18 +02:00
releases shell, synced 2026-10-03
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
name: Promotion gate (update feeds agree)
|
||||
|
||||
# The "don't let a half-baked release become latest" gate. Releases should be
|
||||
# cut as DRAFT first (publish.sh drafts experimental builds; do the same for
|
||||
# stable and only un-draft after dogfooding — see docs/RELEASE_CHECKLIST.md).
|
||||
# When a release is published / un-drafted, this verifies BOTH auto-updater
|
||||
# feeds exist, agree on version (with each other and the tag), and that every
|
||||
# referenced asset actually resolves (HEAD 200). If a platform's feed is
|
||||
# missing or versions mismatch, this goes red so the bad release is caught
|
||||
# before users auto-update into it.
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published, released, prereleased]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to verify (e.g. v1.2.3)'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
verify-feeds:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.18.1'
|
||||
- name: Resolve tag
|
||||
id: tag
|
||||
shell: bash
|
||||
run: |
|
||||
tag="${{ github.event.release.tag_name }}"
|
||||
[ -z "$tag" ] && tag="${{ github.event.inputs.tag }}"
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
echo "ver=${tag#v}" >> "$GITHUB_OUTPUT"
|
||||
- name: Download release feeds
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p feeds
|
||||
gh release download "${{ steps.tag.outputs.tag }}" --repo "${{ github.repository }}" \
|
||||
-p 'latest*.yml' -D feeds || true
|
||||
ls -la feeds
|
||||
- name: Verify both feeds exist, agree, and resolve
|
||||
shell: bash
|
||||
run: |
|
||||
node scripts/release/verify-release.js \
|
||||
--dir feeds \
|
||||
--expect-version "${{ steps.tag.outputs.ver }}" \
|
||||
--base-url "https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}"
|
||||
@@ -0,0 +1,45 @@
|
||||
OpenSwarm Proprietary Software Licence
|
||||
|
||||
Copyright (c) 2026 Open Swarm. All rights reserved.
|
||||
|
||||
This software and its source code are proprietary and confidential. They are made
|
||||
available only to people expressly authorised by Open Swarm, and only for the purpose
|
||||
Open Swarm authorised.
|
||||
|
||||
You may not copy, modify, merge, publish, distribute, sublicense, sell, or create
|
||||
derivative works from this software or its source code, in whole or in part, and you
|
||||
may not disclose it to anyone outside your authorisation, without prior written
|
||||
permission from Open Swarm.
|
||||
|
||||
You may not reverse engineer, decompile, disassemble, or decrypt any part of this
|
||||
software, or otherwise attempt to derive its source code, structure, or algorithms from
|
||||
the compiled application, and you may not remove, bypass, or defeat any measure that
|
||||
protects it, except to the extent applicable law expressly permits this despite this
|
||||
restriction.
|
||||
|
||||
You may not use this software, its source code, or any part or derivative of either,
|
||||
for any commercial purpose, including to build, offer, or train any product or service,
|
||||
without prior written permission from Open Swarm.
|
||||
|
||||
These restrictions apply to the source code and to the compiled OpenSwarm application
|
||||
alike.
|
||||
|
||||
Use of the compiled OpenSwarm application is also governed by the terms of service and
|
||||
any end user licence agreement published at https://openswarm.com. Those terms add to
|
||||
this file; they do not relax the restrictions above unless they say so expressly.
|
||||
|
||||
Third-party components distributed with this software remain under their own licences,
|
||||
which are unaffected by this file.
|
||||
|
||||
Earlier public releases: versions of this software published before 8 September 2026
|
||||
were released under the GNU Affero General Public License version 3. Those releases,
|
||||
and the source published with them, remain available under that licence, and nothing in
|
||||
this file withdraws, narrows, or otherwise affects the rights any person already
|
||||
received under it. This licence governs this version and later ones only.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,19 @@
|
||||
# OpenSwarm
|
||||
|
||||
OpenSwarm has moved in a new direction, and its development no longer happens here. This repository now holds only the signed builds: every installed copy checks here for updates, and the download links on [openswarm.com](https://openswarm.com) point here.
|
||||
|
||||
## Download
|
||||
|
||||
- macOS (Apple silicon): https://github.com/openswarm-ai/openswarm/releases/latest/download/OpenSwarm-arm64.dmg
|
||||
- macOS (Intel): https://github.com/openswarm-ai/openswarm/releases/latest/download/OpenSwarm-x64.dmg
|
||||
- Windows: https://github.com/openswarm-ai/openswarm/releases/latest/download/OpenSwarm-Setup-x64.exe
|
||||
|
||||
The release marked **Latest** is the stable build. Releases marked **Pre-release** are the experimental channel, which the app offers when you turn on experimental updates in Settings.
|
||||
|
||||
## Bugs and requests
|
||||
|
||||
Open an issue here: https://github.com/openswarm-ai/openswarm/issues/new
|
||||
|
||||
## Licence
|
||||
|
||||
OpenSwarm is proprietary software. See [LICENSE](LICENSE), and the terms of service at [openswarm.com](https://openswarm.com).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env node
|
||||
// Phase 5a promotion gate: before a draft release is allowed to become "latest",
|
||||
// prove both auto-updater feeds exist, agree on version, and that their assets
|
||||
// actually resolve. A release that ships latest.yml but not latest-mac.yml (or
|
||||
// with mismatched versions) silently strands one platform's users on the old
|
||||
// build, which is the exact "broken latest" failure this gate exists to stop.
|
||||
//
|
||||
// Usage:
|
||||
// node scripts/release/verify-release.js --dir <artifacts-dir> --expect-version 1.2.3
|
||||
// node scripts/release/verify-release.js --dir <dir> --expect-version 1.2.3 \
|
||||
// --base-url https://github.com/openswarm-ai/openswarm/releases/download/v1.2.3
|
||||
//
|
||||
// --dir directory containing latest.yml + latest-mac.yml
|
||||
// --expect-version version both feeds (and their filenames) must match
|
||||
// --base-url if given, HEAD-check every referenced asset resolves (200)
|
||||
//
|
||||
// Exit 0 = promotable. Exit 1 = blocked (prints the first blocking reason).
|
||||
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
const FEEDS = ['latest.yml', 'latest-mac.yml'];
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = { dir: null, expectVersion: null, baseUrl: null, json: false };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
if (argv[i] === '--dir') out.dir = argv[++i];
|
||||
else if (argv[i] === '--expect-version') out.expectVersion = argv[++i];
|
||||
else if (argv[i] === '--base-url') out.baseUrl = argv[++i];
|
||||
else if (argv[i] === '--json') out.json = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Minimal electron-builder-feed parser. We only need `version:` and the asset
|
||||
// filenames (top-level `path:` plus each `- url:` under `files:`). Avoiding a
|
||||
// YAML dependency keeps this runnable on a bare CI node with no install step.
|
||||
function parseFeed(text) {
|
||||
const version = (text.match(/^version:\s*(.+)$/m) || [])[1];
|
||||
const assets = new Set();
|
||||
const topPath = (text.match(/^path:\s*(.+)$/m) || [])[1];
|
||||
if (topPath) assets.add(topPath.trim());
|
||||
const urlRe = /^\s*-?\s*url:\s*(.+)$/gm;
|
||||
let m;
|
||||
while ((m = urlRe.exec(text)) !== null) assets.add(m[1].trim());
|
||||
return { version: version ? version.trim() : null, assets: [...assets] };
|
||||
}
|
||||
|
||||
function headOk(url) {
|
||||
return new Promise((resolve) => {
|
||||
const req = https.request(url, { method: 'HEAD' }, (res) => {
|
||||
// GitHub release assets 302 to a signed CDN URL; follow one hop.
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
headOk(res.headers.location).then(resolve);
|
||||
} else {
|
||||
resolve(res.statusCode === 200);
|
||||
}
|
||||
});
|
||||
req.on('error', () => resolve(false));
|
||||
req.setTimeout(15000, () => { req.destroy(); resolve(false); });
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function fail(msg, json) {
|
||||
if (json) process.stdout.write(JSON.stringify({ ok: false, error: msg }) + '\n');
|
||||
else process.stderr.write(`BLOCKED: ${msg}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
if (!args.dir) fail('--dir <artifacts-dir> is required', args.json);
|
||||
|
||||
const feeds = {};
|
||||
for (const name of FEEDS) {
|
||||
const p = path.join(args.dir, name);
|
||||
if (!fs.existsSync(p)) fail(`missing feed: ${name} (one platform would be stranded on the old build)`, args.json);
|
||||
feeds[name] = parseFeed(fs.readFileSync(p, 'utf8'));
|
||||
if (!feeds[name].version) fail(`${name} has no version: field`, args.json);
|
||||
}
|
||||
|
||||
const versions = FEEDS.map((n) => feeds[n].version);
|
||||
if (new Set(versions).size !== 1) {
|
||||
fail(`version mismatch across feeds: ${FEEDS.map((n) => `${n}=${feeds[n].version}`).join(', ')}`, args.json);
|
||||
}
|
||||
const releaseVersion = versions[0];
|
||||
|
||||
if (args.expectVersion) {
|
||||
const want = args.expectVersion.replace(/^v/, '');
|
||||
if (releaseVersion !== want) fail(`feeds say ${releaseVersion} but expected ${want}`, args.json);
|
||||
}
|
||||
|
||||
if (args.baseUrl) {
|
||||
const base = args.baseUrl.replace(/\/+$/, '');
|
||||
for (const name of FEEDS) {
|
||||
// The .yml itself must resolve, plus every asset it points at.
|
||||
const toCheck = [name, ...feeds[name].assets];
|
||||
for (const asset of toCheck) {
|
||||
const url = `${base}/${asset}`;
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const ok = await headOk(url);
|
||||
if (!ok) fail(`asset does not resolve (HEAD != 200): ${url}`, args.json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = { ok: true, version: releaseVersion, feeds: FEEDS, checkedUrls: !!args.baseUrl };
|
||||
if (args.json) process.stdout.write(JSON.stringify(result) + '\n');
|
||||
else {
|
||||
process.stdout.write(`\nPROMOTABLE: both feeds present, version ${releaseVersion} agrees`);
|
||||
process.stdout.write(args.baseUrl ? ', all assets resolve.\n\n' : ' (URL check skipped; pass --base-url to enable).\n\n');
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user