| … | … | @@ -28,6 +28,7 @@ const HEX64_RE = /^[0-9a-f]{64}$/i |
| 28 | 28 | const Z32_RE = /^[13-9a-km-uw-z]{52}$/i |
| 29 | 29 | const APP_LINK_RE = /^(?:hyper|pear|file):\/\/.+/i |
| 30 | 30 | const VERIFICATION_RANK = { 'author-signed': 3, 'relay-listed': 2, unverified: 1 } |
| 31 | const NATIVE_DELIVERY_STATUSES = new Set(['migration-required', 'available']) | |
| 31 | 32 | |
| 32 | 33 | function trimString (value) { |
| 33 | 34 | return typeof value === 'string' ? value.trim() : '' |
| … | … | @@ -90,6 +91,16 @@ function normalizeAppType (value) { |
| 90 | 91 | return type === 'standalone' || type === 'hypersite' ? type : '' |
| 91 | 92 | } |
| 92 | 93 | |
| 94 | function normalizeLegacyMigrationId (value) { | |
| 95 | const id = trimString(value).toLowerCase() | |
| 96 | return Z32_RE.test(id) ? id : '' | |
| 97 | } | |
| 98 | ||
| 99 | function normalizeNativeDelivery (value) { | |
| 100 | const status = trimString(value && typeof value === 'object' ? value.status : value) | |
| 101 | return NATIVE_DELIVERY_STATUSES.has(status) ? { status } : null | |
| 102 | } | |
| 103 | ||
| 93 | 104 | function normalizeCatalogApp (app, opts = {}) { |
| 94 | 105 | if (!app || typeof app !== 'object' || Array.isArray(app)) return null |
| 95 | 106 | const out = { ...app } |
| … | … | @@ -102,8 +113,10 @@ function normalizeCatalogApp (app, opts = {}) { |
| 102 | 113 | const rawLink = trimString(out.link) |
| 103 | 114 | const link = normalizeCatalogLink(out.link) |
| 104 | 115 | const driveKey = normalizeDriveKey(key) || driveKeyFromHyperLink(link) |
| 105 | const id = trimString(out.id) || driveKey || link | |
| 106 | if ((key && !driveKey && !link) || (rawLink && !link && !driveKey) || (!driveKey && !link) || !id) return null | |
| 116 | const legacyMigrationId = normalizeLegacyMigrationId(out.legacyMigrationId) | |
| 117 | const nativeDelivery = normalizeNativeDelivery(out.nativeDelivery) | |
| 118 | const id = trimString(out.id) || driveKey || link || (legacyMigrationId ? `legacy:${legacyMigrationId}` : '') | |
| 119 | if ((key && !driveKey && !link) || (rawLink && !link && !driveKey) || (!driveKey && !link && !legacyMigrationId) || !id) return null | |
| 107 | 120 | |
| 108 | 121 | out.id = id || undefined |
| 109 | 122 | out.name = trimString(out.name) || id || undefined |
| … | … | @@ -111,6 +124,10 @@ function normalizeCatalogApp (app, opts = {}) { |
| 111 | 124 | else delete out.driveKey |
| 112 | 125 | if (link) out.link = link |
| 113 | 126 | else delete out.link |
| 127 | if (legacyMigrationId) out.legacyMigrationId = legacyMigrationId | |
| 128 | else delete out.legacyMigrationId | |
| 129 | if (nativeDelivery) out.nativeDelivery = nativeDelivery | |
| 130 | else delete out.nativeDelivery | |
| 114 | 131 | const type = normalizeAppType(out.type) |
| 115 | 132 | if (type) out.type = type |
| 116 | 133 | else delete out.type |
| … | … | @@ -145,6 +162,8 @@ function catalogAppStableKey (app) { |
| 145 | 162 | const driveKey = normalizeDriveKey(key) || driveKeyFromHyperLink(link) |
| 146 | 163 | if (driveKey) return `drive:${driveKey}` |
| 147 | 164 | if (link) return `link:${link}` |
| 165 | const legacyMigrationId = normalizeLegacyMigrationId(app.legacyMigrationId) | |
| 166 | if (legacyMigrationId) return `legacy:${legacyMigrationId}` | |
| 148 | 167 | const id = trimString(app.id) |
| 149 | 168 | if (id) return `id:${id}` |
| 150 | 169 | return '' |
| … | … | @@ -233,6 +252,8 @@ function catalogAppSearchText (app) { |
| 233 | 252 | app.source, |
| 234 | 253 | app.catalogName, |
| 235 | 254 | app.verification, |
| 255 | app.legacyMigrationId, | |
| 256 | app.nativeDelivery && app.nativeDelivery.status, | |
| 236 | 257 | app.link, |
| 237 | 258 | app.driveKey, |
| 238 | 259 | ...(Array.isArray(app.categories) ? app.categories : []), |
| … | … | @@ -293,6 +314,8 @@ module.exports = { |
| 293 | 314 | normalizeDriveKey, |
| 294 | 315 | driveKeyFromHyperLink, |
| 295 | 316 | normalizeCatalogData, |
| 317 | normalizeLegacyMigrationId, | |
| 318 | normalizeNativeDelivery, | |
| 296 | 319 | normalizeAppType, |
| 297 | 320 | scrubPrototypeKeys, |
| 298 | 321 | safeJSONParse, |
| … | … | @@ -18,7 +18,9 @@ module.exports = { |
| 18 | 18 | "name": "PearBrowser Desktop", |
| 19 | 19 | "type": "standalone", |
| 20 | 20 | "driveKey": "03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f", |
| 21 | "link": "pear://tco5k7h38uoxatedp1wongdbhjxow1x7jiwm3t1i9cujbebhsbty", | |
| 21 | "link": "hyper://03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f/", | |
| 22 | "legacyMigrationId": "tco5k7h38uoxatedp1wongdbhjxow1x7jiwm3t1i9cujbebhsbty", | |
| 23 | "nativeDeliveryStatus": "migration-required", | |
| 22 | 24 | "author": "bigdestiny2", |
| 23 | 25 | "categories": [ |
| 24 | 26 | "browser", |
| … | … | @@ -90,7 +92,9 @@ module.exports = { |
| 90 | 92 | "name": "Pear Dealroom", |
| 91 | 93 | "type": "standalone", |
| 92 | 94 | "driveKey": "0724aabf2ad6394983f91c6b24ebd417cb3d25addcf29c98eb246c512dc77f90", |
| 93 | "link": "pear://7octu5dimjye8rn68raehwuetk5mttzt4zxh6yjw1eqktn49bq7o", | |
| 95 | "link": "hyper://0724aabf2ad6394983f91c6b24ebd417cb3d25addcf29c98eb246c512dc77f90/", | |
| 96 | "legacyMigrationId": "7octu5dimjye8rn68raehwuetk5mttzt4zxh6yjw1eqktn49bq7o", | |
| 97 | "nativeDeliveryStatus": "migration-required", | |
| 94 | 98 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMzIgMTJsMTYgNnYxMWMwIDExLTcgMTgtMTYgMjMtOS01LTE2LTEyLTE2LTIzVjE4eiIgZmlsbD0iIzU4YTZmZiIvPjxnIHN0cm9rZT0iIzBhMGQxMiIgc3Ryb2tlLXdpZHRoPSIyLjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTI2IDI4aDEyTTI2IDM0aDEyTTI2IDQwaDgiLz48L2c+PC9zdmc+", |
| 95 | 99 | "author": "bigdestiny2", |
| 96 | 100 | "categories": [ |
| … | … | @@ -108,7 +112,9 @@ module.exports = { |
| 108 | 112 | "name": "Paste", |
| 109 | 113 | "type": "standalone", |
| 110 | 114 | "driveKey": "25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b", |
| 111 | "link": "pear://qnax5k8ojtod51ci9qwkrawdof1hx5w3a7gqbueoqnzzq9dw5hfo", | |
| 115 | "link": "hyper://25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b/", | |
| 116 | "legacyMigrationId": "qnax5k8ojtod51ci9qwkrawdof1hx5w3a7gqbueoqnzzq9dw5hfo", | |
| 117 | "nativeDeliveryStatus": "migration-required", | |
| 112 | 118 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIxOCIgeT0iMTYiIHdpZHRoPSIyOCIgaGVpZ2h0PSIzNCIgcng9IjQiIGZpbGw9IiM0YWRlODAiLz48cmVjdCB4PSIyNSIgeT0iMTEiIHdpZHRoPSIxNCIgaGVpZ2h0PSI5IiByeD0iMi41IiBmaWxsPSIjNGFkZTgwIiBzdHJva2U9IiMwYTBkMTIiIHN0cm9rZS13aWR0aD0iMiIvPjxnIHN0cm9rZT0iIzBhMGQxMiIgc3Ryb2tlLXdpZHRoPSIyLjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTI0IDMwaDE2TTI0IDM2aDE2TTI0IDQyaDEwIi8+PC9nPjwvc3ZnPg==", |
| 113 | 119 | "author": "bigdestiny2", |
| 114 | 120 | "categories": [ |
| … | … | @@ -125,9 +131,10 @@ module.exports = { |
| 125 | 131 | }, |
| 126 | 132 | { |
| 127 | 133 | "name": "PearPoker", |
| 128 | "type": "standalone", | |
| 134 | "type": "hypersite", | |
| 129 | 135 | "driveKey": "850929ab0b7f1eb927dd69c6ae057af0a43fba1ced4c33e0df2e7cff0ee92268", |
| 130 | "link": "pear://owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy", | |
| 136 | "legacyMigrationId": "owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy", | |
| 137 | "nativeDeliveryStatus": "migration-required", | |
| 131 | 138 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMzIgMTNDMjEgMjUgMTUgMjkgMTUgMzdhOCA4IDAgMCAwIDE0IDVjLTEgNC0zIDYtNiA5aDE4Yy0zLTMtNS01LTYtOWE4IDggMCAwIDAgMTQtNWMwLTgtNi0xMi0xNy0yNHoiIGZpbGw9IiMzZmI5NTAiLz48L3N2Zz4=", |
| 132 | 139 | "author": "bigdestiny2", |
| 133 | 140 | "categories": [ |
| … | … | @@ -142,8 +149,9 @@ module.exports = { |
| 142 | 149 | }, |
| 143 | 150 | { |
| 144 | 151 | "name": "Keet", |
| 145 | "type": "standalone", | |
| 146 | "link": "pear://oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo", | |
| 152 | "type": "hypersite", | |
| 153 | "legacyMigrationId": "oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo", | |
| 154 | "nativeDeliveryStatus": "migration-required", | |
| 147 | 155 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjFhNiA2IDAgMCAxIDYtNmgyNGE2IDYgMCAwIDEgNiA2djE1YTYgNiAwIDAgMS02IDZIMjlsLTEwIDh2LThhNiA2IDAgMCAxLTUtNnoiIGZpbGw9IiNmYmJmMjQiLz48ZyBmaWxsPSIjMGEwZDEyIj48Y2lyY2xlIGN4PSIyNSIgY3k9IjI4LjUiIHI9IjIuNiIvPjxjaXJjbGUgY3g9IjMzIiBjeT0iMjguNSIgcj0iMi42Ii8+PGNpcmNsZSBjeD0iNDEiIGN5PSIyOC41IiByPSIyLjYiLz48L2c+PC9zdmc+", |
| 148 | 156 | "author": "Holepunch", |
| 149 | 157 | "categories": [ |
| … | … | @@ -157,8 +165,9 @@ module.exports = { |
| 157 | 165 | }, |
| 158 | 166 | { |
| 159 | 167 | "name": "PearPass", |
| 160 | "type": "standalone", | |
| 161 | "link": "pear://tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo", | |
| 168 | "type": "hypersite", | |
| 169 | "legacyMigrationId": "tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo", | |
| 170 | "nativeDeliveryStatus": "migration-required", | |
| 162 | 171 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIxOCIgeT0iMzAiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyMiIgcng9IjQiIGZpbGw9IiMzZmI5NTAiLz48cGF0aCBkPSJNMjQgMzB2LTZhOCA4IDAgMCAxIDE2IDB2NiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjM2ZiOTUwIiBzdHJva2Utd2lkdGg9IjQiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjM5IiByPSIzLjQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIzMC41IiB5PSIzOSIgd2lkdGg9IjMiIGhlaWdodD0iOCIgcng9IjEuNSIgZmlsbD0iIzBhMGQxMiIvPjwvc3ZnPg==", |
| 163 | 172 | "author": "Tether", |
| 164 | 173 | "categories": [ |
| … | … | @@ -173,7 +182,8 @@ module.exports = { |
| 173 | 182 | { |
| 174 | 183 | "name": "Peercord", |
| 175 | 184 | "type": "standalone", |
| 176 | "link": "pear://wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy", | |
| 185 | "legacyMigrationId": "wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy", | |
| 186 | "nativeDeliveryStatus": "migration-required", | |
| 177 | 187 | "author": "Mastercodeon", |
| 178 | 188 | "categories": [ |
| 179 | 189 | "chat", |
| … | … | @@ -204,8 +214,9 @@ module.exports = { |
| 204 | 214 | }, |
| 205 | 215 | { |
| 206 | 216 | "name": "anonGPT", |
| 207 | "type": "standalone", | |
| 208 | "link": "pear://rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo", | |
| 217 | "type": "hypersite", | |
| 218 | "legacyMigrationId": "rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo", | |
| 219 | "nativeDeliveryStatus": "migration-required", | |
| 209 | 220 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjFhNiA2IDAgMCAxIDYtNmgyNGE2IDYgMCAwIDEgNiA2djE1YTYgNiAwIDAgMS02IDZIMjlsLTEwIDh2LThhNiA2IDAgMCAxLTUtNnoiIGZpbGw9IiMyMmQzZWUiLz48cGF0aCBkPSJNMzMgMjBsMi4zIDUuNyA1LjcgMi4zLTUuNyAyLjMtMi4zIDUuNy0yLjMtNS43LTUuNy0yLjMgNS43LTIuM3oiIGZpbGw9IiMwYTBkMTIiLz48L3N2Zz4=", |
| 210 | 221 | "author": "anonGPT", |
| 211 | 222 | "categories": [ |
| … | … | @@ -218,9 +229,10 @@ module.exports = { |
| 218 | 229 | }, |
| 219 | 230 | { |
| 220 | 231 | "name": "Pear POS", |
| 221 | "type": "standalone", | |
| 232 | "type": "hypersite", | |
| 222 | 233 | "driveKey": "b776f15f3e6860ecf6d923853c295350e55b708772e67d7124899b96aecfcd43", |
| 223 | "link": "pear://myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy", | |
| 234 | "legacyMigrationId": "myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy", | |
| 235 | "nativeDeliveryStatus": "migration-required", | |
| 224 | 236 | "author": "Pear POS", |
| 225 | 237 | "categories": [ |
| 226 | 238 | "business", |
| … | … | @@ -233,9 +245,10 @@ module.exports = { |
| 233 | 245 | }, |
| 234 | 246 | { |
| 235 | 247 | "name": "Pear Tickets", |
| 236 | "type": "standalone", | |
| 248 | "type": "hypersite", | |
| 237 | 249 | "driveKey": "ec309f516da659718746fe10ded086e2b6d157718c3e3651f86e07a4df34210a", |
| 238 | "link": "pear://gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy", | |
| 250 | "legacyMigrationId": "gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy", | |
| 251 | "nativeDeliveryStatus": "migration-required", | |
| 239 | 252 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjRoMzZhNCA0IDAgMCAxIDQgNHYxLjVhMy4yIDMuMiAwIDAgMCAwIDYuNFYzN2E0IDQgMCAwIDEtNCA0SDE0YTQgNCAwIDAgMS00LTR2LTEuMWEzLjIgMy4yIDAgMCAwIDAtNi40VjI4YTQgNCAwIDAgMSA0LTR6IiBmaWxsPSIjZmJiZjI0Ii8+PHBhdGggZD0iTTQwIDI2djEzIiBzdHJva2U9IiMwYTBkMTIiIHN0cm9rZS13aWR0aD0iMi40IiBzdHJva2UtZGFzaGFycmF5PSIzIDMiLz48L3N2Zz4=", |
| 240 | 253 | "author": "bigdestiny2", |
| 241 | 254 | "categories": [ |
| … | … | @@ -21,7 +21,7 @@ const b4a = require('b4a') |
| 21 | 21 | const { normalizeCatalogApp } = require('./catalog-safety.cjs') |
| 22 | 22 | |
| 23 | 23 | // The canonical `apps` schema (design §4.1). Created on a fresh room; carries |
| 24 | // both driveKey (installable) and link (launch-only), plus the run-in-tab type. | |
| 24 | // browser-drive metadata and native migration state, plus the run-in-tab type. | |
| 25 | 25 | const APPS_SCHEMA = { |
| 26 | 26 | type: 'object', |
| 27 | 27 | properties: { |
| … | … | @@ -30,6 +30,8 @@ const APPS_SCHEMA = { |
| 30 | 30 | description: { type: 'string', maxLength: 1000 }, |
| 31 | 31 | driveKey: { type: 'string', pattern: '^[0-9a-f]{64}$' }, |
| 32 | 32 | link: { type: 'string', maxLength: 300 }, |
| 33 | legacyMigrationId: { type: 'string', pattern: '^[13-9a-km-uw-z]{52}$' }, | |
| 34 | nativeDeliveryStatus: { enum: ['migration-required', 'available'] }, | |
| 33 | 35 | iconData: { type: 'string', maxLength: 20000 }, |
| 34 | 36 | type: { enum: ['standalone', 'hypersite'] }, |
| 35 | 37 | author: { type: 'string', maxLength: 200 }, |
| … | … | @@ -43,7 +45,7 @@ const APPS_SCHEMA = { |
| 43 | 45 | verification: { enum: ['unverified', 'relay-listed', 'author-signed'] } |
| 44 | 46 | }, |
| 45 | 47 | required: ['name', 'type'], |
| 46 | anyOf: [{ required: ['driveKey'] }, { required: ['link'] }], | |
| 48 | anyOf: [{ required: ['driveKey'] }, { required: ['link'] }, { required: ['legacyMigrationId'] }], | |
| 47 | 49 | additionalProperties: false |
| 48 | 50 | } |
| 49 | 51 | |
| … | … | @@ -114,6 +116,8 @@ function rowToApp (row) { |
| 114 | 116 | description: j.description, |
| 115 | 117 | driveKey: j.driveKey || null, |
| 116 | 118 | link: j.link || null, |
| 119 | legacyMigrationId: j.legacyMigrationId || null, | |
| 120 | nativeDelivery: j.nativeDeliveryStatus ? { status: j.nativeDeliveryStatus } : null, | |
| 117 | 121 | type: j.type || (j.link && !j.driveKey ? 'standalone' : undefined), |
| 118 | 122 | author: j.author || null, |
| 119 | 123 | homepage: j.homepage || null, |
| … | … | @@ -17,7 +17,7 @@ |
| 17 | 17 | "p2p" |
| 18 | 18 | ], |
| 19 | 19 | "publishedAt": "2026-04-28T00:00:00Z", |
| 20 | "legacyPearLink": "pear://tco5k7h38uoxatedp1wongdbhjxow1x7jiwm3t1i9cujbebhsbty", | |
| 20 | "legacyMigrationId": "tco5k7h38uoxatedp1wongdbhjxow1x7jiwm3t1i9cujbebhsbty", | |
| 21 | 21 | "nativeDelivery": { "status": "migration-required" }, |
| 22 | 22 | "driveKey": "03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f", |
| 23 | 23 | "homepage": "hyper://03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f/", |
| … | … | @@ -116,7 +116,7 @@ |
| 116 | 116 | ], |
| 117 | 117 | "verification": "relay-listed", |
| 118 | 118 | "link": "hyper://0724aabf2ad6394983f91c6b24ebd417cb3d25addcf29c98eb246c512dc77f90/", |
| 119 | "legacyPearLink": "pear://7octu5dimjye8rn68raehwuetk5mttzt4zxh6yjw1eqktn49bq7o", | |
| 119 | "legacyMigrationId": "7octu5dimjye8rn68raehwuetk5mttzt4zxh6yjw1eqktn49bq7o", | |
| 120 | 120 | "nativeDelivery": { "status": "migration-required" }, |
| 121 | 121 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMzIgMTJsMTYgNnYxMWMwIDExLTcgMTgtMTYgMjMtOS01LTE2LTEyLTE2LTIzVjE4eiIgZmlsbD0iIzU4YTZmZiIvPjxnIHN0cm9rZT0iIzBhMGQxMiIgc3Ryb2tlLXdpZHRoPSIyLjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTI2IDI4aDEyTTI2IDM0aDEyTTI2IDQwaDgiLz48L2c+PC9zdmc+" |
| 122 | 122 | }, |
| … | … | @@ -134,7 +134,7 @@ |
| 134 | 134 | "publishedAt": "2026-06-21T00:00:00Z", |
| 135 | 135 | "type": "standalone", |
| 136 | 136 | "link": "hyper://25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b/", |
| 137 | "legacyPearLink": "pear://qnax5k8ojtod51ci9qwkrawdof1hx5w3a7gqbueoqnzzq9dw5hfo", | |
| 137 | "legacyMigrationId": "qnax5k8ojtod51ci9qwkrawdof1hx5w3a7gqbueoqnzzq9dw5hfo", | |
| 138 | 138 | "nativeDelivery": { "status": "migration-required" }, |
| 139 | 139 | "homepage": "hyper://25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b/", |
| 140 | 140 | "url": "hyper://25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b/", |
| … | … | @@ -161,8 +161,8 @@ |
| 161 | 161 | "p2p" |
| 162 | 162 | ], |
| 163 | 163 | "publishedAt": "2026-06-21T00:00:00Z", |
| 164 | "pearLink": "pear://owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy", | |
| 165 | "link": "pear://owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy", | |
| 164 | "legacyMigrationId": "owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy", | |
| 165 | "nativeDelivery": { "status": "migration-required" }, | |
| 166 | 166 | "driveKey": "850929ab0b7f1eb927dd69c6ae057af0a43fba1ced4c33e0df2e7cff0ee92268", |
| 167 | 167 | "type": "pear-app", |
| 168 | 168 | "license": "MIT", |
| … | … | @@ -187,8 +187,8 @@ |
| 187 | 187 | "version": "1.0.0", |
| 188 | 188 | "publishedAt": "2026-06-21T00:00:00Z", |
| 189 | 189 | "verification": "relay-listed", |
| 190 | "link": "pear://oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo", | |
| 191 | "pearLink": "pear://oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo", | |
| 190 | "legacyMigrationId": "oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo", | |
| 191 | "nativeDelivery": { "status": "migration-required" }, | |
| 192 | 192 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjFhNiA2IDAgMCAxIDYtNmgyNGE2IDYgMCAwIDEgNiA2djE1YTYgNiAwIDAgMS02IDZIMjlsLTEwIDh2LThhNiA2IDAgMCAxLTUtNnoiIGZpbGw9IiNmYmJmMjQiLz48ZyBmaWxsPSIjMGEwZDEyIj48Y2lyY2xlIGN4PSIyNSIgY3k9IjI4LjUiIHI9IjIuNiIvPjxjaXJjbGUgY3g9IjMzIiBjeT0iMjguNSIgcj0iMi42Ii8+PGNpcmNsZSBjeD0iNDEiIGN5PSIyOC41IiByPSIyLjYiLz48L2c+PC9zdmc+" |
| 193 | 193 | }, |
| 194 | 194 | { |
| … | … | @@ -204,15 +204,15 @@ |
| 204 | 204 | "version": "1.0.0", |
| 205 | 205 | "publishedAt": "2026-06-21T00:00:00Z", |
| 206 | 206 | "verification": "relay-listed", |
| 207 | "link": "pear://tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo", | |
| 208 | "pearLink": "pear://tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo", | |
| 207 | "legacyMigrationId": "tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo", | |
| 208 | "nativeDelivery": { "status": "migration-required" }, | |
| 209 | 209 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIxOCIgeT0iMzAiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyMiIgcng9IjQiIGZpbGw9IiMzZmI5NTAiLz48cGF0aCBkPSJNMjQgMzB2LTZhOCA4IDAgMCAxIDE2IDB2NiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjM2ZiOTUwIiBzdHJva2Utd2lkdGg9IjQiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjM5IiByPSIzLjQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIzMC41IiB5PSIzOSIgd2lkdGg9IjMiIGhlaWdodD0iOCIgcng9IjEuNSIgZmlsbD0iIzBhMGQxMiIvPjwvc3ZnPg==" |
| 210 | 210 | }, |
| 211 | 211 | { |
| 212 | 212 | "id": "peercord", |
| 213 | 213 | "name": "Peercord", |
| 214 | 214 | "type": "standalone", |
| 215 | "link": "pear://wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy", | |
| 215 | "legacyMigrationId": "wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy", | |
| 216 | 216 | "author": "Mastercodeon", |
| 217 | 217 | "categories": [ |
| 218 | 218 | "chat", |
| … | … | @@ -226,7 +226,7 @@ |
| 226 | 226 | "homepage": "https://git.churchofmalware.org/mastercodeon/Peercord", |
| 227 | 227 | "source": "https://git.churchofmalware.org/mastercodeon/Peercord", |
| 228 | 228 | "license": "GPL-3.0", |
| 229 | "pearLink": "pear://wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy", | |
| 229 | "nativeDelivery": { "status": "migration-required" }, | |
| 230 | 230 | "publishedAt": "2026-06-22T00:00:00Z" |
| 231 | 231 | }, |
| 232 | 232 | { |
| … | … | @@ -257,8 +257,8 @@ |
| 257 | 257 | "version": "1.0.0", |
| 258 | 258 | "publishedAt": "2026-06-21T00:00:00Z", |
| 259 | 259 | "verification": "relay-listed", |
| 260 | "link": "pear://rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo", | |
| 261 | "pearLink": "pear://rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo", | |
| 260 | "legacyMigrationId": "rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo", | |
| 261 | "nativeDelivery": { "status": "migration-required" }, | |
| 262 | 262 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjFhNiA2IDAgMCAxIDYtNmgyNGE2IDYgMCAwIDEgNiA2djE1YTYgNiAwIDAgMS02IDZIMjlsLTEwIDh2LThhNiA2IDAgMCAxLTUtNnoiIGZpbGw9IiMyMmQzZWUiLz48cGF0aCBkPSJNMzMgMjBsMi4zIDUuNyA1LjcgMi4zLTUuNyAyLjMtMi4zIDUuNy0yLjMtNS43LTUuNy0yLjMgNS43LTIuM3oiIGZpbGw9IiMwYTBkMTIiLz48L3N2Zz4=" |
| 263 | 263 | }, |
| 264 | 264 | { |
| … | … | @@ -275,8 +275,8 @@ |
| 275 | 275 | "publishedAt": "2026-06-21T00:00:00Z", |
| 276 | 276 | "verification": "relay-listed", |
| 277 | 277 | "driveKey": "b776f15f3e6860ecf6d923853c295350e55b708772e67d7124899b96aecfcd43", |
| 278 | "link": "pear://myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy", | |
| 279 | "pearLink": "pear://myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy" | |
| 278 | "legacyMigrationId": "myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy", | |
| 279 | "nativeDelivery": { "status": "migration-required" } | |
| 280 | 280 | }, |
| 281 | 281 | { |
| 282 | 282 | "id": "pear-tickets", |
| … | … | @@ -291,8 +291,8 @@ |
| 291 | 291 | "publishedAt": "2026-06-21T00:00:00Z", |
| 292 | 292 | "verification": "relay-listed", |
| 293 | 293 | "driveKey": "ec309f516da659718746fe10ded086e2b6d157718c3e3651f86e07a4df34210a", |
| 294 | "link": "pear://gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy", | |
| 295 | "pearLink": "pear://gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy", | |
| 294 | "legacyMigrationId": "gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy", | |
| 295 | "nativeDelivery": { "status": "migration-required" }, | |
| 296 | 296 | "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjRoMzZhNCA0IDAgMCAxIDQgNHYxLjVhMy4yIDMuMiAwIDAgMCAwIDYuNFYzN2E0IDQgMCAwIDEtNCA0SDE0YTQgNCAwIDAgMS00LTR2LTEuMWEzLjIgMy4yIDAgMCAwIDAtNi40VjI4YTQgNCAwIDAgMSA0LTR6IiBmaWxsPSIjZmJiZjI0Ii8+PHBhdGggZD0iTTQwIDI2djEzIiBzdHJva2U9IiMwYTBkMTIiIHN0cm9rZS13aWR0aD0iMi40IiBzdHJva2UtZGFzaGFycmF5PSIzIDMiLz48L3N2Zz4=" |
| 297 | 297 | } |
| 298 | 298 | ] |
| … | … | @@ -14,7 +14,8 @@ |
| 14 | 14 | // The manifest is rich (id, pearLink, homepage, source, license, platforms, …); |
| 15 | 15 | // seed rows are validated against the strict APPS_SCHEMA |
| 16 | 16 | // (additionalProperties:false), so we project onto exactly its allowed fields and |
| 17 | // infer `type` from `link`. | |
| 17 | // infer `type` from `link`. Native migration records retain only a local | |
| 18 | // migration ID, never an executable remote URI. | |
| 18 | 19 | |
| 19 | 20 | import { readFileSync, writeFileSync } from 'node:fs' |
| 20 | 21 | import { fileURLToPath } from 'node:url' |
| … | … | @@ -52,6 +53,8 @@ function toSeedRow (a) { |
| 52 | 53 | const row = { name: a.name, type: manifestType(a.type) || (a.link ? 'standalone' : 'hypersite') } |
| 53 | 54 | if (a.driveKey && /^[0-9a-f]{64}$/i.test(a.driveKey)) row.driveKey = a.driveKey |
| 54 | 55 | if (a.link) row.link = a.link |
| 56 | if (typeof a.legacyMigrationId === 'string') row.legacyMigrationId = a.legacyMigrationId | |
| 57 | if (a.nativeDelivery && typeof a.nativeDelivery.status === 'string') row.nativeDeliveryStatus = a.nativeDelivery.status | |
| 55 | 58 | // Inline icon (data: URI) for apps without a fetchable drive icon — rendered |
| 56 | 59 | // directly by AppIcon, so every catalogue app shows a real icon, not a glyph. |
| 57 | 60 | if (a.iconData) row.iconData = a.iconData |
| … | … | @@ -69,7 +72,7 @@ function toSeedRow (a) { |
| 69 | 72 | const rows = cat.apps.map(toSeedRow) |
| 70 | 73 | for (const [i, r] of rows.entries()) { |
| 71 | 74 | if (!r.name || !r.type) throw new Error(`row ${i} missing name/type`) |
| 72 | if (!r.driveKey && !r.link) throw new Error(`row ${i} (${r.name}) has neither driveKey nor link`) | |
| 75 | if (!r.driveKey && !r.link && !r.legacyMigrationId) throw new Error(`row ${i} (${r.name}) has neither driveKey, link, nor legacy migration ID`) | |
| 73 | 76 | } |
| 74 | 77 | |
| 75 | 78 | const header = `/** |
| … | … | @@ -270,7 +270,7 @@ test('Pear stage ignore excludes local release/operator scratch files', () => { |
| 270 | 270 | assert.ok(ignored.includes('/test')) |
| 271 | 271 | }) |
| 272 | 272 | |
| 273 | test('PearBrowser catalogue release row stays in sync with package and public addresses', () => { | |
| 273 | test('PearBrowser catalogue row is a migration-required native record, not a remote launcher', () => { | |
| 274 | 274 | const source = catalogSource.apps.find((app) => app.id === 'pearbrowser-desktop') |
| 275 | 275 | const seed = SEED_APPS.find((app) => app.name === 'PearBrowser Desktop') |
| 276 | 276 | const homepageKey = '03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f' |
| … | … | @@ -278,11 +278,13 @@ test('PearBrowser catalogue release row stays in sync with package and public ad |
| 278 | 278 | assert.ok(source, 'catalogue source row missing') |
| 279 | 279 | assert.ok(seed, 'offline catalogue seed row missing') |
| 280 | 280 | assert.equal(source.version, pkg.version) |
| 281 | assert.equal(source.pearLink, pearConfig.links.production) | |
| 282 | assert.equal(source.link, pearConfig.links.production) | |
| 281 | assert.equal(source.legacyMigrationId, pearConfig.links.production.replace(/^pear:\/\//, '')) | |
| 282 | assert.equal(source.nativeDelivery?.status, 'migration-required') | |
| 283 | 283 | assert.equal(source.driveKey, homepageKey) |
| 284 | 284 | assert.equal(source.homepage, `hyper://${homepageKey}/`) |
| 285 | assert.equal(source.link, source.homepage) | |
| 285 | 286 | assert.equal(seed.version, source.version) |
| 287 | assert.equal(seed.nativeDeliveryStatus, source.nativeDelivery.status) | |
| 286 | 288 | assert.equal(seed.link, source.link) |
| 287 | 289 | assert.equal(seed.driveKey, source.driveKey) |
| 288 | 290 | assert.equal(seed.homepage, source.homepage) |