onbuzz 5.4.8 → 5.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -12
- package/scripts/swap-brand.js +66 -0
- package/scripts/verify-update-feed.js +121 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onbuzz",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.9",
|
|
4
4
|
"description": "Loxia OnBuzz - Your AI Fleet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -128,9 +128,9 @@
|
|
|
128
128
|
"uuid": "$uuid"
|
|
129
129
|
},
|
|
130
130
|
"build": {
|
|
131
|
-
"appId": "com.loxia.
|
|
132
|
-
"productName": "Loxia
|
|
133
|
-
"executableName": "loxia-
|
|
131
|
+
"appId": "com.loxia.onbuzz",
|
|
132
|
+
"productName": "Loxia OnBuzz",
|
|
133
|
+
"executableName": "loxia-onbuzz",
|
|
134
134
|
"npmRebuild": false,
|
|
135
135
|
"publish": [
|
|
136
136
|
{
|
|
@@ -154,20 +154,20 @@
|
|
|
154
154
|
},
|
|
155
155
|
"win": {
|
|
156
156
|
"target": "nsis",
|
|
157
|
-
"icon": "electron/icon.png"
|
|
157
|
+
"icon": "electron/icon-onbuzz.png"
|
|
158
|
+
},
|
|
159
|
+
"nsis": {
|
|
160
|
+
"artifactName": "Loxia-OnBuzz-Setup-${version}.${ext}"
|
|
158
161
|
},
|
|
159
162
|
"mac": {
|
|
160
163
|
"target": "dmg",
|
|
161
|
-
"icon": "electron/icon.png",
|
|
162
|
-
"artifactName": "Loxia-
|
|
164
|
+
"icon": "electron/icon-onbuzz.png",
|
|
165
|
+
"artifactName": "Loxia-OnBuzz-${version}-${arch}.${ext}"
|
|
163
166
|
},
|
|
164
167
|
"linux": {
|
|
165
168
|
"target": "AppImage",
|
|
166
|
-
"icon": "electron/icon.png",
|
|
167
|
-
"artifactName": "Loxia-
|
|
168
|
-
},
|
|
169
|
-
"nsis": {
|
|
170
|
-
"artifactName": "Loxia-Autopilot-Setup-${version}.${ext}"
|
|
169
|
+
"icon": "electron/icon-onbuzz.png",
|
|
170
|
+
"artifactName": "Loxia-OnBuzz-${version}.${ext}"
|
|
171
171
|
}
|
|
172
172
|
},
|
|
173
173
|
"pkg": {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Brand swap for publishing: rewrite package.json as the OnBuzz identity
|
|
4
|
+
* (after snapshotting the current file to package.autopilot.json for restore).
|
|
5
|
+
*
|
|
6
|
+
* CRITICAL: the swap must cover the COMPLETE brand surface, including the
|
|
7
|
+
* electron-builder `build` section. The v5.4.8 release was built with a swap
|
|
8
|
+
* that changed only name/bin/description — the base config's hardcoded
|
|
9
|
+
* "Loxia-Autopilot-Setup-…" artifactName leaked into the OnBuzz build, both
|
|
10
|
+
* brands produced identically-named installers, the OnBuzz upload CLOBBERED
|
|
11
|
+
* the Autopilot one, and Autopilot updaters sha-failed (installed apps kept
|
|
12
|
+
* crash-looping). Any build that reads a swapped package.json must inherit
|
|
13
|
+
* brand-correct artifact names.
|
|
14
|
+
*
|
|
15
|
+
* Usage: node scripts/swap-brand.js onbuzz
|
|
16
|
+
* (Restore is a plain copy: package.autopilot.json → package.json.)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'fs';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
|
|
23
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
24
|
+
const pkgPath = path.join(ROOT, 'package.json');
|
|
25
|
+
const backupPath = path.join(ROOT, 'package.autopilot.json');
|
|
26
|
+
|
|
27
|
+
const target = process.argv[2];
|
|
28
|
+
if (target !== 'onbuzz') {
|
|
29
|
+
console.error('Usage: node scripts/swap-brand.js onbuzz');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Snapshot the Autopilot identity for the post-publish restore — byte-exact
|
|
34
|
+
// copy (NOT a re-serialization, which shuffles key order and leaves phantom
|
|
35
|
+
// diffs after the restore).
|
|
36
|
+
fs.copyFileSync(pkgPath, backupPath);
|
|
37
|
+
|
|
38
|
+
const p = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
39
|
+
|
|
40
|
+
// npm identity
|
|
41
|
+
p.name = 'onbuzz';
|
|
42
|
+
p.bin = { onbuzz: './bin/cli.js' };
|
|
43
|
+
p.description = 'Loxia OnBuzz - Your AI Fleet';
|
|
44
|
+
delete p.scripts.prepublishOnly;
|
|
45
|
+
|
|
46
|
+
// electron-builder identity — keep EVERY brand-bearing field consistent so
|
|
47
|
+
// any electron build from this file produces OnBuzz-named, OnBuzz-branded
|
|
48
|
+
// artifacts that cannot collide with Autopilot's on the shared release.
|
|
49
|
+
if (p.build) {
|
|
50
|
+
p.build.appId = 'com.loxia.onbuzz';
|
|
51
|
+
p.build.productName = 'Loxia OnBuzz';
|
|
52
|
+
p.build.executableName = 'loxia-onbuzz';
|
|
53
|
+
p.build.nsis = { ...(p.build.nsis || {}), artifactName: 'Loxia-OnBuzz-Setup-${version}.${ext}' };
|
|
54
|
+
if (p.build.win) p.build.win.icon = 'electron/icon-onbuzz.png';
|
|
55
|
+
if (p.build.mac) {
|
|
56
|
+
p.build.mac.artifactName = 'Loxia-OnBuzz-${version}-${arch}.${ext}';
|
|
57
|
+
p.build.mac.icon = 'electron/icon-onbuzz.png';
|
|
58
|
+
}
|
|
59
|
+
if (p.build.linux) {
|
|
60
|
+
p.build.linux.artifactName = 'Loxia-OnBuzz-${version}.${ext}';
|
|
61
|
+
p.build.linux.icon = 'electron/icon-onbuzz.png';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fs.writeFileSync(pkgPath, JSON.stringify(p, null, 2) + '\n');
|
|
66
|
+
console.log(' name: ' + p.name + ' | nsis artifact: ' + p.build?.nsis?.artifactName);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Update-feed verifier — the guarantee that INSTALLED apps can always find
|
|
4
|
+
* and download the next version.
|
|
5
|
+
*
|
|
6
|
+
* How updates actually work: an installed app (any vintage, either naming
|
|
7
|
+
* era) fetches `<brand>.yml` from the LATEST GitHub release and downloads
|
|
8
|
+
* exactly the filename that yml names, from that same release. It never
|
|
9
|
+
* guesses names — so artifact renames between releases are safe IF AND ONLY
|
|
10
|
+
* IF each release's yml matches its real assets. Every updater incident so
|
|
11
|
+
* far was a violation of that contract:
|
|
12
|
+
* - ≤5.4.7: yml said hyphens, GitHub dot-sanitized the space-named
|
|
13
|
+
* uploads → 404 → installed apps crash-looped.
|
|
14
|
+
* - 5.4.8: onbuzz.yml pointed at the AUTOPILOT installer with a mismatched
|
|
15
|
+
* sha512 (brand artifactName collision) → OnBuzz updates dead.
|
|
16
|
+
*
|
|
17
|
+
* Checks, per brand (autopilot + onbuzz), against the live feed:
|
|
18
|
+
* 1. <brand>.yml exists on the latest release.
|
|
19
|
+
* 2. yml version matches the release tag.
|
|
20
|
+
* 3. Every files[].url names an asset that EXISTS on that release
|
|
21
|
+
* (byte-for-byte name match).
|
|
22
|
+
* 4. yml size matches the asset's actual size (catches wrong-file uploads
|
|
23
|
+
* even when the name matches — sha512 isn't exposed by the GitHub API,
|
|
24
|
+
* size is the practical proxy).
|
|
25
|
+
* 5. The two brands' installers are DISTINCT files (collision guard).
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* node scripts/verify-update-feed.js → verify latest release
|
|
29
|
+
* node scripts/verify-update-feed.js v5.4.8 → verify a specific tag
|
|
30
|
+
*
|
|
31
|
+
* Exit 0 = feed healthy. Exit 1 = installed apps cannot update — fix the
|
|
32
|
+
* release BEFORE walking away.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const REPO = 'Loxia-ai/autopilot-releases';
|
|
36
|
+
const BRANDS = ['autopilot', 'onbuzz'];
|
|
37
|
+
|
|
38
|
+
async function getJson(url) {
|
|
39
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'loxia-feed-verify' } });
|
|
40
|
+
if (!res.ok) throw new Error(`GET ${url} → ${res.status}`);
|
|
41
|
+
return res.json();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function getText(url) {
|
|
45
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'loxia-feed-verify' } });
|
|
46
|
+
if (!res.ok) return null;
|
|
47
|
+
return res.text();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Minimal yml field extraction — the electron-builder yml is flat enough. */
|
|
51
|
+
function parseYml(text) {
|
|
52
|
+
const version = (text.match(/^version:\s*(.+)$/m) || [])[1]?.trim();
|
|
53
|
+
const files = [];
|
|
54
|
+
// files: - url: NAME \n sha512: … \n size: N (block list)
|
|
55
|
+
const fileBlocks = text.matchAll(/-\s+url:\s*(.+)\n(?:\s+sha512:.*\n)?\s+size:\s*(\d+)/g);
|
|
56
|
+
for (const m of fileBlocks) files.push({ url: m[1].trim(), size: Number(m[2]) });
|
|
57
|
+
// fallback: top-level path (older shape) — no size guaranteed
|
|
58
|
+
const path = (text.match(/^path:\s*(.+)$/m) || [])[1]?.trim();
|
|
59
|
+
if (path && !files.some((f) => f.url === path)) files.push({ url: path, size: null });
|
|
60
|
+
return { version, files };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const argTag = process.argv[2];
|
|
64
|
+
const release = argTag
|
|
65
|
+
? await getJson(`https://api.github.com/repos/${REPO}/releases/tags/${argTag}`)
|
|
66
|
+
: await getJson(`https://api.github.com/repos/${REPO}/releases/latest`);
|
|
67
|
+
|
|
68
|
+
const tag = release.tag_name;
|
|
69
|
+
const tagVersion = tag.replace(/^v/, '');
|
|
70
|
+
const assets = new Map(release.assets.map((a) => [a.name, a]));
|
|
71
|
+
|
|
72
|
+
console.log(`\n Verifying update feed on ${tag} (${assets.size} assets)\n`);
|
|
73
|
+
|
|
74
|
+
const problems = [];
|
|
75
|
+
const brandInstallers = {};
|
|
76
|
+
|
|
77
|
+
for (const brand of BRANDS) {
|
|
78
|
+
const ymlName = `${brand}.yml`;
|
|
79
|
+
if (!assets.has(ymlName)) {
|
|
80
|
+
problems.push(`[${brand}] ${ymlName} MISSING from ${tag} — installed ${brand} apps see no update feed.`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const text = await getText(`https://github.com/${REPO}/releases/download/${tag}/${ymlName}`);
|
|
84
|
+
if (!text) { problems.push(`[${brand}] ${ymlName} exists but could not be fetched.`); continue; }
|
|
85
|
+
|
|
86
|
+
const { version, files } = parseYml(text);
|
|
87
|
+
if (version !== tagVersion) {
|
|
88
|
+
problems.push(`[${brand}] ${ymlName} says version ${version} but the release tag is ${tagVersion}.`);
|
|
89
|
+
}
|
|
90
|
+
if (files.length === 0) {
|
|
91
|
+
problems.push(`[${brand}] ${ymlName} lists no files — malformed yml.`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const f of files) {
|
|
95
|
+
const asset = assets.get(f.url);
|
|
96
|
+
if (!asset) {
|
|
97
|
+
problems.push(`[${brand}] yml names "${f.url}" but NO asset with that exact name exists → updater 404s.`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (f.size != null && asset.size !== f.size) {
|
|
101
|
+
problems.push(`[${brand}] "${f.url}": yml size ${f.size} != asset size ${asset.size} → WRONG FILE uploaded under this name (sha will fail).`);
|
|
102
|
+
}
|
|
103
|
+
if (f.url.endsWith('.exe')) brandInstallers[brand] = f.url;
|
|
104
|
+
console.log(` [${brand}] ${f.url} → asset ${asset ? 'found' : 'MISSING'}${f.size != null && asset ? (asset.size === f.size ? ', size OK' : ', SIZE MISMATCH') : ''}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Cross-brand collision: both ymls must not claim the same installer file.
|
|
109
|
+
if (brandInstallers.autopilot && brandInstallers.onbuzz
|
|
110
|
+
&& brandInstallers.autopilot === brandInstallers.onbuzz) {
|
|
111
|
+
problems.push(`BRAND COLLISION: autopilot.yml and onbuzz.yml both point at "${brandInstallers.autopilot}" — one brand's users get the other brand's app (or a sha failure).`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('');
|
|
115
|
+
if (problems.length) {
|
|
116
|
+
console.error(` ❌ Update feed BROKEN on ${tag} — installed apps cannot update cleanly:\n`);
|
|
117
|
+
for (const p of problems) console.error(` • ${p}`);
|
|
118
|
+
console.error('');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
console.log(` ✅ Update feed healthy on ${tag} — installed apps (all vintages, both brands) can find and download this version.\n`);
|