@voltro/plugin-releases 0.84.0 → 0.86.0

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/dist/index.js CHANGED
@@ -1,212 +1,20 @@
1
- import { ReleaseNotFound as e, ReleaseRefused as t } from "./errors.js";
2
- import { a as n, i as r, n as i, o as a, r as o, s, t as c } from "./rollout-CGRKMTfv.js";
3
- import { _ as l, a as u, c as ee, d as te, f as ne, g as d, h as f, l as re, m as ie, o as ae, p as oe, r as p, s as se, t as m, u as ce, v as h, y as g } from "./rpc-DeEV8l3U.js";
4
- import { Effect as _, Runtime as le, Schema as ue } from "effect";
5
- import { randomBytes as v } from "node:crypto";
6
- import { AsyncLocalStorage as y } from "node:async_hooks";
7
- import { definePlugin as de } from "@voltro/protocol";
8
- import { and as fe, boolean as b, eq as x, id as S, integer as C, json as w, queryFor as T, registerRetention as pe, retentionTtlMsFromEnv as me, table as E, text as D, timestamp as O } from "@voltro/database";
9
- import { newTraceId as he } from "@voltro/runtime";
10
- var ge = (e, t, n) => {
11
- let r = /* @__PURE__ */ new Set();
12
- for (let e of t) e.currentReleaseId !== null && r.add(e.currentReleaseId);
13
- let i = [];
14
- for (let a of t) {
15
- let t = n(a.name), o = Number.isFinite(t) ? Math.max(1, Math.floor(t)) : 10, s = e.filter((e) => e.channelId === a.id).sort((e, t) => c(t.version, e.version));
16
- for (let e of s.slice(o)) r.has(e.id) || i.push(e);
17
- }
18
- return i;
19
- }, k = [
20
- "web",
21
- "ios",
22
- "android",
23
- "macos",
24
- "windows",
25
- "linux"
26
- ], A = [
27
- "pwa",
28
- "expo",
29
- "tauri",
30
- "electron"
31
- ], j = E(d, {
32
- id: S({ prefix: "relapp" }),
33
- app: D(),
34
- platform: D(),
35
- shell: D(),
36
- revision: C().default(0),
37
- bundleId: D().nullable(),
38
- updaterPublicKey: D().nullable(),
39
- runtimeVersion: D().nullable(),
40
- createdAt: O().default("now")
41
- }).index("byReleaseAppTarget", [
42
- "app",
43
- "platform",
44
- "shell"
45
- ]), M = E(h, {
46
- id: S({ prefix: "relch" }),
47
- appId: D(),
48
- name: D(),
49
- currentReleaseId: D().nullable(),
50
- minSupportedVersion: D().nullable(),
51
- killSwitches: w().nullable(),
52
- public: b().default(!1),
53
- allowUnsigned: b().default(!1),
54
- updatedAt: O().default("now").onUpdate("now")
55
- }).index("byReleaseChannelApp", ["appId", "name"]), N = E(f, {
56
- id: S({ prefix: "rel" }),
57
- appId: D(),
58
- channelId: D(),
59
- version: D(),
60
- buildId: D().nullable(),
61
- notes: D().nullable(),
62
- state: D().default("draft"),
63
- rolloutPercent: C().default(0),
64
- rolloutSalt: D(),
65
- cohorts: w().nullable(),
66
- createdBy: D().nullable(),
67
- createdAt: O().default("now"),
68
- updatedAt: O().default("now").onUpdate("now")
69
- }).index("byReleaseAppChannel", ["appId", "channelId"]).index("byReleaseVersion", ["appId", "version"]), P = E(l, {
70
- id: S({ prefix: "relart" }),
71
- releaseId: D(),
72
- kind: D(),
73
- target: D().nullable(),
74
- url: D(),
75
- sha256: D(),
76
- size: C(),
77
- signature: D().nullable(),
78
- notarized: b().default(!1),
79
- assetKey: D().nullable(),
80
- contentType: D().nullable(),
81
- createdAt: O().default("now")
82
- }).index("byReleaseArtifactRelease", ["releaseId"]), F = E(g, {
83
- id: S({ prefix: "relev" }),
84
- appId: D(),
85
- channelId: D().nullable(),
86
- releaseId: D().nullable(),
87
- kind: D(),
88
- reason: D().nullable(),
89
- actorId: D().nullable(),
90
- detail: w().nullable(),
91
- at: O().default("now")
92
- }).index("byReleaseEventApp", ["appId", "at"]), I = [
93
- j,
94
- M,
95
- N,
96
- P,
97
- F
98
- ], L = (e, t) => t.currentVersion !== null && c(e.version, t.currentVersion) <= 0 ? !1 : o({
99
- installId: t.installId,
100
- salt: e.rolloutSalt,
101
- percent: e.rolloutPercent,
102
- ...t.cohorts === void 0 ? {} : { installCohorts: t.cohorts },
103
- releaseCohorts: e.cohorts
104
- }), R = (e, t) => {
105
- if (!L(e, t)) return null;
106
- let n = e.artifacts.find((e) => e.target === t.target && e.signature !== null);
107
- return !n || n.signature === null ? null : {
108
- version: e.version,
109
- notes: e.notes ?? "",
110
- pub_date: e.createdAt.toISOString(),
111
- platforms: { [t.target]: {
112
- signature: n.signature,
113
- url: n.url
114
- } }
115
- };
116
- }, z = (e, t, n = {}) => {
117
- let r = {
118
- win: [
119
- "nsis",
120
- "msi",
121
- "exe"
122
- ],
123
- mac: ["dmg", "zip"],
124
- linux: [
125
- "appimage",
126
- "deb",
127
- "rpm"
128
- ]
129
- }, i = e.artifacts.filter((e) => r[t].includes(e.kind));
130
- if (i.length === 0) return null;
131
- let a = [`version: ${e.version}`, "files:"];
132
- for (let e of i) {
133
- a.push(` - url: ${e.url}`);
134
- let t = n[e.sha256];
135
- a.push(t === void 0 ? ` sha256: ${e.sha256}` : ` sha512: ${t}`), a.push(` size: ${e.size}`);
136
- }
137
- let o = i[0];
138
- a.push(`path: ${o.url}`);
139
- let s = n[o.sha256];
140
- return a.push(s === void 0 ? `sha256: ${o.sha256}` : `sha512: ${s}`), a.push(`releaseDate: '${e.createdAt.toISOString()}'`), e.notes && a.push(`releaseNotes: ${JSON.stringify(e.notes)}`), a.join("\n") + "\n";
141
- }, B = (e, t, n) => {
142
- if (n.rolledBackTo !== null) return {
143
- kind: "rollback",
144
- commitTime: n.rolledBackTo.commitTime.toISOString()
145
- };
146
- if (e === null || n.runtimeVersion !== null && n.runtimeVersion !== t.runtimeVersion) return { kind: "no-update" };
147
- let r = e.buildId ?? e.id;
148
- if (t.currentUpdateId === r || !L(e, {
149
- installId: t.installId,
150
- currentVersion: null,
151
- ...t.cohorts === void 0 ? {} : { cohorts: t.cohorts }
152
- })) return { kind: "no-update" };
153
- let i = e.artifacts.find((e) => e.kind === "expo-bundle" && e.target === t.platform);
154
- if (!i) return { kind: "no-update" };
155
- let a = e.artifacts.filter((e) => e.kind === "expo-asset" && (e.target === null || e.target === t.platform)).map((e) => ({
156
- hash: e.sha256,
157
- key: e.assetKey ?? e.sha256,
158
- contentType: e.contentType ?? "application/octet-stream",
159
- url: e.url
160
- }));
161
- return {
162
- kind: "manifest",
163
- manifest: {
164
- id: r,
165
- createdAt: e.createdAt.toISOString(),
166
- runtimeVersion: t.runtimeVersion,
167
- launchAsset: {
168
- hash: i.sha256,
169
- key: i.assetKey ?? "bundle",
170
- contentType: i.contentType ?? "application/javascript",
171
- url: i.url
172
- },
173
- assets: a,
174
- metadata: {},
175
- extra: {
176
- version: e.version,
177
- ...e.notes ? { notes: e.notes } : {}
178
- }
179
- }
180
- };
181
- }, V = {
182
- macos: ["dmg", "zip"],
183
- windows: [
184
- "nsis",
185
- "msi",
186
- "exe"
187
- ],
188
- linux: [
189
- "appimage",
190
- "deb",
191
- "rpm"
192
- ],
193
- ios: ["ipa"],
194
- android: ["apk", "aab"]
195
- }, _e = (e, t, n) => {
196
- let r = V[t] ?? [], i = e.artifacts.filter((e) => r.includes(e.kind));
197
- if (i.length === 0) return null;
198
- if (n !== void 0) {
199
- let e = i.find((e) => e.target !== null && e.target.endsWith(n));
200
- if (e) return e;
201
- }
202
- return i[0];
203
- }, H = (e) => /Windows/i.test(e) ? {
204
- os: "windows",
205
- arch: /ARM64|aarch64/i.test(e) ? "aarch64" : "x86_64"
206
- } : /Macintosh|Mac OS X/i.test(e) ? { os: "macos" } : /Android/i.test(e) ? { os: "android" } : /iPhone|iPad|iPod/i.test(e) ? { os: "ios" } : /Linux/i.test(e) ? {
207
- os: "linux",
208
- arch: /aarch64|arm64/i.test(e) ? "aarch64" : "x86_64"
209
- } : null, U = (e, t, n = {}) => ({
1
+ import { artifactMetadataIssue as e, artifactOperationId as t, artifactStoredSize as n, hasArtifactSignature as r } from "./artifactMetadata.js";
2
+ import { matchesUploadArtifact as i, uploadArtifactId as a } from "./uploadIdentity.server.js";
3
+ import { ReleaseNotFound as o, ReleaseRefused as s } from "./errors.js";
4
+ import { compareVersions as c, evaluatePolicy as l, inRollout as u, isNewerVersion as d, parseVersion as f, rolloutBucket as p, sortVersionsDesc as ee } from "./rollout.js";
5
+ import { planRetention as te } from "./retention.js";
6
+ import { RELEASES_TABLE as m, RELEASE_APPS_TABLE as ne, RELEASE_ARTIFACTS_TABLE as h, RELEASE_CHANNELS_TABLE as g, RELEASE_EVENTS_TABLE as _ } from "./names.js";
7
+ import { BASE_NAME as v, RELEASES_MANAGE_SCOPE as y, createDescriptor as re, listDescriptor as ie, policyDescriptor as ae, prepareUploadDescriptor as oe, promoteDescriptor as se, registerAppDescriptor as ce, releasesRpcClientImports as le, rollbackDescriptor as ue, rolloutDescriptor as de, setPolicyDescriptor as fe, uploadDescriptor as pe } from "./rpc.js";
8
+ import { RELEASE_PLATFORMS as b, RELEASE_SHELLS as x, releaseAppsTable as S, releaseArtifactsTable as C, releaseChannelsTable as w, releaseEventsTable as me, releaseTables as he, releasesTable as T } from "./schema.js";
9
+ import { offeredTo as ge, osFromUserAgent as _e, pickDownload as ve, renderElectronLatestYml as ye, renderExpoFeed as be, renderTauriFeed as xe } from "./feeds.js";
10
+ import { Effect as E, Result as D, Schema as Se } from "effect";
11
+ import { randomBytes as Ce } from "node:crypto";
12
+ import { AsyncLocalStorage as we } from "node:async_hooks";
13
+ import { definePlugin as Te } from "@voltro/protocol";
14
+ import { and as Ee, eq as O, inSet as De, queryFor as k, registerRetention as Oe, retentionTtlMsFromEnv as ke } from "@voltro/database";
15
+ import { newTraceId as Ae } from "@voltro/runtime";
16
+ //#region src/index.ts
17
+ var A = (e, t, n = {}) => ({
210
18
  status: e,
211
19
  body: JSON.stringify(t),
212
20
  contentType: "application/json; charset=utf-8",
@@ -214,25 +22,25 @@ var ge = (e, t, n) => {
214
22
  "cache-control": "no-store",
215
23
  ...n
216
24
  }
217
- }), ve = (e, t, n) => ({
25
+ }), je = (e, t, n) => ({
218
26
  status: e,
219
27
  body: t,
220
28
  contentType: n,
221
29
  headers: { "cache-control": "no-store" }
222
- }), W = (e) => ({
30
+ }), j = (e) => ({
223
31
  status: e,
224
32
  body: "",
225
33
  headers: { "cache-control": "no-store" }
226
- }), G = (e) => `${e}_${v(9).toString("base64url")}`, ye = (n) => {
227
- let r = (n.prefix ?? "/releases").replace(/\/+$/, ""), a = n.now ?? Date.now, o, s = new y(), p = new y(), v = () => {
228
- let e = s.getStore();
34
+ }), M = (e) => `${e}_${Ce(9).toString("base64url")}`, N = (u) => {
35
+ let d = (u.prefix ?? "/releases").replace(/\/+$/, ""), f = u.now ?? Date.now, p, ee = new we(), y = new we(), b = () => {
36
+ let e = ee.getStore();
229
37
  if (e) return e;
230
- if (!o) throw new t({ reason: "releases: the app store is not bound yet" });
231
- return o;
232
- }, b = null, S = async (e) => {
233
- let t = (await v().query(T(j).where(x("app", e.app)).descriptor)).find((t) => t.platform === e.platform && t.shell === e.shell), n = !t;
234
- t ||= await v().insert(d, {
235
- id: G("relapp"),
38
+ if (!p) throw new s({ reason: "releases: the app store is not bound yet" });
39
+ return p;
40
+ }, x = null, ge = async (e) => {
41
+ let t = (await b().query(k(S).where(O("app", e.app)).descriptor)).find((t) => t.platform === e.platform && t.shell === e.shell), n = !t;
42
+ t ||= await b().insert(ne, {
43
+ id: M("relapp"),
236
44
  app: e.app,
237
45
  platform: e.platform,
238
46
  shell: e.shell,
@@ -240,15 +48,15 @@ var ge = (e, t, n) => {
240
48
  bundleId: e.bundleId ?? null,
241
49
  updaterPublicKey: e.updaterPublicKey ?? null,
242
50
  runtimeVersion: e.runtimeVersion ?? null,
243
- createdAt: new Date(a())
51
+ createdAt: new Date(f())
244
52
  });
245
- let r = await v().query(T(M).where(x("appId", t.id)).descriptor);
53
+ let r = await b().query(k(w).where(O("appId", t.id)).descriptor);
246
54
  for (let n of e.channels ?? [
247
55
  "stable",
248
56
  "beta",
249
57
  "internal"
250
- ]) r.some((e) => e.name === n) || await v().insert(h, {
251
- id: G("relch"),
58
+ ]) r.some((e) => e.name === n) || await b().insert(g, {
59
+ id: M("relch"),
252
60
  appId: t.id,
253
61
  name: n,
254
62
  currentReleaseId: null,
@@ -256,40 +64,43 @@ var ge = (e, t, n) => {
256
64
  killSwitches: null,
257
65
  public: n === "stable",
258
66
  allowUnsigned: n === "internal",
259
- updatedAt: new Date(a())
67
+ updatedAt: new Date(f())
260
68
  });
261
69
  return {
262
70
  row: t,
263
71
  created: n
264
72
  };
265
- }, C = async () => {
266
- for (let e of n.apps) await S(e);
267
- }, w = () => b ?? Promise.resolve(), E = (t, n, r) => _.tryPromise({
73
+ }, Ce = async () => {
74
+ for (let e of u.apps) await ge(e);
75
+ }, N = () => x ?? Promise.resolve(), P = (e, t, n) => E.tryPromise({
268
76
  try: async () => {
269
- await w();
270
- let i = await v().query(T(j).where(x("app", t)).descriptor), a = p.getStore(), o = i.find((e) => (a === void 0 || e.id === a) && (n === void 0 || e.platform === n) && (r === void 0 || e.shell === r));
271
- if (!o) throw new e({ app: t });
272
- return o;
77
+ await N();
78
+ let r = await b().query(k(S).where(O("app", e)).descriptor), i = y.getStore(), a = r.find((e) => (i === void 0 || e.id === i) && (t === void 0 || e.platform === t) && (n === void 0 || e.shell === n));
79
+ if (!a) throw new o({ app: e });
80
+ return a;
273
81
  },
274
- catch: (n) => n instanceof e ? n : new e({ app: t })
275
- }), D = (t, n, r) => _.tryPromise({
82
+ catch: (t) => t instanceof o ? t : new o({ app: e })
83
+ }), F = (e, t, n) => E.tryPromise({
276
84
  try: async () => {
277
- let i = (await v().query(T(M).where(x("appId", t)).descriptor)).find((e) => e.name === n);
278
- if (!i) throw new e({
279
- app: r,
280
- channel: n
85
+ let r = (await b().query(k(w).where(O("appId", e)).descriptor)).find((e) => e.name === t);
86
+ if (!r) throw new o({
87
+ app: n,
88
+ channel: t
281
89
  });
282
- return i;
90
+ return r;
283
91
  },
284
- catch: (t) => t instanceof e ? t : new e({
285
- app: r,
286
- channel: n
92
+ catch: (e) => e instanceof o ? e : new o({
93
+ app: n,
94
+ channel: t
287
95
  })
288
- }), O = (e) => v().query(T(N).where(x("id", e)).descriptor).then((e) => e[0] ?? null), k = (e) => v().query(T(N).where(x("appId", e)).descriptor), A = (e) => v().query(T(P).where(x("releaseId", e)).descriptor), L = (e) => v().query(T(M).where(x("appId", e)).descriptor), V = (e) => n.retention?.perChannel?.[e] ?? n.retention?.keep ?? 10, ye = async (e, t) => {
289
- let n = await L(e), r = ge(await k(e), n, V);
96
+ }), I = (e, t = !1) => {
97
+ let n = k(T).where(O("id", e));
98
+ return b().query((t ? n.forUpdate() : n).descriptor).then((e) => e[0] ?? null);
99
+ }, L = (e) => b().query(k(T).where(O("appId", e)).descriptor), Pe = (e) => b().query(k(C).where(O("releaseId", e)).descriptor), Fe = (e) => b().query(k(w).where(O("appId", e)).descriptor), Ie = (e) => u.retention?.perChannel?.[e] ?? u.retention?.keep ?? 10, Le = async (e, t) => {
100
+ let n = await Fe(e), r = te(await L(e), n, Ie);
290
101
  if (r.length === 0) return [];
291
- for (let e of r) await v().deleteMany(l, { where: x("releaseId", e.id) }), await v().delete(f, e.id);
292
- return await q({
102
+ for (let e of r) await b().deleteMany(h, { where: O("releaseId", e.id) }), await b().delete(m, e.id);
103
+ return await z({
293
104
  appId: e,
294
105
  kind: "prune",
295
106
  actorId: t,
@@ -298,8 +109,8 @@ var ge = (e, t, n) => {
298
109
  channel: n.find((t) => t.id === e.channelId)?.name ?? null
299
110
  })) }
300
111
  }), r;
301
- }, Se = async (e) => {
302
- let t = await A(e.id);
112
+ }, Re = async (e) => {
113
+ let t = await Pe(e.id);
303
114
  return {
304
115
  id: e.id,
305
116
  version: e.version,
@@ -314,14 +125,14 @@ var ge = (e, t, n) => {
314
125
  target: e.target,
315
126
  url: e.url,
316
127
  sha256: e.sha256,
317
- size: e.size,
128
+ size: n(e.size),
318
129
  signature: e.signature,
319
130
  assetKey: e.assetKey,
320
131
  contentType: e.contentType
321
132
  }))
322
133
  };
323
- }, K = async (e, t) => {
324
- let n = await _.runPromise(E(e)), r = await _.runPromise(D(n.id, t, e)), i = r.currentReleaseId ? await O(r.currentReleaseId) : null;
134
+ }, R = async (e, t) => {
135
+ let n = await E.runPromise(P(e)), r = await E.runPromise(F(n.id, t, e)), i = r.currentReleaseId ? await I(r.currentReleaseId) : null;
325
136
  return {
326
137
  app: e,
327
138
  channel: t,
@@ -332,8 +143,8 @@ var ge = (e, t, n) => {
332
143
  rolloutSalt: i?.rolloutSalt ?? null,
333
144
  cohorts: i?.cohorts ?? null
334
145
  };
335
- }, q = (e) => v().insert(g, {
336
- id: G("relev"),
146
+ }, z = (e) => b().insert(_, {
147
+ id: M("relev"),
337
148
  appId: e.appId,
338
149
  channelId: e.channelId ?? null,
339
150
  releaseId: e.releaseId ?? null,
@@ -341,42 +152,55 @@ var ge = (e, t, n) => {
341
152
  reason: e.reason ?? null,
342
153
  actorId: e.actorId ?? null,
343
154
  detail: e.detail ?? null,
344
- at: new Date(a())
345
- }), J = (e) => e.request.subject.type === "user" ? e.request.subject.id : null, Ce = {
346
- ...se,
155
+ at: new Date(f())
156
+ }), B = (e) => e.request.subject.type === "user" ? e.request.subject.id : null, ze = {
157
+ ...ae,
347
158
  description: "The reactive release policy of a channel: latest version, minimum supported version, kill switches, rollout.",
348
- execute: (e) => _.tryPromise({
349
- try: () => K(e.app, e.channel),
159
+ execute: (e) => E.tryPromise({
160
+ try: () => R(e.app, e.channel),
350
161
  catch: (e) => e
351
162
  })
352
- }, we = {
353
- ...ae,
163
+ }, Be = {
164
+ ...ie,
354
165
  description: "Every release of an app target, newest first.",
355
- execute: (e) => _.gen(function* () {
356
- let t = yield* E(e.app), n = yield* _.promise(() => k(t.id)), r = yield* _.promise(() => v().query(T(M).where(x("appId", t.id)).descriptor)), i = [];
166
+ execute: (e) => E.gen(function* () {
167
+ let t = yield* P(e.app), n = yield* E.promise(() => L(t.id)), i = yield* E.promise(() => b().query(k(w).where(O("appId", t.id)).descriptor)), a = /* @__PURE__ */ new Map();
168
+ for (let e of i) a.has(e.id) || a.set(e.id, e.name);
169
+ let o = /* @__PURE__ */ new Map(), s = [...new Set(n.map((e) => e.id))];
170
+ for (let e = 0; e < s.length; e += 500) {
171
+ let t = yield* E.promise(() => b().query(k(C).where(De("releaseId", s.slice(e, e + 500))).descriptor));
172
+ for (let e of t) {
173
+ let t = o.get(e.releaseId) ?? {
174
+ count: 0,
175
+ signed: !0
176
+ };
177
+ t.count++, t.signed &&= r(e.signature), o.set(e.releaseId, t);
178
+ }
179
+ }
180
+ let l = [];
357
181
  for (let e of [...n].sort((e, t) => t.createdAt.getTime() - e.createdAt.getTime() || c(t.version, e.version))) {
358
- let t = yield* _.promise(() => A(e.id));
359
- i.push({
182
+ let t = o.get(e.id);
183
+ l.push({
360
184
  id: e.id,
361
185
  version: e.version,
362
186
  buildId: e.buildId,
363
187
  state: e.state,
364
188
  rolloutPercent: e.rolloutPercent,
365
- channel: r.find((t) => t.id === e.channelId)?.name ?? "?",
189
+ channel: a.get(e.channelId) ?? "?",
366
190
  createdAt: e.createdAt.toISOString(),
367
- artifacts: t.length,
368
- signed: t.length > 0 && t.every((e) => e.signature !== null)
191
+ artifacts: t?.count ?? 0,
192
+ signed: t !== void 0 && t.count > 0 && t.signed
369
193
  });
370
194
  }
371
- return i;
195
+ return l;
372
196
  })
373
- }, Te = {
374
- ...re,
197
+ }, V = {
198
+ ...ce,
375
199
  description: "Register an app target at runtime, with its channels; idempotent.",
376
- execute: (e) => _.tryPromise({
200
+ execute: (e) => E.tryPromise({
377
201
  try: async () => {
378
- await w();
379
- let { row: t, created: n } = await S({
202
+ await N();
203
+ let { row: t, created: n } = await ge({
380
204
  app: e.app,
381
205
  platform: e.platform,
382
206
  shell: e.shell,
@@ -390,255 +214,297 @@ var ge = (e, t, n) => {
390
214
  created: n
391
215
  };
392
216
  },
393
- catch: (n) => new t({ reason: `releases: ${e.app} could not be registered: ${n instanceof Error ? n.message : String(n)}` })
217
+ catch: (t) => new s({ reason: `releases: ${e.app} could not be registered: ${t instanceof Error ? t.message : String(t)}` })
394
218
  })
395
- }, Ee = {
396
- ...u,
219
+ }, H = {
220
+ ...re,
397
221
  description: "Create a draft release of a version on a channel.",
398
- execute: (e, n) => _.gen(function* () {
399
- let r = yield* E(e.app), i = yield* D(r.id, e.channel, e.app);
400
- if ((yield* _.promise(() => k(r.id))).some((t) => t.version === e.version)) return yield* _.fail(new t({ reason: `version ${e.version} already exists for ${e.app}` }));
401
- let o = G("rel");
402
- return yield* _.promise(() => v().insert(f, {
403
- id: o,
404
- appId: r.id,
405
- channelId: i.id,
222
+ execute: (e, t) => E.gen(function* () {
223
+ let n = yield* P(e.app), r = yield* F(n.id, e.channel, e.app);
224
+ if ((yield* E.promise(() => L(n.id))).some((t) => t.version === e.version)) return yield* E.fail(new s({ reason: `version ${e.version} already exists for ${e.app}` }));
225
+ let i = M("rel");
226
+ return yield* E.promise(() => b().insert(m, {
227
+ id: i,
228
+ appId: n.id,
229
+ channelId: r.id,
406
230
  version: e.version,
407
231
  buildId: e.buildId ?? null,
408
232
  notes: e.notes ?? null,
409
233
  state: "draft",
410
234
  rolloutPercent: 0,
411
- rolloutSalt: G("salt"),
235
+ rolloutSalt: M("salt"),
412
236
  cohorts: e.cohorts ?? null,
413
- createdBy: J(n),
414
- createdAt: new Date(a()),
415
- updatedAt: new Date(a())
416
- })), yield* _.promise(() => q({
417
- appId: r.id,
418
- channelId: i.id,
419
- releaseId: o,
237
+ createdBy: B(t),
238
+ createdAt: new Date(f()),
239
+ updatedAt: new Date(f())
240
+ })), yield* E.promise(() => z({
241
+ appId: n.id,
242
+ channelId: r.id,
243
+ releaseId: i,
420
244
  kind: "create",
421
- actorId: J(n),
245
+ actorId: B(t),
422
246
  detail: { version: e.version }
423
- })), yield* _.promise(() => ye(r.id, J(n))), { id: o };
247
+ })), yield* E.promise(() => Le(n.id, B(t))), { id: i };
424
248
  })
425
- }, De = {
426
- ...ie,
249
+ }, Ve = async (e, n) => {
250
+ if (n.operationId !== void 0 && !t(n.operationId)) throw new s({ reason: "Invalid artifact operationId." });
251
+ let r = (await e.query(k(T).where(O("id", n.releaseId)).descriptor))[0];
252
+ if (!r) throw new o({
253
+ app: "?",
254
+ version: n.releaseId
255
+ });
256
+ let i = (await e.query(k(S).where(O("id", r.appId)).forUpdate().descriptor))[0], c = (await e.query(k(T).where(O("id", n.releaseId)).forUpdate().descriptor))[0];
257
+ if (!i || !c || n.appPrefix !== void 0 && !i.app.startsWith(n.appPrefix)) throw new o({
258
+ app: "?",
259
+ version: n.releaseId
260
+ });
261
+ if (c.appId !== i.id || n.app !== void 0 && n.app !== i.app || n.version !== void 0 && n.version !== c.version) throw new s({ reason: "Release does not match requested upload destination." });
262
+ let l = n.operationId === void 0 ? void 0 : (await e.query(k(C).where(O("id", a(n.releaseId, n.operationId))).forUpdate().descriptor))[0];
263
+ if (l && l.releaseId !== c.id) throw new s({ reason: "Upload operation does not match the current release." });
264
+ let u = n.expectedTarget;
265
+ if (u !== void 0 && (u.id !== i.id || !Number.isSafeInteger(u.revision) || u.revision < 0 || u.revision >= 2 ** 53 - 1 || !l && u.revision !== i.revision)) throw new s({ reason: "Release target changed. Refresh and review the action again." });
266
+ return {
267
+ release: c,
268
+ target: i,
269
+ completed: l
270
+ };
271
+ }, U = {
272
+ ...oe,
273
+ description: "Resolve a release upload destination before transferring bytes; does not reserve publication.",
274
+ execute: (e, t) => E.tryPromise({
275
+ try: async () => {
276
+ await N();
277
+ let n = async (t) => {
278
+ let { release: n, target: r } = await Ve(t, e);
279
+ return {
280
+ releaseId: n.id,
281
+ app: r.app,
282
+ version: n.version
283
+ };
284
+ };
285
+ return t.mutationStore ? n(t.mutationStore) : b().transactional(n);
286
+ },
287
+ catch: (e) => e
288
+ })
289
+ }, W = {
290
+ ...pe,
427
291
  description: "Attach an artefact (a file the client downloads) to a release.",
428
- execute: (n, r) => _.gen(function* () {
429
- let i = yield* _.promise(() => O(n.releaseId));
430
- if (!i) return yield* _.fail(new e({
292
+ execute: (t, n) => E.gen(function* () {
293
+ let r = yield* E.promise(() => I(t.releaseId));
294
+ if (!r) return yield* E.fail(new o({
431
295
  app: "?",
432
- version: n.releaseId
296
+ version: t.releaseId
433
297
  }));
434
- if (!/^[a-f0-9]{64}$/i.test(n.sha256)) return yield* _.fail(new t({ reason: "sha256 must be 64 hex characters" }));
435
- let o = G("relart");
436
- return yield* _.promise(() => v().insert(l, {
437
- id: o,
438
- releaseId: i.id,
439
- kind: n.kind,
440
- target: n.target ?? null,
441
- url: n.url,
442
- sha256: n.sha256.toLowerCase(),
443
- size: n.size,
444
- signature: n.signature ?? null,
445
- notarized: n.notarized ?? !1,
446
- assetKey: n.assetKey ?? null,
447
- contentType: n.contentType ?? null,
448
- createdAt: new Date(a())
449
- })), yield* _.promise(() => q({
450
- appId: i.appId,
451
- releaseId: i.id,
298
+ if (t.version !== void 0 && t.version !== r.version) return yield* E.fail(new s({ reason: "Release does not match the requested upload version." }));
299
+ let i = e(t);
300
+ if (i !== void 0) return yield* E.fail(new s({ reason: i }));
301
+ let c = t.operationId === void 0 ? M("relart") : a(t.releaseId, t.operationId);
302
+ return yield* E.promise(() => b().insert(h, {
303
+ id: c,
304
+ releaseId: r.id,
305
+ kind: t.kind,
306
+ target: t.target ?? null,
307
+ url: t.url,
308
+ sha256: t.sha256.toLowerCase(),
309
+ size: String(t.size),
310
+ signature: t.signature ?? null,
311
+ notarized: t.notarized ?? !1,
312
+ assetKey: t.assetKey ?? null,
313
+ contentType: t.contentType ?? null,
314
+ createdAt: new Date(f())
315
+ })), yield* E.promise(() => z({
316
+ appId: r.appId,
317
+ releaseId: r.id,
452
318
  kind: "upload",
453
- actorId: J(r),
319
+ actorId: B(n),
454
320
  detail: {
455
- kind: n.kind,
456
- target: n.target ?? null
321
+ kind: t.kind,
322
+ target: t.target ?? null
457
323
  }
458
- })), { id: o };
324
+ })), { id: c };
459
325
  })
460
- }, Oe = {
461
- ...ee,
326
+ }, G = {
327
+ ...se,
462
328
  description: "Point a channel at a release (no rebuild) and start its rollout.",
463
- execute: (n, r) => _.gen(function* () {
464
- let i = yield* E(n.app), o = yield* D(i.id, n.to, n.app), s = yield* _.promise(() => k(i.id)), c = s.find((e) => e.version === n.version);
465
- if (!c) return yield* _.fail(new e({
466
- app: n.app,
467
- version: n.version
329
+ execute: (e, t) => E.gen(function* () {
330
+ let n = yield* P(e.app), i = yield* F(n.id, e.to, e.app), a = yield* E.promise(() => L(n.id)), c = a.find((t) => t.version === e.version);
331
+ if (!c) return yield* E.fail(new o({
332
+ app: e.app,
333
+ version: e.version
468
334
  }));
469
- let l = yield* _.promise(() => A(c.id));
470
- if (!o.allowUnsigned && l.some((e) => e.signature === null)) return yield* _.fail(new t({ reason: `release ${n.version} has unsigned artefacts; channel ${n.to} refuses them` }));
471
- let u = Math.max(0, Math.min(100, n.rolloutPercent ?? 100));
472
- return yield* _.promise(() => v().update(f, c.id, {
473
- channelId: o.id,
335
+ let l = yield* E.promise(() => Pe(c.id));
336
+ if (!i.allowUnsigned && l.some((e) => !r(e.signature))) return yield* E.fail(new s({ reason: `release ${e.version} has unsigned artefacts; channel ${e.to} refuses them` }));
337
+ let u = Math.max(0, Math.min(100, e.rolloutPercent ?? 100));
338
+ return yield* E.promise(() => b().update(m, c.id, {
339
+ channelId: i.id,
474
340
  state: u >= 100 ? "complete" : "rolling",
475
341
  rolloutPercent: u,
476
- updatedAt: new Date(a())
477
- })), yield* _.promise(() => v().update(h, o.id, {
342
+ updatedAt: new Date(f())
343
+ })), yield* E.promise(() => b().update(g, i.id, {
478
344
  currentReleaseId: c.id,
479
- updatedAt: new Date(a())
480
- })), yield* _.promise(() => q({
481
- appId: i.id,
482
- channelId: o.id,
345
+ updatedAt: new Date(f())
346
+ })), yield* E.promise(() => z({
347
+ appId: n.id,
348
+ channelId: i.id,
483
349
  releaseId: c.id,
484
350
  kind: "promote",
485
- actorId: J(r),
351
+ actorId: B(t),
486
352
  detail: {
487
- from: s.find((e) => e.id === o.currentReleaseId)?.version ?? null,
488
- to: n.version,
353
+ from: a.find((e) => e.id === i.currentReleaseId)?.version ?? null,
354
+ to: e.version,
489
355
  rolloutPercent: u
490
356
  }
491
357
  })), {
492
- channel: n.to,
493
- version: n.version,
358
+ channel: e.to,
359
+ version: e.version,
494
360
  rolloutPercent: u
495
361
  };
496
362
  })
497
- }, ke = {
498
- ...ne,
363
+ }, K = {
364
+ ...de,
499
365
  description: "Change the rollout percentage of the channel's current release.",
500
- execute: (n, r) => _.gen(function* () {
501
- let i = yield* E(n.app), o = yield* D(i.id, n.channel, n.app);
502
- if (!o.currentReleaseId) return yield* _.fail(new t({ reason: `channel ${n.channel} has no current release` }));
503
- let s = yield* _.promise(() => O(o.currentReleaseId));
504
- if (!s) return yield* _.fail(new e({
505
- app: n.app,
506
- channel: n.channel
366
+ execute: (e, t) => E.gen(function* () {
367
+ let n = yield* P(e.app), r = yield* F(n.id, e.channel, e.app);
368
+ if (!r.currentReleaseId) return yield* E.fail(new s({ reason: `channel ${e.channel} has no current release` }));
369
+ let i = yield* E.promise(() => I(r.currentReleaseId));
370
+ if (!i) return yield* E.fail(new o({
371
+ app: e.app,
372
+ channel: e.channel
507
373
  }));
508
- let c = Math.max(0, Math.min(100, n.percent));
509
- return yield* _.promise(() => v().update(f, s.id, {
510
- rolloutPercent: c,
511
- state: c >= 100 ? "complete" : "rolling",
512
- updatedAt: new Date(a())
513
- })), yield* _.promise(() => q({
514
- appId: i.id,
515
- channelId: o.id,
516
- releaseId: s.id,
374
+ let a = Math.max(0, Math.min(100, e.percent));
375
+ return yield* E.promise(() => b().update(m, i.id, {
376
+ rolloutPercent: a,
377
+ state: a >= 100 ? "complete" : "rolling",
378
+ updatedAt: new Date(f())
379
+ })), yield* E.promise(() => z({
380
+ appId: n.id,
381
+ channelId: r.id,
382
+ releaseId: i.id,
517
383
  kind: "rollout",
518
- actorId: J(r),
519
- detail: { percent: c }
384
+ actorId: B(t),
385
+ detail: { percent: a }
520
386
  })), {
521
- version: s.version,
522
- rolloutPercent: c
387
+ version: i.version,
388
+ rolloutPercent: a
523
389
  };
524
390
  })
525
- }, Ae = {
526
- ...te,
391
+ }, q = {
392
+ ...ue,
527
393
  description: "Point the channel back at the previous release; the current one is marked rolled back.",
528
- execute: (e, n) => _.gen(function* () {
529
- let r = yield* E(e.app), i = yield* D(r.id, e.channel, e.app);
530
- if (!i.currentReleaseId) return yield* _.fail(new t({ reason: `channel ${e.channel} has no current release` }));
531
- let o = yield* _.promise(() => k(r.id)), s = o.find((e) => e.id === i.currentReleaseId), l = o.filter((e) => e.channelId === i.id && e.id !== i.currentReleaseId && e.state !== "rolledBack").sort((e, t) => c(t.version, e.version))[0];
532
- return s && (yield* _.promise(() => v().update(f, s.id, {
394
+ execute: (e, t) => E.gen(function* () {
395
+ let n = yield* P(e.app), r = yield* F(n.id, e.channel, e.app);
396
+ if (!r.currentReleaseId) return yield* E.fail(new s({ reason: `channel ${e.channel} has no current release` }));
397
+ let i = yield* E.promise(() => L(n.id)), a = i.find((e) => e.id === r.currentReleaseId), o = i.filter((e) => e.channelId === r.id && e.id !== r.currentReleaseId && e.state !== "rolledBack").sort((e, t) => c(t.version, e.version))[0];
398
+ return a && (yield* E.promise(() => b().update(m, a.id, {
533
399
  state: "rolledBack",
534
- updatedAt: new Date(a())
535
- }))), yield* _.promise(() => v().update(h, i.id, {
536
- currentReleaseId: l?.id ?? null,
537
- updatedAt: new Date(a())
538
- })), yield* _.promise(() => q({
539
- appId: r.id,
540
- channelId: i.id,
541
- releaseId: s?.id ?? null,
400
+ updatedAt: new Date(f())
401
+ }))), yield* E.promise(() => b().update(g, r.id, {
402
+ currentReleaseId: o?.id ?? null,
403
+ updatedAt: new Date(f())
404
+ })), yield* E.promise(() => z({
405
+ appId: n.id,
406
+ channelId: r.id,
407
+ releaseId: a?.id ?? null,
542
408
  kind: "rollback",
543
409
  reason: e.reason,
544
- actorId: J(n),
410
+ actorId: B(t),
545
411
  detail: {
546
- from: s?.version ?? null,
547
- to: l?.version ?? null
412
+ from: a?.version ?? null,
413
+ to: o?.version ?? null
548
414
  }
549
- })), { version: l?.version ?? null };
415
+ })), { version: o?.version ?? null };
550
416
  })
551
- }, je = {
552
- ...oe,
417
+ }, J = {
418
+ ...fe,
553
419
  description: "Set the minimum supported version and kill switches of a channel.",
554
- execute: (e, t) => _.gen(function* () {
555
- let n = yield* E(e.app), r = yield* D(n.id, e.channel, e.app);
556
- return yield* _.promise(() => v().update(h, r.id, {
420
+ execute: (e, t) => E.gen(function* () {
421
+ let n = yield* P(e.app), r = yield* F(n.id, e.channel, e.app);
422
+ return yield* E.promise(() => b().update(g, r.id, {
557
423
  ...e.minSupportedVersion === void 0 ? {} : { minSupportedVersion: e.minSupportedVersion },
558
424
  ...e.killSwitches === void 0 ? {} : { killSwitches: e.killSwitches },
559
- updatedAt: new Date(a())
560
- })), yield* _.promise(() => q({
425
+ updatedAt: new Date(f())
426
+ })), yield* E.promise(() => z({
561
427
  appId: n.id,
562
428
  channelId: r.id,
563
429
  kind: "policy",
564
430
  reason: e.reason,
565
- actorId: J(t),
431
+ actorId: B(t),
566
432
  detail: {
567
433
  minSupportedVersion: e.minSupportedVersion ?? null,
568
434
  killSwitches: e.killSwitches ?? null
569
435
  }
570
- })), yield* _.promise(() => K(e.app, e.channel));
436
+ })), yield* E.promise(() => R(e.app, e.channel));
571
437
  })
572
438
  }, Y = async (e, t) => {
573
- let n = await _.runPromise(E(e)), r = await _.runPromise(D(n.id, t, e)), i = r.currentReleaseId ? await O(r.currentReleaseId) : null;
439
+ let n = await E.runPromise(P(e)), r = await E.runPromise(F(n.id, t, e)), i = r.currentReleaseId ? await I(r.currentReleaseId) : null;
574
440
  return {
575
441
  app: n,
576
442
  channel: r,
577
- release: i ? await Se(i) : null
443
+ release: i ? await Re(i) : null
578
444
  };
579
- }, X = (e, t) => t.get("install") ?? e.headers["x-voltro-install-id"] ?? e.headers["expo-embedded-update-id"] ?? "anonymous", Me = {
445
+ }, X = (e, t) => t.get("install") ?? e.headers["x-voltro-install-id"] ?? e.headers["expo-embedded-update-id"] ?? "anonymous", He = {
580
446
  method: "GET",
581
- path: r,
582
- handle: async (t) => {
583
- if (t.method !== "GET" && t.method !== "HEAD") return U(405, { error: "method_not_allowed" });
584
- let n = t.path.slice(r.length).split("/").filter((e) => e.length > 0).map(decodeURIComponent), a = new URLSearchParams(t.query), [o, s] = n;
585
- if (!o || !s) return U(404, { error: "not_found" });
447
+ path: d,
448
+ handle: async (e) => {
449
+ if (e.method !== "GET" && e.method !== "HEAD") return A(405, { error: "method_not_allowed" });
450
+ let t = e.path.slice(d.length).split("/").filter((e) => e.length > 0).map(decodeURIComponent), n = new URLSearchParams(e.query), [r, i] = t;
451
+ if (!r || !i) return A(404, { error: "not_found" });
586
452
  try {
587
- if (s === "tauri") {
588
- let [, , e, r, i] = n;
589
- if (!e || !r || !i) return U(400, { error: "expected /tauri/:channel/:target/:currentVersion" });
590
- let { release: s } = await Y(o, e), c = s ? R(s, {
591
- installId: X(t, a),
592
- currentVersion: i,
593
- target: r
453
+ if (i === "tauri") {
454
+ let [, , i, a, o] = t;
455
+ if (!i || !a || !o) return A(400, { error: "expected /tauri/:channel/:target/:currentVersion" });
456
+ let { release: s } = await Y(r, i), c = s ? xe(s, {
457
+ installId: X(e, n),
458
+ currentVersion: o,
459
+ target: a
594
460
  }) : null;
595
- return c ? U(200, c) : W(204);
461
+ return c ? A(200, c) : j(204);
596
462
  }
597
- if (s === "expo" && n[2] === "manifest") {
598
- let e = t.headers["expo-channel-name"] ?? a.get("channel") ?? "stable", n = t.headers["expo-platform"] ?? a.get("platform"), r = t.headers["expo-runtime-version"] ?? a.get("runtimeVersion") ?? "";
599
- if (n !== "ios" && n !== "android") return U(400, { error: "expo-platform must be ios or android" });
600
- let { app: i, release: s } = await Y(o, e), c = B(s, {
601
- installId: X(t, a),
463
+ if (i === "expo" && t[2] === "manifest") {
464
+ let t = e.headers["expo-channel-name"] ?? n.get("channel") ?? "stable", i = e.headers["expo-platform"] ?? n.get("platform"), a = e.headers["expo-runtime-version"] ?? n.get("runtimeVersion") ?? "";
465
+ if (i !== "ios" && i !== "android") return A(400, { error: "expo-platform must be ios or android" });
466
+ let { app: o, release: s } = await Y(r, t), c = be(s, {
467
+ installId: X(e, n),
602
468
  currentVersion: null,
603
- platform: n,
604
- runtimeVersion: r,
605
- currentUpdateId: t.headers["expo-current-update-id"] ?? null
469
+ platform: i,
470
+ runtimeVersion: a,
471
+ currentUpdateId: e.headers["expo-current-update-id"] ?? null
606
472
  }, {
607
- runtimeVersion: i.runtimeVersion,
473
+ runtimeVersion: o.runtimeVersion,
608
474
  rolledBackTo: null
609
475
  });
610
- return c.kind === "no-update" ? U(200, { type: "noUpdateAvailable" }, {
476
+ return c.kind === "no-update" ? A(200, { type: "noUpdateAvailable" }, {
611
477
  "expo-protocol-version": "1",
612
478
  "expo-sfv-version": "0"
613
- }) : c.kind === "rollback" ? U(200, {
479
+ }) : c.kind === "rollback" ? A(200, {
614
480
  type: "rollBackToEmbedded",
615
481
  parameters: { commitTime: c.commitTime }
616
482
  }, {
617
483
  "expo-protocol-version": "1",
618
484
  "expo-sfv-version": "0"
619
- }) : U(200, c.manifest, {
485
+ }) : A(200, c.manifest, {
620
486
  "expo-protocol-version": "1",
621
487
  "expo-sfv-version": "0",
622
488
  "expo-manifest-filters": ""
623
489
  });
624
490
  }
625
- if (s === "electron") {
626
- let [, , e, t] = n;
627
- if (!e || !t) return U(400, { error: "expected /electron/:channel/latest.yml" });
628
- let r = t === "latest-mac.yml" ? "mac" : t === "latest-linux.yml" ? "linux" : t === "latest.yml" ? "win" : null;
629
- if (r === null) return U(404, { error: "not_found" });
630
- let { release: i } = await Y(o, e), a = i ? z(i, r) : null;
631
- return a ? ve(200, a, "text/yaml; charset=utf-8") : W(204);
491
+ if (i === "electron") {
492
+ let [, , e, n] = t;
493
+ if (!e || !n) return A(400, { error: "expected /electron/:channel/latest.yml" });
494
+ let i = n === "latest-mac.yml" ? "mac" : n === "latest-linux.yml" ? "linux" : n === "latest.yml" ? "win" : null;
495
+ if (i === null) return A(404, { error: "not_found" });
496
+ let { release: a } = await Y(r, e), o = a ? ye(a, i) : null;
497
+ return o ? je(200, o, "text/yaml; charset=utf-8") : j(204);
632
498
  }
633
- if (s === "download") {
634
- let [, , e] = n;
635
- if (!e) return U(400, { error: "expected /download/:channel" });
636
- let { channel: r, release: i } = await Y(o, e);
637
- if (!r.public) return U(403, { error: "channel_not_public" });
638
- if (!i) return W(204);
639
- let s = a.get("os") ?? H(t.headers["user-agent"] ?? "")?.os, c = a.get("arch") ?? H(t.headers["user-agent"] ?? "")?.arch;
640
- if (!s) return U(400, { error: "os is required (?os=macos|windows|linux|ios|android)" });
641
- let l = _e(i, s, c);
499
+ if (i === "download") {
500
+ let [, , i] = t;
501
+ if (!i) return A(400, { error: "expected /download/:channel" });
502
+ let { channel: a, release: o } = await Y(r, i);
503
+ if (!a.public) return A(403, { error: "channel_not_public" });
504
+ if (!o) return j(204);
505
+ let s = n.get("os") ?? _e(e.headers["user-agent"] ?? "")?.os, c = n.get("arch") ?? _e(e.headers["user-agent"] ?? "")?.arch;
506
+ if (!s) return A(400, { error: "os is required (?os=macos|windows|linux|ios|android)" });
507
+ let l = ve(o, s, c);
642
508
  return l ? {
643
509
  status: 302,
644
510
  body: "",
@@ -646,47 +512,53 @@ var ge = (e, t, n) => {
646
512
  location: l.url,
647
513
  "cache-control": "no-store"
648
514
  }
649
- } : U(404, { error: "no_artifact_for_os" });
515
+ } : A(404, { error: "no_artifact_for_os" });
650
516
  }
651
- if (s === "policy") {
652
- let [, , e] = n;
653
- if (!e) return U(400, { error: "expected /policy/:channel" });
654
- let r = await K(o, e), s = a.get("version"), c = s ? i(r, {
655
- version: s,
656
- installId: X(t, a)
517
+ if (i === "policy") {
518
+ let [, , i] = t;
519
+ if (!i) return A(400, { error: "expected /policy/:channel" });
520
+ let a = await R(r, i), o = n.get("version"), s = o ? l(a, {
521
+ version: o,
522
+ installId: X(e, n)
657
523
  }) : null;
658
- return U(200, {
659
- policy: r,
660
- ...c ? { verdict: c } : {}
524
+ return A(200, {
525
+ policy: a,
526
+ ...s ? { verdict: s } : {}
661
527
  });
662
528
  }
663
- return U(404, { error: "not_found" });
664
- } catch (t) {
665
- if (t instanceof e) return U(404, {
529
+ return A(404, { error: "not_found" });
530
+ } catch (e) {
531
+ if (e instanceof o) return A(404, {
666
532
  error: "not_found",
667
- app: t.app
533
+ app: e.app
668
534
  });
669
- throw t;
535
+ throw e;
670
536
  }
671
537
  }
672
538
  }, Z = (e) => ({
673
539
  kind: "json",
674
540
  data: e
675
- }), Ne = [{
541
+ }), Ue = [{
676
542
  method: "GET",
677
543
  path: "/overview",
678
544
  scope: "shared-store",
679
545
  description: "Every app target with its channels, current versions and rollout state.",
680
- handler: () => _.promise(async () => {
681
- let e = await v().query(T(j).descriptor), t = await v().query(T(M).descriptor), n = await v().query(T(N).descriptor);
546
+ handler: () => E.promise(async () => {
547
+ let e = await b().query(k(S).descriptor), t = await b().query(k(w).descriptor), n = await b().query(k(T).descriptor), r = /* @__PURE__ */ new Map();
548
+ for (let e of t) {
549
+ let t = r.get(e.appId);
550
+ t === void 0 ? r.set(e.appId, [e]) : t.push(e);
551
+ }
552
+ let i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map();
553
+ for (let e of n) i.has(e.id) || i.set(e.id, e), a.set(e.appId, (a.get(e.appId) ?? 0) + 1);
682
554
  return Z({ apps: e.map((e) => ({
683
555
  id: e.id,
684
556
  app: e.app,
685
557
  platform: e.platform,
686
558
  shell: e.shell,
687
559
  revision: e.revision,
688
- channels: t.filter((t) => t.appId === e.id).map((e) => {
689
- let t = n.find((t) => t.id === e.currentReleaseId);
560
+ channels: (r.get(e.id) ?? []).map((e) => {
561
+ let t = e.currentReleaseId === null ? void 0 : i.get(e.currentReleaseId);
690
562
  return {
691
563
  name: e.name,
692
564
  current: t?.version ?? null,
@@ -696,7 +568,7 @@ var ge = (e, t, n) => {
696
568
  public: e.public
697
569
  };
698
570
  }),
699
- releases: n.filter((t) => t.appId === e.id).length
571
+ releases: a.get(e.id) ?? 0
700
572
  })) });
701
573
  })
702
574
  }, {
@@ -704,90 +576,122 @@ var ge = (e, t, n) => {
704
576
  path: "/events",
705
577
  scope: "shared-store",
706
578
  description: "The last 100 release events (promote, rollback, rollout, policy).",
707
- handler: () => _.promise(async () => {
708
- let e = await v().query(T(F).descriptor), t = await v().query(T(M).descriptor), n = new Map(t.map((e) => [e.id, e.name]));
579
+ handler: () => E.promise(async () => {
580
+ let e = await b().query(k(me).descriptor), t = await b().query(k(w).descriptor), n = new Map(t.map((e) => [e.id, e.name]));
709
581
  return Z({ events: [...e].sort((e, t) => t.at.getTime() - e.at.getTime()).slice(0, 100).map((e) => ({
710
582
  ...e,
711
583
  channelName: e.channelId === null ? null : n.get(e.channelId) ?? null,
712
584
  at: e.at.toISOString()
713
585
  })) });
714
586
  })
715
- }], Pe = async (n, r) => {
716
- let i = n.releaseId === void 0 ? null : await O(n.releaseId);
717
- if (n.releaseId !== void 0 && !i) throw new e({ app: n.releaseId });
718
- let a = (await v().query(T(j).descriptor)).filter((e) => (n.app === void 0 || e.app === n.app) && (n.platform === void 0 || e.platform === n.platform) && (n.shell === void 0 || e.shell === n.shell) && (i === null || e.id === i.appId) && (n.expectedTarget === void 0 || e.id === n.expectedTarget.id));
719
- if (a.length === 0 && r) return;
720
- if (a.length === 0) throw new e({ app: n.app ?? n.releaseId ?? "" });
721
- if (a.length !== 1) throw new t({ reason: "Multiple release targets match this app. Select an exact target." });
722
- let o = a[0], s = n.expectedTarget?.revision ?? o.revision;
723
- if (!Number.isSafeInteger(s) || s < 0 || s >= 2 ** 53 - 1 || o.revision !== s || await v().updateMany("_voltro_release_apps", { revision: s + 1 }, { where: fe(x("id", o.id), x("revision", s)) }) !== 1) throw new t({ reason: "Release target changed. Refresh and review the action again." });
724
- return o.id;
725
- }, Fe = (e) => e.kind === "mutation" ? {
726
- ...e,
727
- execute: (t, n) => _.gen(function* () {
728
- let r = yield* _.runtime();
729
- return yield* _.tryPromise({
730
- try: async (i) => {
731
- await w();
732
- let a = (a) => s.run(a, async () => {
733
- let a = await Pe(t, e.name === "releases.registerApp"), o = () => le.runPromise(r)(e.execute(t, n).pipe(_.either), { signal: i }), s = a === void 0 ? await o() : await p.run(a, o);
734
- if (s._tag === "Left") throw s.left;
735
- return s.right;
587
+ }], We = async (e, t) => {
588
+ let n = e.releaseId === void 0 ? null : await I(e.releaseId);
589
+ if (e.releaseId !== void 0 && !n) throw new o({ app: e.releaseId });
590
+ let r = (await b().query(k(S).descriptor)).filter((t) => (e.appPrefix === void 0 || t.app.startsWith(e.appPrefix)) && (e.app === void 0 || t.app === e.app) && (e.platform === void 0 || t.platform === e.platform) && (e.shell === void 0 || t.shell === e.shell) && (n === null || t.id === n.appId) && (e.expectedTarget === void 0 || t.id === e.expectedTarget.id));
591
+ if (r.length === 0 && t) return;
592
+ if (r.length === 0) throw new o({ app: e.app ?? e.releaseId ?? "" });
593
+ if (r.length !== 1) throw new s({ reason: "Multiple release targets match this app. Select an exact target." });
594
+ let i = r[0], a = e.expectedTarget?.revision ?? i.revision;
595
+ if (!Number.isSafeInteger(a) || a < 0 || a >= 2 ** 53 - 1 || i.revision !== a || await b().updateMany("_voltro_release_apps", { revision: a + 1 }, { where: Ee(O("id", i.id), O("revision", a), O("app", i.app), O("platform", i.platform), O("shell", i.shell)) }) !== 1) throw new s({ reason: "Release target changed. Refresh and review the action again." });
596
+ let c = (await b().query(k(S).where(O("id", i.id)).forUpdate().descriptor))[0];
597
+ if (c === void 0 || c.app !== i.app || c.platform !== i.platform || c.shell !== i.shell || c.revision !== a + 1 || e.appPrefix !== void 0 && !c.app.startsWith(e.appPrefix)) throw new s({ reason: "Release target changed. Refresh and review the action again." });
598
+ if (e.releaseId !== void 0) {
599
+ let t = await I(e.releaseId, !0);
600
+ if (t === null || t.appId !== i.id) throw new s({ reason: "Release target changed. Refresh and review the action again." });
601
+ }
602
+ return i.id;
603
+ }, Ge = (t) => t.kind === "mutation" ? {
604
+ ...t,
605
+ execute: (n, r) => E.gen(function* () {
606
+ let a = yield* E.context();
607
+ return yield* E.tryPromise({
608
+ try: async (o) => {
609
+ await N();
610
+ let c = (c) => ee.run(c, async () => {
611
+ if (t.name === "releases.upload") {
612
+ let t = n;
613
+ if (t.operationId !== void 0) {
614
+ let n = e(t);
615
+ if (n !== void 0) throw new s({ reason: n });
616
+ let { completed: r } = await Ve(c, t);
617
+ if (r) {
618
+ if (!i(r, t)) throw new s({ reason: "Upload operationId was already used for different artifact metadata." });
619
+ return { id: r.id };
620
+ }
621
+ }
622
+ }
623
+ let l = await We(n, t.name === "releases.registerApp"), u = () => E.runPromiseWith(a)(t.execute(n, r).pipe(E.result), { signal: o }), d = l === void 0 ? await u() : await y.run(l, u);
624
+ if (D.isFailure(d)) throw d.failure;
625
+ return d.success;
736
626
  });
737
- return n.mutationStore ? a(n.mutationStore) : v().transactional(a);
627
+ return r.mutationStore ? c(r.mutationStore) : b().transactional(c);
738
628
  },
739
629
  catch: (e) => e
740
630
  });
741
631
  })
742
- } : e, Ie = () => ({ request: {
632
+ } : t, Ke = {
633
+ apps: V,
634
+ create: H,
635
+ "prepare-upload": U,
636
+ upload: W,
637
+ promote: G,
638
+ rollout: K,
639
+ rollback: q,
640
+ policy: J
641
+ }, qe = (e, t, n) => {
642
+ if (!Object.hasOwn(Ke, e)) return E.fail(new s({ reason: "Unknown release management action." }));
643
+ let r = Ke[e];
644
+ return Se.decodeUnknownEffect(r.input)(t).pipe(E.flatMap((e) => Ge(r).execute(e, n)));
645
+ }, Je = () => ({ request: {
743
646
  subject: {
744
647
  type: "anonymous",
745
648
  id: null,
746
649
  tenantId: null
747
650
  },
748
- traceId: he()
651
+ traceId: Ae()
749
652
  } }), Q = (e, t) => ({
750
653
  kind: "json",
751
654
  status: e,
752
655
  data: { error: t }
753
- }), $ = (n, r, i) => ({
656
+ }), $ = (e, t, n) => ({
754
657
  method: "POST",
755
- path: n,
756
- description: i,
757
- handler: (n) => _.gen(function* () {
758
- let i;
658
+ path: e,
659
+ description: n,
660
+ handler: (e) => E.gen(function* () {
661
+ let n;
759
662
  try {
760
- i = JSON.parse(n.body || "{}");
663
+ n = JSON.parse(e.body || "{}");
761
664
  } catch {
762
665
  return Q(400, "invalid JSON body");
763
666
  }
764
- if (typeof i != "object" || !i || Array.isArray(i)) return Q(400, "expected a JSON object body");
765
- let a = yield* ue.decodeUnknown(r.input)(i).pipe(_.either);
766
- if (a._tag === "Left") return Q(400, "invalid action input");
767
- let o = yield* Fe(r).execute(a.right, Ie()).pipe(_.either);
768
- if (o._tag === "Left") {
769
- let n = o.left;
770
- return n instanceof e ? Q(404, `not found: ${n.app}${n.version ? ` ${n.version}` : ""}${n.channel ? ` (${n.channel})` : ""}`) : n instanceof t ? Q(409, n.reason) : Q(500, "release action failed");
667
+ if (typeof n != "object" || !n || Array.isArray(n)) return Q(400, "expected a JSON object body");
668
+ let r = yield* Se.decodeUnknownEffect(t.input)(n).pipe(E.result);
669
+ if (D.isFailure(r)) return Q(400, "invalid action input");
670
+ let i = yield* Ge(t).execute(r.success, Je()).pipe(E.result);
671
+ if (D.isFailure(i)) {
672
+ let e = i.failure;
673
+ return e instanceof o ? Q(404, `not found: ${e.app}${e.version ? ` ${e.version}` : ""}${e.channel ? ` (${e.channel})` : ""}`) : e instanceof s ? Q(409, e.reason) : Q(500, "release action failed");
771
674
  }
772
- return Z({ result: o.right });
675
+ return Z({ result: i.success });
773
676
  })
774
- }), Le = [
775
- $("/apps", Te, "Register an app target at runtime: { app, platform, shell, channels? }."),
776
- $("/create", Ee, "Create a draft release: { app, channel, version, buildId?, notes? }."),
777
- $("/upload", De, "Attach an artefact: { releaseId, kind, target?, url, sha256, size, signature? }."),
778
- $("/promote", Oe, "Promote a version onto a channel: { app, version, to, rolloutPercent? }."),
779
- $("/rollout", ke, "Set the rollout percentage: { app, channel, percent }."),
780
- $("/rollback", Ae, "Roll a channel back: { app, channel, reason }."),
781
- $("/policy", je, "Set the policy: { app, channel, minSupportedVersion?, killSwitches?, reason }.")
677
+ }), Ye = [
678
+ $("/apps", V, "Register an app target at runtime: { app, platform, shell, channels? }."),
679
+ $("/create", H, "Create a draft release: { app, channel, version, buildId?, notes? }."),
680
+ $("/prepare-upload", U, "Resolve current release/app/version before file transfer; no metadata writes."),
681
+ $("/upload", W, "Attach an artefact: { releaseId, kind, target?, url, sha256, size, signature? }."),
682
+ $("/promote", G, "Promote a version onto a channel: { app, version, to, rolloutPercent? }."),
683
+ $("/rollout", K, "Set the rollout percentage: { app, channel, percent }."),
684
+ $("/rollback", q, "Roll a channel back: { app, channel, reason }."),
685
+ $("/policy", J, "Set the policy: { app, channel, minSupportedVersion?, killSwitches?, reason }.")
782
686
  ];
783
- return pe({
687
+ return Oe({
784
688
  source: "plugin",
785
- table: g,
689
+ table: _,
786
690
  timeColumn: "at",
787
- ttlMs: me(process.env.VOLTRO_RELEASE_EVENTS_TTL_HOURS, 8760),
691
+ ttlMs: ke(process.env.VOLTRO_RELEASE_EVENTS_TTL_HOURS, 8760),
788
692
  envVar: "VOLTRO_RELEASE_EVENTS_TTL_HOURS"
789
- }), de({
790
- name: n.name ? `${m}#${n.name}` : m,
693
+ }), Object.assign(Te({
694
+ name: u.name ? `${v}#${u.name}` : v,
791
695
  description: "Release channels, artefacts and rollouts — the self-hosted update server behind Tauri, Expo OTA, electron-updater and PWA feeds, with a reactive release policy.",
792
696
  permissions: [
793
697
  "inspect:read",
@@ -795,40 +699,41 @@ var ge = (e, t, n) => {
795
699
  "store:write"
796
700
  ],
797
701
  routes: [
798
- Ce,
799
- we,
800
- Te,
801
- Ee,
802
- De,
803
- Oe,
804
- ke,
805
- Ae,
806
- je
807
- ].map(Fe),
808
- rpcClientDescriptors: ce,
809
- httpRoutes: [Me],
810
- inspectEndpoints: [...Ne, ...Le],
811
- extendSchema: { tables: [...I] },
812
- declaredEnv: xe,
813
- privacy: be,
702
+ ze,
703
+ Be,
704
+ V,
705
+ H,
706
+ U,
707
+ W,
708
+ G,
709
+ K,
710
+ q,
711
+ J
712
+ ].map(Ge),
713
+ rpcClientDescriptors: le,
714
+ httpRoutes: [He],
715
+ inspectEndpoints: [...Ue, ...Ye],
716
+ extendSchema: { tables: [...he] },
717
+ declaredEnv: Ne,
718
+ privacy: Me,
814
719
  bindDataStore: (e) => {
815
- o = e, b = C(), b.catch(() => {});
720
+ p = e, x = Ce(), x.catch(() => {});
816
721
  },
817
- onActivate: () => _.tryPromise({
722
+ onActivate: () => E.tryPromise({
818
723
  catch: (e) => /* @__PURE__ */ Error(`@voltro/plugin-releases could not seed its targets and channels: ${e instanceof Error ? e.message : String(e)}`),
819
- try: () => w()
724
+ try: () => N()
820
725
  })
821
- });
822
- }, be = { collects: [{
726
+ }), { manage: qe });
727
+ }, Me = { collects: [{
823
728
  category: "device-id",
824
729
  linked: !1,
825
730
  tracking: !1,
826
731
  purposes: ["app-functionality"]
827
- }] }, xe = [{
732
+ }] }, Ne = [{
828
733
  name: "VOLTRO_RELEASE_EVENTS_TTL_HOURS",
829
734
  required: !1,
830
735
  secret: !1,
831
736
  description: "Hours a release event (create, upload, promote, rollout, rollback, policy, prune) is kept before the retention sweep removes it. Default 8760 (one year)."
832
737
  }];
833
738
  //#endregion
834
- export { m as BASE_NAME, p as RELEASES_MANAGE_SCOPE, f as RELEASES_TABLE, d as RELEASE_APPS_TABLE, l as RELEASE_ARTIFACTS_TABLE, h as RELEASE_CHANNELS_TABLE, g as RELEASE_EVENTS_TABLE, k as RELEASE_PLATFORMS, A as RELEASE_SHELLS, e as ReleaseNotFound, t as ReleaseRefused, c as compareVersions, i as evaluatePolicy, o as inRollout, r as isNewerVersion, L as offeredTo, H as osFromUserAgent, n as parseVersion, _e as pickDownload, j as releaseAppsTable, P as releaseArtifactsTable, M as releaseChannelsTable, F as releaseEventsTable, I as releaseTables, ye as releasesPlugin, N as releasesTable, z as renderElectronLatestYml, B as renderExpoFeed, R as renderTauriFeed, a as rolloutBucket, s as sortVersionsDesc };
739
+ export { v as BASE_NAME, y as RELEASES_MANAGE_SCOPE, m as RELEASES_TABLE, ne as RELEASE_APPS_TABLE, h as RELEASE_ARTIFACTS_TABLE, g as RELEASE_CHANNELS_TABLE, _ as RELEASE_EVENTS_TABLE, b as RELEASE_PLATFORMS, x as RELEASE_SHELLS, o as ReleaseNotFound, s as ReleaseRefused, c as compareVersions, l as evaluatePolicy, u as inRollout, d as isNewerVersion, ge as offeredTo, _e as osFromUserAgent, f as parseVersion, ve as pickDownload, S as releaseAppsTable, C as releaseArtifactsTable, w as releaseChannelsTable, me as releaseEventsTable, he as releaseTables, N as releasesPlugin, T as releasesTable, ye as renderElectronLatestYml, be as renderExpoFeed, xe as renderTauriFeed, p as rolloutBucket, ee as sortVersionsDesc };