@peers-app/peers-sdk 0.18.8 → 0.19.6
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/README.md +74 -1
- package/dist/data/files/file-read-stream.js +7 -0
- package/dist/data/files/file.types.d.ts +6 -0
- package/dist/data/files/file.types.js +18 -0
- package/dist/data/files/files.test.js +50 -7
- package/dist/data/package-version-resolver.d.ts +13 -5
- package/dist/data/package-version-resolver.js +64 -6
- package/dist/data/package-version-resolver.test.d.ts +0 -4
- package/dist/data/package-version-resolver.test.js +127 -5
- package/dist/data/package-versions.d.ts +3 -0
- package/dist/data/package-versions.js +5 -0
- package/dist/data/packages.d.ts +6 -29
- package/dist/data/packages.js +8 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/package-installer/index.d.ts +10 -0
- package/dist/package-installer/index.js +26 -0
- package/dist/package-installer/package-author-signing.d.ts +54 -0
- package/dist/package-installer/package-author-signing.js +82 -0
- package/dist/package-installer/package-author-signing.test.d.ts +1 -0
- package/dist/package-installer/package-author-signing.test.js +189 -0
- package/dist/package-installer/package-cloner.d.ts +16 -0
- package/dist/package-installer/package-cloner.js +115 -0
- package/dist/package-installer/package-cloner.test.d.ts +1 -0
- package/dist/package-installer/package-cloner.test.js +276 -0
- package/dist/package-installer/package-creator.d.ts +22 -0
- package/dist/package-installer/package-creator.js +154 -0
- package/dist/package-installer/package-creator.test.d.ts +1 -0
- package/dist/package-installer/package-creator.test.js +354 -0
- package/dist/package-installer/package-installer.d.ts +32 -0
- package/dist/package-installer/package-installer.js +247 -0
- package/dist/package-installer/package-installer.test.d.ts +1 -0
- package/dist/package-installer/package-installer.test.js +666 -0
- package/dist/package-installer/package-propagation.d.ts +29 -0
- package/dist/package-installer/package-propagation.js +364 -0
- package/dist/package-installer/package-propagation.test.d.ts +1 -0
- package/dist/package-installer/package-propagation.test.js +1145 -0
- package/dist/package-installer/package-publisher.d.ts +55 -0
- package/dist/package-installer/package-publisher.js +71 -0
- package/dist/package-installer/package-publisher.test.d.ts +1 -0
- package/dist/package-installer/package-publisher.test.js +142 -0
- package/dist/package-installer/package-remote-checker.d.ts +54 -0
- package/dist/package-installer/package-remote-checker.js +194 -0
- package/dist/package-installer/package-remote-checker.test.d.ts +1 -0
- package/dist/package-installer/package-remote-checker.test.js +269 -0
- package/dist/package-installer/package-seed-installer.d.ts +45 -0
- package/dist/package-installer/package-seed-installer.js +108 -0
- package/dist/package-installer/package-seed-installer.test.d.ts +1 -0
- package/dist/package-installer/package-seed-installer.test.js +123 -0
- package/dist/package-installer/package-tarball.d.ts +35 -0
- package/dist/package-installer/package-tarball.js +57 -0
- package/dist/package-installer/package-tarball.test.d.ts +1 -0
- package/dist/package-installer/package-tarball.test.js +75 -0
- package/dist/package-installer/types.d.ts +110 -0
- package/dist/package-installer/types.js +2 -0
- package/dist/rpc-types.d.ts +14 -0
- package/dist/rpc-types.js +6 -0
- package/dist/system-ids.d.ts +1 -0
- package/dist/system-ids.js +2 -1
- package/package.json +3 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DataContext } from "../context/data-context";
|
|
2
|
+
import type { UserContext } from "../context/user-context";
|
|
3
|
+
import { type IPackage } from "../data/packages";
|
|
4
|
+
/**
|
|
5
|
+
* Result of a single successful cross-group propagation.
|
|
6
|
+
*/
|
|
7
|
+
export interface IPropagationResult {
|
|
8
|
+
groupId: string;
|
|
9
|
+
packageId: string;
|
|
10
|
+
packageVersionId: string;
|
|
11
|
+
version: string;
|
|
12
|
+
versionTag: string;
|
|
13
|
+
activated: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Subscribes to PackageVersion dataChanged events across all groups and
|
|
17
|
+
* propagates signed PVs to admin groups. Call once at startup.
|
|
18
|
+
*/
|
|
19
|
+
export declare function propagatePvOnChanged(userContext?: UserContext): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Catch-up/backfill scanner. Iterates all local signed PVs and propagates
|
|
22
|
+
* any that are missing from admin groups. Useful for manual repair.
|
|
23
|
+
*/
|
|
24
|
+
export declare function discoverAndPropagateVersions(userContext: UserContext): Promise<IPropagationResult[]>;
|
|
25
|
+
export interface IActivationResult {
|
|
26
|
+
activated: boolean;
|
|
27
|
+
activePackageVersionId?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function activateBestEligibleVersion(dataContext: DataContext, pkg: IPackage): Promise<IActivationResult>;
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.propagatePvOnChanged = propagatePvOnChanged;
|
|
4
|
+
exports.discoverAndPropagateVersions = discoverAndPropagateVersions;
|
|
5
|
+
exports.activateBestEligibleVersion = activateBestEligibleVersion;
|
|
6
|
+
const context_1 = require("../context");
|
|
7
|
+
const files_1 = require("../data/files/files");
|
|
8
|
+
const group_member_roles_1 = require("../data/group-member-roles");
|
|
9
|
+
const group_permissions_1 = require("../data/group-permissions");
|
|
10
|
+
const package_versions_1 = require("../data/package-versions");
|
|
11
|
+
const packages_1 = require("../data/packages");
|
|
12
|
+
const utils_1 = require("../utils");
|
|
13
|
+
const package_author_signing_1 = require("./package-author-signing");
|
|
14
|
+
// --- Module-level state ---
|
|
15
|
+
const RETRY_DELAYS_MS = [2_000, 10_000, 30_000, 60_000, 120_000];
|
|
16
|
+
const pvPropagateMap = {};
|
|
17
|
+
const activationLocks = new Map();
|
|
18
|
+
// --- Public API ---
|
|
19
|
+
/**
|
|
20
|
+
* Subscribes to PackageVersion dataChanged events across all groups and
|
|
21
|
+
* propagates signed PVs to admin groups. Call once at startup.
|
|
22
|
+
*/
|
|
23
|
+
async function propagatePvOnChanged(userContext) {
|
|
24
|
+
const uc = userContext || (await (0, context_1.getUserContext)());
|
|
25
|
+
uc.subscribeToDataChangedAcrossAllGroups((0, package_versions_1.PackageVersions)(), (evt) => {
|
|
26
|
+
if (evt.data.op === "delete")
|
|
27
|
+
return;
|
|
28
|
+
const pv = evt.data.dataObject;
|
|
29
|
+
if (!isPropagatablePackageVersion(pv))
|
|
30
|
+
return;
|
|
31
|
+
if (!pvPropagateMap[pv.packageVersionId]) {
|
|
32
|
+
pvPropagateMap[pv.packageVersionId] = propagatePvToAdminGroups(uc, evt.dataContext, pv).then(() => undefined, (err) => console.error(`[propagation] Unhandled error for ${pv.packageVersionId}:`, err));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Catch-up/backfill scanner. Iterates all local signed PVs and propagates
|
|
38
|
+
* any that are missing from admin groups. Useful for manual repair.
|
|
39
|
+
*/
|
|
40
|
+
async function discoverAndPropagateVersions(userContext) {
|
|
41
|
+
const results = [];
|
|
42
|
+
const adminGroups = await getAdminGroups(userContext);
|
|
43
|
+
const allContexts = [
|
|
44
|
+
userContext.userDataContext,
|
|
45
|
+
...Array.from(userContext.groupDataContexts.entries())
|
|
46
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
47
|
+
.map(([, ctx]) => ctx),
|
|
48
|
+
];
|
|
49
|
+
// Phase 1: Install all missing PVs without activating
|
|
50
|
+
const packagesToActivate = new Map(); // "groupId:packageId" -> set of pvIds installed
|
|
51
|
+
for (const sourceContext of allContexts) {
|
|
52
|
+
const pvs = await (0, package_versions_1.PackageVersions)(sourceContext).list();
|
|
53
|
+
for (const pv of pvs.filter(isPropagatablePackageVersion)) {
|
|
54
|
+
for (const targetContext of adminGroups) {
|
|
55
|
+
if (targetContext.dataContextId === sourceContext.dataContextId)
|
|
56
|
+
continue;
|
|
57
|
+
try {
|
|
58
|
+
const installed = await installPvInGroupNoActivate(sourceContext, targetContext, pv);
|
|
59
|
+
if (installed) {
|
|
60
|
+
const key = `${getContextId(targetContext)}:${pv.packageId}`;
|
|
61
|
+
const set = packagesToActivate.get(key) ?? new Set();
|
|
62
|
+
set.add(pv.packageVersionId);
|
|
63
|
+
packagesToActivate.set(key, set);
|
|
64
|
+
results.push(makeResult(targetContext, pv, false));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.error(`[propagation] Error backfilling ${pv.packageVersionId}:`, err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Phase 2: Activate best eligible version per (group, package)
|
|
74
|
+
for (const [key] of packagesToActivate) {
|
|
75
|
+
const [groupId, packageId] = key.split(":");
|
|
76
|
+
const targetContext = adminGroups.find((ctx) => getContextId(ctx) === groupId);
|
|
77
|
+
if (!targetContext)
|
|
78
|
+
continue;
|
|
79
|
+
const pkg = await (0, packages_1.Packages)(targetContext).get(packageId);
|
|
80
|
+
if (!pkg)
|
|
81
|
+
continue;
|
|
82
|
+
const activation = await withActivationLock(targetContext, packageId, () => activateBestEligibleVersion(targetContext, pkg));
|
|
83
|
+
if (activation.activated && activation.activePackageVersionId) {
|
|
84
|
+
const result = results.find((r) => r.groupId === groupId && r.packageVersionId === activation.activePackageVersionId);
|
|
85
|
+
if (result)
|
|
86
|
+
result.activated = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
// --- Core propagation ---
|
|
92
|
+
async function propagatePvToAdminGroups(userContext, sourceContext, pv) {
|
|
93
|
+
const adminGroups = await getAdminGroups(userContext);
|
|
94
|
+
const results = [];
|
|
95
|
+
await Promise.all(adminGroups.map(async (targetContext) => {
|
|
96
|
+
if (targetContext.dataContextId === sourceContext.dataContextId)
|
|
97
|
+
return;
|
|
98
|
+
try {
|
|
99
|
+
const result = await installPvInGroup(sourceContext, targetContext, pv);
|
|
100
|
+
if (result)
|
|
101
|
+
results.push(result);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error(`[propagation] Failed ${pv.packageVersionId} -> ${getContextId(targetContext)}:`, err);
|
|
105
|
+
}
|
|
106
|
+
}));
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Install a PV without activation or retries. Used by the backfill scanner.
|
|
111
|
+
* Returns true if the PV was installed, false/undefined otherwise.
|
|
112
|
+
*/
|
|
113
|
+
async function installPvInGroupNoActivate(source, dest, pv) {
|
|
114
|
+
const pkg = await (0, packages_1.Packages)(dest).get(pv.packageId);
|
|
115
|
+
if (!pkg || pkg.disabled)
|
|
116
|
+
return false;
|
|
117
|
+
const existingPv = await (0, package_versions_1.PackageVersions)(dest).get(pv.packageVersionId);
|
|
118
|
+
if (existingPv)
|
|
119
|
+
return false;
|
|
120
|
+
if (!(await verifyOrEstablishTofu(source, dest, pkg, pv)))
|
|
121
|
+
return false;
|
|
122
|
+
const files = await copyBundleFiles(source, pv);
|
|
123
|
+
if (!files)
|
|
124
|
+
return false;
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
await copyFileTree(source, dest, file);
|
|
127
|
+
}
|
|
128
|
+
const propagatedPv = buildPropagatedPv(pv, files);
|
|
129
|
+
await (0, package_versions_1.PackageVersions)(dest).signAndSave(propagatedPv, { saveAsSnapshot: true });
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
async function installPvInGroup(source, dest, pv, retryCnt = 0) {
|
|
133
|
+
if (retryCnt > 0) {
|
|
134
|
+
const delayMs = RETRY_DELAYS_MS[retryCnt - 1];
|
|
135
|
+
if (!delayMs) {
|
|
136
|
+
console.error(`[propagation] Gave up on ${pv.packageVersionId} -> ${getContextId(dest)} after ${RETRY_DELAYS_MS.length} retries`);
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
await (0, utils_1.sleep)(delayMs);
|
|
140
|
+
}
|
|
141
|
+
const pkg = await (0, packages_1.Packages)(dest).get(pv.packageId);
|
|
142
|
+
if (!pkg || pkg.disabled)
|
|
143
|
+
return undefined;
|
|
144
|
+
const existingPv = await (0, package_versions_1.PackageVersions)(dest).get(pv.packageVersionId);
|
|
145
|
+
if (existingPv) {
|
|
146
|
+
// Already installed; still run activation in case policy changed
|
|
147
|
+
const activation = await withActivationLock(dest, pv.packageId, () => activateBestEligibleVersion(dest, pkg));
|
|
148
|
+
return activation.activated
|
|
149
|
+
? makeResult(dest, pv, activation.activePackageVersionId === pv.packageVersionId)
|
|
150
|
+
: undefined;
|
|
151
|
+
}
|
|
152
|
+
// TOFU verification
|
|
153
|
+
if (!(await verifyOrEstablishTofu(source, dest, pkg, pv))) {
|
|
154
|
+
return await retryOrFail(source, dest, pv, retryCnt, "TOFU");
|
|
155
|
+
}
|
|
156
|
+
// Copy bundle files
|
|
157
|
+
const files = await copyBundleFiles(source, pv);
|
|
158
|
+
if (!files) {
|
|
159
|
+
return await retryOrFail(source, dest, pv, retryCnt, "missing files");
|
|
160
|
+
}
|
|
161
|
+
// Copy file records (including index files) into destination
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
await copyFileTree(source, dest, file);
|
|
164
|
+
}
|
|
165
|
+
// Save sanitized PV
|
|
166
|
+
const propagatedPv = buildPropagatedPv(pv, files);
|
|
167
|
+
await (0, package_versions_1.PackageVersions)(dest).signAndSave(propagatedPv, { saveAsSnapshot: true });
|
|
168
|
+
// Activation pass
|
|
169
|
+
const activation = await withActivationLock(dest, pv.packageId, () => activateBestEligibleVersion(dest, pkg));
|
|
170
|
+
return makeResult(dest, pv, activation.activated && activation.activePackageVersionId === pv.packageVersionId);
|
|
171
|
+
}
|
|
172
|
+
async function retryOrFail(source, dest, pv, retryCnt, reason) {
|
|
173
|
+
if (retryCnt < RETRY_DELAYS_MS.length) {
|
|
174
|
+
console.warn(`[propagation] ${reason} for ${pv.packageVersionId}, retrying...`);
|
|
175
|
+
return installPvInGroup(source, dest, pv, retryCnt + 1);
|
|
176
|
+
}
|
|
177
|
+
console.error(`[propagation] ${reason} for ${pv.packageVersionId}, giving up`);
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
// --- TOFU ---
|
|
181
|
+
/**
|
|
182
|
+
* Returns true if signature verification passes. Establishes TOFU key
|
|
183
|
+
* from source if target has no publishPublicKey yet. Returns false on
|
|
184
|
+
* transient failures (missing source key).
|
|
185
|
+
*/
|
|
186
|
+
async function verifyOrEstablishTofu(sourceContext, targetContext, targetPkg, pv) {
|
|
187
|
+
if (targetPkg.publishPublicKey) {
|
|
188
|
+
if (!(0, package_author_signing_1.verifyPackageAuthorSignature)(pv, targetPkg.publishPublicKey).valid) {
|
|
189
|
+
console.error(`[propagation] Invalid signature for ${pv.packageVersionId}`);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
// TOFU: get key from source
|
|
195
|
+
const sourcePkg = await (0, packages_1.Packages)(sourceContext).get(targetPkg.packageId);
|
|
196
|
+
if (!sourcePkg?.publishPublicKey)
|
|
197
|
+
return false;
|
|
198
|
+
if (!(0, package_author_signing_1.verifyPackageAuthorSignature)(pv, sourcePkg.publishPublicKey).valid) {
|
|
199
|
+
console.error(`[propagation] Invalid signature for ${pv.packageVersionId} (TOFU)`);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
await (0, packages_1.Packages)(targetContext).signAndSave({
|
|
203
|
+
...targetPkg,
|
|
204
|
+
publishPublicKey: sourcePkg.publishPublicKey,
|
|
205
|
+
});
|
|
206
|
+
targetPkg.publishPublicKey = sourcePkg.publishPublicKey;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Loads and verifies all bundle file records from source. Returns null if
|
|
211
|
+
* any required file is missing (transient failure).
|
|
212
|
+
*/
|
|
213
|
+
async function copyBundleFiles(source, pv) {
|
|
214
|
+
const sourceFiles = (0, files_1.Files)(source);
|
|
215
|
+
const bundles = [];
|
|
216
|
+
const entries = [
|
|
217
|
+
[pv.packageBundleFileId, pv.packageBundleFileHash, "packageBundleFileId"],
|
|
218
|
+
[pv.routesBundleFileId, pv.routesBundleFileHash, "routesBundleFileId"],
|
|
219
|
+
[pv.uiBundleFileId, pv.uiBundleFileHash, "uiBundleFileId"],
|
|
220
|
+
];
|
|
221
|
+
for (const [fileId, expectedHash, field] of entries) {
|
|
222
|
+
if (!fileId && !expectedHash)
|
|
223
|
+
continue;
|
|
224
|
+
if (!fileId || !expectedHash) {
|
|
225
|
+
console.error(`[propagation] Malformed ${field} on ${pv.packageVersionId}: id=${fileId}, hash=${expectedHash}`);
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const file = await sourceFiles.get(fileId);
|
|
229
|
+
if (!file)
|
|
230
|
+
return null;
|
|
231
|
+
if (file.fileHash !== expectedHash) {
|
|
232
|
+
console.error(`[propagation] Hash mismatch on ${field} for ${pv.packageVersionId}: expected ${expectedHash}, got ${file.fileHash}`);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
bundles.push({ file, expectedHash, field });
|
|
236
|
+
}
|
|
237
|
+
if (!bundles.some((b) => b.field === "packageBundleFileId")) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return bundles;
|
|
241
|
+
}
|
|
242
|
+
async function copyFileTree(source, dest, bundle, copiedIds = new Set()) {
|
|
243
|
+
await saveFileAndIndexTree((0, files_1.Files)(source), (0, files_1.Files)(dest), bundle.file, copiedIds);
|
|
244
|
+
}
|
|
245
|
+
async function saveFileAndIndexTree(sourceFiles, destFiles, file, copiedIds) {
|
|
246
|
+
if (copiedIds.has(file.fileId))
|
|
247
|
+
return;
|
|
248
|
+
copiedIds.add(file.fileId);
|
|
249
|
+
if (file.indexFileId) {
|
|
250
|
+
const indexFile = await sourceFiles.get(file.indexFileId);
|
|
251
|
+
if (indexFile) {
|
|
252
|
+
await saveFileAndIndexTree(sourceFiles, destFiles, indexFile, copiedIds);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
await destFiles.saveFileRecord(file);
|
|
256
|
+
}
|
|
257
|
+
async function activateBestEligibleVersion(dataContext, pkg) {
|
|
258
|
+
const range = pkg.versionFollowRange ?? "pinned";
|
|
259
|
+
if (range === "pinned")
|
|
260
|
+
return { activated: false };
|
|
261
|
+
const pvs = await (0, package_versions_1.PackageVersions)(dataContext).list({ packageId: pkg.packageId });
|
|
262
|
+
const activePv = pkg.activePackageVersionId
|
|
263
|
+
? (pvs.find((p) => p.packageVersionId === pkg.activePackageVersionId) ??
|
|
264
|
+
(await (0, package_versions_1.PackageVersions)(dataContext).get(pkg.activePackageVersionId)))
|
|
265
|
+
: undefined;
|
|
266
|
+
let best;
|
|
267
|
+
for (const candidate of pvs) {
|
|
268
|
+
if (!isEligibleForActivation(candidate, pkg, activePv))
|
|
269
|
+
continue;
|
|
270
|
+
if (!(0, package_versions_1.doesTagMatch)(activePv?.versionTag, candidate.versionTag, pkg.followVersionTags, undefined))
|
|
271
|
+
continue;
|
|
272
|
+
if (activePv && !(0, package_versions_1.isVersionInRange)(activePv.version, candidate.version, range))
|
|
273
|
+
continue;
|
|
274
|
+
if (!best || (0, package_versions_1.isNewerVersion)(best.version, candidate.version))
|
|
275
|
+
best = candidate;
|
|
276
|
+
}
|
|
277
|
+
if (!best || best.packageVersionId === pkg.activePackageVersionId) {
|
|
278
|
+
return { activated: false, activePackageVersionId: pkg.activePackageVersionId };
|
|
279
|
+
}
|
|
280
|
+
pkg.activePackageVersionId = best.packageVersionId;
|
|
281
|
+
await (0, packages_1.Packages)(dataContext).signAndSave(pkg);
|
|
282
|
+
return { activated: true, activePackageVersionId: best.packageVersionId };
|
|
283
|
+
}
|
|
284
|
+
function isEligibleForActivation(pv, pkg, activePv) {
|
|
285
|
+
if (pv.versionTag === "dev")
|
|
286
|
+
return false;
|
|
287
|
+
if (!pv.packageAuthorSignature)
|
|
288
|
+
return false;
|
|
289
|
+
if (!pkg.publishPublicKey)
|
|
290
|
+
return false;
|
|
291
|
+
if (activePv && !(0, package_versions_1.isNewerVersion)(activePv.version, pv.version))
|
|
292
|
+
return false;
|
|
293
|
+
return (0, package_author_signing_1.verifyPackageAuthorSignature)(pv, pkg.publishPublicKey).valid;
|
|
294
|
+
}
|
|
295
|
+
// --- Utilities ---
|
|
296
|
+
function isPropagatablePackageVersion(pv) {
|
|
297
|
+
return Boolean(pv.packageAuthorSignature && pv.versionTag !== "dev");
|
|
298
|
+
}
|
|
299
|
+
async function getAdminGroups(userContext) {
|
|
300
|
+
const adminContexts = [];
|
|
301
|
+
for (const [groupId, dataContext] of Array.from(userContext.groupDataContexts.entries())) {
|
|
302
|
+
try {
|
|
303
|
+
const role = await (0, group_permissions_1.getUserRole)(groupId, userContext.userId);
|
|
304
|
+
if (role >= group_member_roles_1.GroupMemberRole.Admin) {
|
|
305
|
+
adminContexts.push(dataContext);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Skip groups where role can't be determined
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return adminContexts;
|
|
313
|
+
}
|
|
314
|
+
function withActivationLock(targetContext, packageId, fn) {
|
|
315
|
+
const key = `${getContextId(targetContext)}:${packageId}`;
|
|
316
|
+
const previous = activationLocks.get(key) ?? Promise.resolve();
|
|
317
|
+
const current = previous
|
|
318
|
+
.catch(() => undefined)
|
|
319
|
+
.then(fn)
|
|
320
|
+
.finally(() => {
|
|
321
|
+
if (activationLocks.get(key) === current) {
|
|
322
|
+
activationLocks.delete(key);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
activationLocks.set(key, current);
|
|
326
|
+
return current;
|
|
327
|
+
}
|
|
328
|
+
function buildPropagatedPv(pv, files) {
|
|
329
|
+
const fileMap = new Map(files.map((f) => [f.field, f]));
|
|
330
|
+
const pkgBundle = fileMap.get("packageBundleFileId");
|
|
331
|
+
const routesBundle = fileMap.get("routesBundleFileId");
|
|
332
|
+
const uiBundle = fileMap.get("uiBundleFileId");
|
|
333
|
+
return {
|
|
334
|
+
packageVersionId: pv.packageVersionId,
|
|
335
|
+
packageId: pv.packageId,
|
|
336
|
+
version: pv.version,
|
|
337
|
+
versionTag: pv.versionTag,
|
|
338
|
+
packageVersionHash: (0, package_versions_1.computePackageVersionHash)(pv.version, pv.versionTag ?? "", pkgBundle.expectedHash, routesBundle?.expectedHash, uiBundle?.expectedHash),
|
|
339
|
+
packageBundleFileId: pkgBundle.file.fileId,
|
|
340
|
+
packageBundleFileHash: pkgBundle.expectedHash,
|
|
341
|
+
routesBundleFileId: routesBundle?.file.fileId,
|
|
342
|
+
routesBundleFileHash: routesBundle?.expectedHash,
|
|
343
|
+
uiBundleFileId: uiBundle?.file.fileId,
|
|
344
|
+
uiBundleFileHash: uiBundle?.expectedHash,
|
|
345
|
+
appNavs: pv.appNavs,
|
|
346
|
+
packageAuthorSignature: pv.packageAuthorSignature,
|
|
347
|
+
signature: "",
|
|
348
|
+
createdBy: pv.createdBy,
|
|
349
|
+
createdAt: pv.createdAt,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function getContextId(dataContext) {
|
|
353
|
+
return dataContext.groupId ?? dataContext.dataContextId;
|
|
354
|
+
}
|
|
355
|
+
function makeResult(targetContext, pv, activated) {
|
|
356
|
+
return {
|
|
357
|
+
groupId: getContextId(targetContext),
|
|
358
|
+
packageId: pv.packageId,
|
|
359
|
+
packageVersionId: pv.packageVersionId,
|
|
360
|
+
version: pv.version,
|
|
361
|
+
versionTag: pv.versionTag ?? "stable",
|
|
362
|
+
activated,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|