@peers-app/peers-sdk 0.19.0 → 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/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 +126 -5
- package/dist/data/packages.d.ts +0 -29
- package/dist/data/packages.js +0 -6
- package/dist/package-installer/package-author-signing.d.ts +10 -4
- package/dist/package-installer/package-author-signing.js +10 -1
- package/dist/package-installer/package-propagation.js +1 -0
- package/dist/package-installer/package-publisher.d.ts +5 -0
- package/dist/package-installer/package-publisher.js +4 -0
- package/dist/package-installer/package-remote-checker.js +11 -3
- package/dist/package-installer/package-remote-checker.test.js +6 -0
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Device-local package version resolver.
|
|
3
3
|
*
|
|
4
|
-
* Each device
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the group
|
|
4
|
+
* Each device resolves which package version to run from the shared group
|
|
5
|
+
* package settings plus optional device-local prefs. When an admin device
|
|
6
|
+
* upgrades a non-dev version while following group settings, the shared
|
|
7
|
+
* `IPackage.activePackageVersionId` advances so the group stays aligned.
|
|
8
8
|
*/
|
|
9
9
|
import type { DataContext } from "../context/data-context";
|
|
10
10
|
import { type IPackageVersion } from "./package-versions";
|
|
11
|
-
import type
|
|
11
|
+
import { type IPackage } from "./packages";
|
|
12
12
|
import { type PersistentVar } from "./persistent-vars";
|
|
13
13
|
/**
|
|
14
14
|
* Device-local preferences for a single package. Stored in a `groupDeviceVar`
|
|
@@ -68,6 +68,7 @@ export interface IEffectivePackagePrefs {
|
|
|
68
68
|
* resolver and by UI components that need to reflect device behavior.
|
|
69
69
|
*/
|
|
70
70
|
export declare function getEffectivePackagePrefs(pkg: IPackage, devicePrefs: IDevicePackagePrefs | undefined): IEffectivePackagePrefs;
|
|
71
|
+
/** Result of a device-level package version resolution attempt. */
|
|
71
72
|
export interface IResolveResult {
|
|
72
73
|
/** Whether a package was successfully loaded */
|
|
73
74
|
loaded: boolean;
|
|
@@ -90,3 +91,10 @@ export declare function resolveDevicePackageVersion(pkg: IPackage, dataContext:
|
|
|
90
91
|
force?: boolean;
|
|
91
92
|
localPath?: string;
|
|
92
93
|
}): Promise<IResolveResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Returns true when the device has an explicit follow-policy override
|
|
96
|
+
* (`followRange`, `followTags`, or `pinned`). Used by the UI to drive the
|
|
97
|
+
* "Override on this device" toggle and by the resolver to decide whether
|
|
98
|
+
* to advance the group-level active version on upgrade.
|
|
99
|
+
*/
|
|
100
|
+
export declare function hasDeviceFollowOverride(prefs: IDevicePackagePrefs | undefined): boolean;
|
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Device-local package version resolver.
|
|
4
4
|
*
|
|
5
|
-
* Each device
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the group
|
|
5
|
+
* Each device resolves which package version to run from the shared group
|
|
6
|
+
* package settings plus optional device-local prefs. When an admin device
|
|
7
|
+
* upgrades a non-dev version while following group settings, the shared
|
|
8
|
+
* `IPackage.activePackageVersionId` advances so the group stays aligned.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.packagePrefsVar = packagePrefsVar;
|
|
12
12
|
exports.updatePackagePrefs = updatePackagePrefs;
|
|
13
13
|
exports.getEffectivePackagePrefs = getEffectivePackagePrefs;
|
|
14
14
|
exports.resolveDevicePackageVersion = resolveDevicePackageVersion;
|
|
15
|
+
exports.hasDeviceFollowOverride = hasDeviceFollowOverride;
|
|
15
16
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
16
17
|
const assistants_1 = require("./assistants");
|
|
18
|
+
const group_member_roles_1 = require("./group-member-roles");
|
|
19
|
+
const group_permissions_1 = require("./group-permissions");
|
|
17
20
|
const package_versions_1 = require("./package-versions");
|
|
21
|
+
const packages_1 = require("./packages");
|
|
18
22
|
const persistent_vars_1 = require("./persistent-vars");
|
|
19
23
|
const workflows_1 = require("./workflows");
|
|
20
24
|
/**
|
|
@@ -129,6 +133,17 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
129
133
|
.get(effectiveActivePvId)
|
|
130
134
|
.catch(() => undefined);
|
|
131
135
|
}
|
|
136
|
+
// Dev versions are intentionally local working versions. If a device is
|
|
137
|
+
// currently running dev, keep loading that exact PV until the user manually
|
|
138
|
+
// activates a non-dev version.
|
|
139
|
+
if (activePv?.versionTag === "dev") {
|
|
140
|
+
const instance = await dataContext.packageLoader.loadPackage(pkg, {
|
|
141
|
+
force: opts?.force,
|
|
142
|
+
localPath: opts?.localPath,
|
|
143
|
+
packageVersionId: effectiveActivePvId,
|
|
144
|
+
});
|
|
145
|
+
return { loaded: !!instance, pv: activePv, upgraded: false };
|
|
146
|
+
}
|
|
132
147
|
// Evaluate all available PVs to find the best one
|
|
133
148
|
const allVersions = await (0, package_versions_1.PackageVersions)(dataContext).list({ packageId: pkg.packageId });
|
|
134
149
|
let bestPv = activePv;
|
|
@@ -178,9 +193,41 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
178
193
|
}
|
|
179
194
|
return { loaded: false, upgraded: false };
|
|
180
195
|
}
|
|
181
|
-
// Load succeeded - install assistants/workflows, then
|
|
196
|
+
// Load succeeded - install assistants/workflows, then persist the activation
|
|
197
|
+
// either as a group-level active version or as device-local state.
|
|
182
198
|
await installPackageContents(dataContext, pkg, instance);
|
|
183
|
-
|
|
199
|
+
// When the device is following group settings (no device-level follow
|
|
200
|
+
// override) and the upgraded version is non-dev, also advance the group's
|
|
201
|
+
// activePackageVersionId so other devices stay in sync. Only admin+ users
|
|
202
|
+
// are allowed to write to the Packages record.
|
|
203
|
+
let advancedGroupActiveVersion = false;
|
|
204
|
+
if (bestPv.versionTag !== "dev" && !hasDeviceFollowOverride(rawPrefs)) {
|
|
205
|
+
try {
|
|
206
|
+
const groupId = dataContext.groupId;
|
|
207
|
+
if (groupId) {
|
|
208
|
+
const userCtx = await (0, user_context_singleton_1.getUserContext)();
|
|
209
|
+
const role = await (0, group_permissions_1.getUserRole)(groupId, userCtx.userId);
|
|
210
|
+
if (role >= group_member_roles_1.GroupMemberRole.Admin) {
|
|
211
|
+
const freshPkg = await (0, packages_1.Packages)(dataContext).get(pkg.packageId);
|
|
212
|
+
if (freshPkg) {
|
|
213
|
+
if (freshPkg.activePackageVersionId !== bestPv.packageVersionId) {
|
|
214
|
+
freshPkg.activePackageVersionId = bestPv.packageVersionId;
|
|
215
|
+
await (0, packages_1.Packages)(dataContext).signAndSave(freshPkg);
|
|
216
|
+
}
|
|
217
|
+
advancedGroupActiveVersion = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Non-critical: personal contexts have no group, signature checks may
|
|
224
|
+
// fail for non-admins. Swallow and continue — the device upgrade
|
|
225
|
+
// already succeeded.
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
229
|
+
activePackageVersionId: advancedGroupActiveVersion ? undefined : bestPv.packageVersionId,
|
|
230
|
+
}, dataContext);
|
|
184
231
|
return { loaded: true, pv: bestPv, upgraded: true };
|
|
185
232
|
}
|
|
186
233
|
/**
|
|
@@ -197,6 +244,17 @@ async function installPackageContents(dataContext, pkg, instance) {
|
|
|
197
244
|
];
|
|
198
245
|
await Promise.all(saves);
|
|
199
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Returns true when the device has an explicit follow-policy override
|
|
249
|
+
* (`followRange`, `followTags`, or `pinned`). Used by the UI to drive the
|
|
250
|
+
* "Override on this device" toggle and by the resolver to decide whether
|
|
251
|
+
* to advance the group-level active version on upgrade.
|
|
252
|
+
*/
|
|
253
|
+
function hasDeviceFollowOverride(prefs) {
|
|
254
|
+
if (!prefs)
|
|
255
|
+
return false;
|
|
256
|
+
return prefs.followRange != null || prefs.followTags != null || prefs.pinned != null;
|
|
257
|
+
}
|
|
200
258
|
/** Convert `IPackage.versionFollowRange` to the resolver's range type. */
|
|
201
259
|
function rangeFromPkg(pkg) {
|
|
202
260
|
const r = pkg.versionFollowRange;
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Regression tests for device-local package version resolution.
|
|
3
|
-
*
|
|
4
|
-
* Several cases document known gaps from the device-local package versions review.
|
|
5
|
-
* They are expected to fail until the resolver honors group-level pinned, legacy
|
|
6
|
-
* deviceVersionTag, and pinned devices are not overridden before resolve.
|
|
7
3
|
*/
|
|
8
4
|
export {};
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Regression tests for device-local package version resolution.
|
|
4
|
-
*
|
|
5
|
-
* Several cases document known gaps from the device-local package versions review.
|
|
6
|
-
* They are expected to fail until the resolver honors group-level pinned, legacy
|
|
7
|
-
* deviceVersionTag, and pinned devices are not overridden before resolve.
|
|
8
4
|
*/
|
|
9
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
6
|
const SQLiteDB = require("better-sqlite3");
|
|
@@ -12,6 +8,8 @@ const user_context_1 = require("../context/user-context");
|
|
|
12
8
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
13
9
|
const utils_1 = require("../utils");
|
|
14
10
|
const assistants_1 = require("./assistants");
|
|
11
|
+
const group_member_roles_1 = require("./group-member-roles");
|
|
12
|
+
const groups_1 = require("./groups");
|
|
15
13
|
const sql_data_source_1 = require("./orm/sql.data-source");
|
|
16
14
|
const package_version_resolver_1 = require("./package-version-resolver");
|
|
17
15
|
const package_versions_1 = require("./package-versions");
|
|
@@ -51,6 +49,11 @@ function groupDc() {
|
|
|
51
49
|
dc.setAsDefault();
|
|
52
50
|
return dc;
|
|
53
51
|
}
|
|
52
|
+
function freshGroupDc() {
|
|
53
|
+
const dc = userContext.getDataContext((0, utils_1.newid)());
|
|
54
|
+
dc.setAsDefault();
|
|
55
|
+
return dc;
|
|
56
|
+
}
|
|
54
57
|
function makePV(packageId, overrides = {}) {
|
|
55
58
|
return {
|
|
56
59
|
packageVersionId: (0, utils_1.newid)(),
|
|
@@ -85,6 +88,7 @@ beforeAll(async () => {
|
|
|
85
88
|
userContext.deviceId((0, utils_1.newid)());
|
|
86
89
|
(0, user_context_singleton_1.setUserContext)(userContext);
|
|
87
90
|
packages_1.PackagesTable.isPassthrough = true;
|
|
91
|
+
packages_1.PackagesTable.enablePackageSigning((pkg) => pkg);
|
|
88
92
|
package_versions_1.PackageVersionsTable.isPassthrough = true;
|
|
89
93
|
});
|
|
90
94
|
afterAll(async () => {
|
|
@@ -96,7 +100,7 @@ afterAll(async () => {
|
|
|
96
100
|
afterEach(() => {
|
|
97
101
|
jest.restoreAllMocks();
|
|
98
102
|
});
|
|
99
|
-
describe("resolveDevicePackageVersion —
|
|
103
|
+
describe("resolveDevicePackageVersion — regression coverage", () => {
|
|
100
104
|
it("does not auto-upgrade when IPackage.versionFollowRange is pinned (no device prefs)", async () => {
|
|
101
105
|
const dc = groupDc();
|
|
102
106
|
const packageId = (0, utils_1.newid)();
|
|
@@ -177,6 +181,123 @@ describe("resolveDevicePackageVersion — known gaps (expected to fail until fix
|
|
|
177
181
|
});
|
|
178
182
|
});
|
|
179
183
|
describe("resolveDevicePackageVersion — expected behavior (control)", () => {
|
|
184
|
+
it("advances the group active version when an admin auto-upgrades without device overrides", async () => {
|
|
185
|
+
const dc = freshGroupDc();
|
|
186
|
+
const packageId = (0, utils_1.newid)();
|
|
187
|
+
const stablePv = makePV(packageId, { version: "1.0.0", versionTag: "stable" });
|
|
188
|
+
const stableV2 = makePV(packageId, { version: "2.0.0", versionTag: "stable" });
|
|
189
|
+
await (0, package_versions_1.PackageVersions)(dc).save(stablePv);
|
|
190
|
+
await (0, package_versions_1.PackageVersions)(dc).save(stableV2);
|
|
191
|
+
const pkg = makePkg({
|
|
192
|
+
packageId,
|
|
193
|
+
activePackageVersionId: stablePv.packageVersionId,
|
|
194
|
+
versionFollowRange: "latest",
|
|
195
|
+
followVersionTags: "stable",
|
|
196
|
+
});
|
|
197
|
+
await (0, packages_1.Packages)(dc).save(pkg);
|
|
198
|
+
jest.spyOn(dc.packageLoader, "loadPackage").mockResolvedValue({ packageId });
|
|
199
|
+
const result = await (0, package_version_resolver_1.resolveDevicePackageVersion)(pkg, dc, { force: true });
|
|
200
|
+
expect(result.upgraded).toBe(true);
|
|
201
|
+
expect(result.pv?.packageVersionId).toBe(stableV2.packageVersionId);
|
|
202
|
+
const savedPkg = await (0, packages_1.Packages)(dc).get(packageId);
|
|
203
|
+
expect(savedPkg?.activePackageVersionId).toBe(stableV2.packageVersionId);
|
|
204
|
+
const pvar = (0, package_version_resolver_1.packagePrefsVar)(packageId, dc);
|
|
205
|
+
await pvar.loadingPromise;
|
|
206
|
+
expect(pvar()?.activePackageVersionId).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
it("keeps an auto-upgrade device-local when device follow overrides are enabled", async () => {
|
|
209
|
+
const dc = freshGroupDc();
|
|
210
|
+
const packageId = (0, utils_1.newid)();
|
|
211
|
+
const stablePv = makePV(packageId, { version: "1.0.0", versionTag: "stable" });
|
|
212
|
+
const stableV2 = makePV(packageId, { version: "2.0.0", versionTag: "stable" });
|
|
213
|
+
await (0, package_versions_1.PackageVersions)(dc).save(stablePv);
|
|
214
|
+
await (0, package_versions_1.PackageVersions)(dc).save(stableV2);
|
|
215
|
+
const pkg = makePkg({
|
|
216
|
+
packageId,
|
|
217
|
+
activePackageVersionId: stablePv.packageVersionId,
|
|
218
|
+
versionFollowRange: "latest",
|
|
219
|
+
followVersionTags: "stable",
|
|
220
|
+
});
|
|
221
|
+
await (0, packages_1.Packages)(dc).save(pkg);
|
|
222
|
+
await (0, package_version_resolver_1.updatePackagePrefs)(packageId, { followRange: "latest", followTags: "stable" }, dc);
|
|
223
|
+
jest.spyOn(dc.packageLoader, "loadPackage").mockResolvedValue({ packageId });
|
|
224
|
+
const result = await (0, package_version_resolver_1.resolveDevicePackageVersion)(pkg, dc, { force: true });
|
|
225
|
+
expect(result.upgraded).toBe(true);
|
|
226
|
+
expect(result.pv?.packageVersionId).toBe(stableV2.packageVersionId);
|
|
227
|
+
const savedPkg = await (0, packages_1.Packages)(dc).get(packageId);
|
|
228
|
+
expect(savedPkg?.activePackageVersionId).toBe(stablePv.packageVersionId);
|
|
229
|
+
const pvar = (0, package_version_resolver_1.packagePrefsVar)(packageId, dc);
|
|
230
|
+
await pvar.loadingPromise;
|
|
231
|
+
expect(pvar()?.activePackageVersionId).toBe(stableV2.packageVersionId);
|
|
232
|
+
});
|
|
233
|
+
it("keeps an auto-upgrade device-local when the device user is not a group admin", async () => {
|
|
234
|
+
const dc = freshGroupDc();
|
|
235
|
+
const packageId = (0, utils_1.newid)();
|
|
236
|
+
const stablePv = makePV(packageId, { version: "1.0.0", versionTag: "stable" });
|
|
237
|
+
const stableV2 = makePV(packageId, { version: "2.0.0", versionTag: "stable" });
|
|
238
|
+
await (0, package_versions_1.PackageVersions)(dc).save(stablePv);
|
|
239
|
+
await (0, package_versions_1.PackageVersions)(dc).save(stableV2);
|
|
240
|
+
await (0, groups_1.Groups)(userContext.userDataContext).save({
|
|
241
|
+
groupId: dc.dataContextId,
|
|
242
|
+
name: "Non-admin resolver test",
|
|
243
|
+
description: "test",
|
|
244
|
+
founderUserId: (0, utils_1.newid)(),
|
|
245
|
+
publicRole: group_member_roles_1.GroupMemberRole.Writer,
|
|
246
|
+
publicKey: "",
|
|
247
|
+
publicBoxKey: "",
|
|
248
|
+
signature: "",
|
|
249
|
+
});
|
|
250
|
+
const pkg = makePkg({
|
|
251
|
+
packageId,
|
|
252
|
+
activePackageVersionId: stablePv.packageVersionId,
|
|
253
|
+
versionFollowRange: "latest",
|
|
254
|
+
followVersionTags: "stable",
|
|
255
|
+
});
|
|
256
|
+
await (0, packages_1.Packages)(dc).save(pkg);
|
|
257
|
+
jest.spyOn(dc.packageLoader, "loadPackage").mockResolvedValue({ packageId });
|
|
258
|
+
const result = await (0, package_version_resolver_1.resolveDevicePackageVersion)(pkg, dc, { force: true });
|
|
259
|
+
expect(result.upgraded).toBe(true);
|
|
260
|
+
expect(result.pv?.packageVersionId).toBe(stableV2.packageVersionId);
|
|
261
|
+
const savedPkg = await (0, packages_1.Packages)(dc).get(packageId);
|
|
262
|
+
expect(savedPkg?.activePackageVersionId).toBe(stablePv.packageVersionId);
|
|
263
|
+
const pvar = (0, package_version_resolver_1.packagePrefsVar)(packageId, dc);
|
|
264
|
+
await pvar.loadingPromise;
|
|
265
|
+
expect(pvar()?.activePackageVersionId).toBe(stableV2.packageVersionId);
|
|
266
|
+
});
|
|
267
|
+
it("keeps a local dev activation on this device without turning override on", async () => {
|
|
268
|
+
const dc = freshGroupDc();
|
|
269
|
+
const packageId = (0, utils_1.newid)();
|
|
270
|
+
const stablePv = makePV(packageId, { version: "1.0.0", versionTag: "stable" });
|
|
271
|
+
const devPv = makePV(packageId, { version: "1.2.0", versionTag: "dev" });
|
|
272
|
+
const stableV2 = makePV(packageId, { version: "2.0.0", versionTag: "stable" });
|
|
273
|
+
await (0, package_versions_1.PackageVersions)(dc).save(stablePv);
|
|
274
|
+
await (0, package_versions_1.PackageVersions)(dc).save(devPv);
|
|
275
|
+
await (0, package_versions_1.PackageVersions)(dc).save(stableV2);
|
|
276
|
+
const pkg = makePkg({
|
|
277
|
+
packageId,
|
|
278
|
+
activePackageVersionId: stablePv.packageVersionId,
|
|
279
|
+
versionFollowRange: "latest",
|
|
280
|
+
followVersionTags: "stable",
|
|
281
|
+
});
|
|
282
|
+
await (0, packages_1.Packages)(dc).save(pkg);
|
|
283
|
+
await (0, package_version_resolver_1.updatePackagePrefs)(packageId, { activePackageVersionId: devPv.packageVersionId }, dc);
|
|
284
|
+
const pvar = (0, package_version_resolver_1.packagePrefsVar)(packageId, dc);
|
|
285
|
+
await pvar.loadingPromise;
|
|
286
|
+
expect((0, package_version_resolver_1.hasDeviceFollowOverride)(pvar())).toBe(false);
|
|
287
|
+
const loadedPvIds = [];
|
|
288
|
+
jest.spyOn(dc.packageLoader, "loadPackage").mockImplementation(async (_pkg, opts) => {
|
|
289
|
+
if (opts?.packageVersionId)
|
|
290
|
+
loadedPvIds.push(opts.packageVersionId);
|
|
291
|
+
return { packageId };
|
|
292
|
+
});
|
|
293
|
+
const result = await (0, package_version_resolver_1.resolveDevicePackageVersion)(pkg, dc, { force: true });
|
|
294
|
+
expect(result.upgraded).toBe(false);
|
|
295
|
+
expect(result.pv?.packageVersionId).toBe(devPv.packageVersionId);
|
|
296
|
+
expect(loadedPvIds).toEqual([devPv.packageVersionId]);
|
|
297
|
+
const savedPkg = await (0, packages_1.Packages)(dc).get(packageId);
|
|
298
|
+
expect(savedPkg?.activePackageVersionId).toBe(stablePv.packageVersionId);
|
|
299
|
+
expect(pvar()?.activePackageVersionId).toBe(devPv.packageVersionId);
|
|
300
|
+
});
|
|
180
301
|
it("does not auto-activate dev when device has no prefs and group default is stable", async () => {
|
|
181
302
|
const dc = groupDc();
|
|
182
303
|
const packageId = (0, utils_1.newid)();
|
package/dist/data/packages.d.ts
CHANGED
|
@@ -9,23 +9,6 @@ declare const schema: z.ZodObject<{
|
|
|
9
9
|
createdBy: z.ZodEffects<z.ZodString, string, string>;
|
|
10
10
|
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
11
11
|
remoteRepo: z.ZodOptional<z.ZodString>;
|
|
12
|
-
/** @deprecated Read appNavs from the active IPackageVersion record instead. */
|
|
13
|
-
appNavs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
14
|
-
name: z.ZodString;
|
|
15
|
-
displayName: z.ZodOptional<z.ZodString>;
|
|
16
|
-
iconClassName: z.ZodString;
|
|
17
|
-
navigationPath: z.ZodString;
|
|
18
|
-
}, "strip", z.ZodTypeAny, {
|
|
19
|
-
name: string;
|
|
20
|
-
iconClassName: string;
|
|
21
|
-
navigationPath: string;
|
|
22
|
-
displayName?: string | undefined;
|
|
23
|
-
}, {
|
|
24
|
-
name: string;
|
|
25
|
-
iconClassName: string;
|
|
26
|
-
navigationPath: string;
|
|
27
|
-
displayName?: string | undefined;
|
|
28
|
-
}>, "many">>;
|
|
29
12
|
activePackageVersionId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
30
13
|
versionFollowRange: z.ZodOptional<z.ZodEnum<["pinned", "patch", "minor", "latest"]>>;
|
|
31
14
|
followVersionTags: z.ZodOptional<z.ZodString>;
|
|
@@ -40,12 +23,6 @@ declare const schema: z.ZodObject<{
|
|
|
40
23
|
createdBy: string;
|
|
41
24
|
publishPublicKey: string;
|
|
42
25
|
disabled?: boolean | undefined;
|
|
43
|
-
appNavs?: {
|
|
44
|
-
name: string;
|
|
45
|
-
iconClassName: string;
|
|
46
|
-
navigationPath: string;
|
|
47
|
-
displayName?: string | undefined;
|
|
48
|
-
}[] | undefined;
|
|
49
26
|
remoteRepo?: string | undefined;
|
|
50
27
|
activePackageVersionId?: string | undefined;
|
|
51
28
|
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
|
@@ -59,12 +36,6 @@ declare const schema: z.ZodObject<{
|
|
|
59
36
|
createdBy: string;
|
|
60
37
|
publishPublicKey: string;
|
|
61
38
|
disabled?: boolean | undefined;
|
|
62
|
-
appNavs?: {
|
|
63
|
-
name: string;
|
|
64
|
-
iconClassName: string;
|
|
65
|
-
navigationPath: string;
|
|
66
|
-
displayName?: string | undefined;
|
|
67
|
-
}[] | undefined;
|
|
68
39
|
remoteRepo?: string | undefined;
|
|
69
40
|
activePackageVersionId?: string | undefined;
|
|
70
41
|
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
package/dist/data/packages.js
CHANGED
|
@@ -38,7 +38,6 @@ exports.autoUpdatePeersCore = exports.reloadPackagesOnPageRefresh = exports.pack
|
|
|
38
38
|
exports.Packages = Packages;
|
|
39
39
|
const zod_1 = require("zod");
|
|
40
40
|
const context_1 = require("../context");
|
|
41
|
-
const app_nav_1 = require("../types/app-nav");
|
|
42
41
|
const zod_types_1 = require("../types/zod-types");
|
|
43
42
|
const decorators_1 = require("./orm/decorators");
|
|
44
43
|
const table_1 = require("./orm/table");
|
|
@@ -56,11 +55,6 @@ const schema = zod_1.z.object({
|
|
|
56
55
|
.optional()
|
|
57
56
|
.describe("Whether the package's components should be loaded and included in the app runtime"),
|
|
58
57
|
remoteRepo: zod_1.z.string().optional().describe("The remote repository where the package is stored"),
|
|
59
|
-
/** @deprecated Read appNavs from the active IPackageVersion record instead. */
|
|
60
|
-
appNavs: app_nav_1.appNavSchema
|
|
61
|
-
.array()
|
|
62
|
-
.optional()
|
|
63
|
-
.describe("DEPRECATED: Read appNavs from the active IPackageVersion record instead"),
|
|
64
58
|
activePackageVersionId: zod_types_1.zodPeerId
|
|
65
59
|
.optional()
|
|
66
60
|
.describe("FK to PackageVersions — the currently active version"),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IPackageVersion } from "../data/package-versions";
|
|
2
|
+
import type { IAppNav } from "../types/app-nav";
|
|
2
3
|
/** Prefix for the persistent variable name that stores a package's signing secret key. */
|
|
3
4
|
export declare const PACKAGE_SIGNING_KEY_PREFIX = "packageSigningKey_";
|
|
4
5
|
/**
|
|
@@ -14,18 +15,23 @@ export interface IAuthorSignedPayload {
|
|
|
14
15
|
routesBundleFileHash?: string;
|
|
15
16
|
uiBundleFileHash?: string;
|
|
16
17
|
publicKey: string;
|
|
18
|
+
appNavs?: IAppNav[];
|
|
19
|
+
createdBy?: string;
|
|
20
|
+
createdAt?: string;
|
|
17
21
|
}
|
|
22
|
+
/** PV fields required by {@link buildAuthorSignedPayload} and related signing/verification functions. */
|
|
23
|
+
export type AuthorSignableFields = Pick<IPackageVersion, "packageId" | "packageVersionId" | "version" | "versionTag" | "packageBundleFileHash" | "routesBundleFileHash" | "uiBundleFileHash" | "appNavs"> & Partial<Pick<IPackageVersion, "createdBy" | "createdAt">>;
|
|
18
24
|
/**
|
|
19
25
|
* Extracts the signable payload from a PackageVersion record.
|
|
20
|
-
* Only includes optional
|
|
26
|
+
* Only includes optional fields when they have a value (undefined fields are omitted
|
|
21
27
|
* entirely so `stableStringify` produces a consistent output).
|
|
22
28
|
*/
|
|
23
|
-
export declare function buildAuthorSignedPayload(pv:
|
|
29
|
+
export declare function buildAuthorSignedPayload(pv: AuthorSignableFields, publicKey: string): IAuthorSignedPayload;
|
|
24
30
|
/**
|
|
25
31
|
* Signs a PackageVersion with the publisher's Ed25519 secret key.
|
|
26
32
|
* @returns A base64url-encoded detached Ed25519 signature over `stableStringify(payload)`.
|
|
27
33
|
*/
|
|
28
|
-
export declare function signPackageAuthor(pv:
|
|
34
|
+
export declare function signPackageAuthor(pv: AuthorSignableFields, secretKey: string): string;
|
|
29
35
|
/**
|
|
30
36
|
* Result of verifying a packageAuthorSignature.
|
|
31
37
|
* `publicKey` is the key embedded in the payload (extracted from the signature verification process).
|
|
@@ -45,4 +51,4 @@ export interface IVerifyAuthorSignatureResult {
|
|
|
45
51
|
* @param expectedPublicKey - The public key to verify against (from Package.publishPublicKey)
|
|
46
52
|
* @returns `{ valid, publicKey }` — valid is true if signature verifies and key matches
|
|
47
53
|
*/
|
|
48
|
-
export declare function verifyPackageAuthorSignature(pv: Pick<IPackageVersion, "
|
|
54
|
+
export declare function verifyPackageAuthorSignature(pv: AuthorSignableFields & Pick<IPackageVersion, "packageAuthorSignature">, expectedPublicKey: string): IVerifyAuthorSignatureResult;
|
|
@@ -11,7 +11,7 @@ const keys_1 = require("../keys");
|
|
|
11
11
|
exports.PACKAGE_SIGNING_KEY_PREFIX = "packageSigningKey_";
|
|
12
12
|
/**
|
|
13
13
|
* Extracts the signable payload from a PackageVersion record.
|
|
14
|
-
* Only includes optional
|
|
14
|
+
* Only includes optional fields when they have a value (undefined fields are omitted
|
|
15
15
|
* entirely so `stableStringify` produces a consistent output).
|
|
16
16
|
*/
|
|
17
17
|
function buildAuthorSignedPayload(pv, publicKey) {
|
|
@@ -29,6 +29,15 @@ function buildAuthorSignedPayload(pv, publicKey) {
|
|
|
29
29
|
if (pv.uiBundleFileHash) {
|
|
30
30
|
payload.uiBundleFileHash = pv.uiBundleFileHash;
|
|
31
31
|
}
|
|
32
|
+
if (pv.appNavs) {
|
|
33
|
+
payload.appNavs = pv.appNavs;
|
|
34
|
+
}
|
|
35
|
+
if (pv.createdBy) {
|
|
36
|
+
payload.createdBy = pv.createdBy;
|
|
37
|
+
}
|
|
38
|
+
if (pv.createdAt) {
|
|
39
|
+
payload.createdAt = pv.createdAt;
|
|
40
|
+
}
|
|
32
41
|
return payload;
|
|
33
42
|
}
|
|
34
43
|
/**
|
|
@@ -342,6 +342,7 @@ function buildPropagatedPv(pv, files) {
|
|
|
342
342
|
routesBundleFileHash: routesBundle?.expectedHash,
|
|
343
343
|
uiBundleFileId: uiBundle?.file.fileId,
|
|
344
344
|
uiBundleFileHash: uiBundle?.expectedHash,
|
|
345
|
+
appNavs: pv.appNavs,
|
|
345
346
|
packageAuthorSignature: pv.packageAuthorSignature,
|
|
346
347
|
signature: "",
|
|
347
348
|
createdBy: pv.createdBy,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* I/O — callers (the system tool, tests) provide bundle strings and receive
|
|
7
7
|
* artifacts in memory.
|
|
8
8
|
*/
|
|
9
|
+
import type { IAppNav } from "../types/app-nav";
|
|
9
10
|
import { type IAuthorSignedPayload } from "./package-author-signing";
|
|
10
11
|
import type { ILatestPointer } from "./package-remote-checker";
|
|
11
12
|
import { type ITarballPayload } from "./package-tarball";
|
|
@@ -30,6 +31,10 @@ export interface IBuildPublishArtifactsOpts {
|
|
|
30
31
|
publicKey: string;
|
|
31
32
|
/** Pre-generated packageVersionId. If omitted, a new one is created via {@link newid}. */
|
|
32
33
|
packageVersionId?: string;
|
|
34
|
+
/** App navigation items to include in the tarball metadata. */
|
|
35
|
+
appNavs?: IAppNav[];
|
|
36
|
+
/** Peer ID of the publisher. Recorded as `createdBy` on the PV. */
|
|
37
|
+
createdBy?: string;
|
|
33
38
|
}
|
|
34
39
|
/** Artifacts produced by {@link buildPublishArtifacts}. */
|
|
35
40
|
export interface IPublishArtifacts {
|
|
@@ -32,6 +32,7 @@ async function buildPublishArtifacts(opts) {
|
|
|
32
32
|
? (0, file_types_1.computeFileHash)(bundles.routesBundle)
|
|
33
33
|
: undefined;
|
|
34
34
|
const uiBundleFileHash = bundles.uiBundle ? (0, file_types_1.computeFileHash)(bundles.uiBundle) : undefined;
|
|
35
|
+
const now = new Date().toISOString();
|
|
35
36
|
const pvFields = {
|
|
36
37
|
packageId,
|
|
37
38
|
packageVersionId,
|
|
@@ -40,6 +41,9 @@ async function buildPublishArtifacts(opts) {
|
|
|
40
41
|
packageBundleFileHash,
|
|
41
42
|
routesBundleFileHash,
|
|
42
43
|
uiBundleFileHash,
|
|
44
|
+
appNavs: opts.appNavs,
|
|
45
|
+
createdBy: opts.createdBy,
|
|
46
|
+
createdAt: now,
|
|
43
47
|
};
|
|
44
48
|
const payload = (0, package_author_signing_1.buildAuthorSignedPayload)(pvFields, publicKey);
|
|
45
49
|
const packageAuthorSignature = await sign(payload);
|
|
@@ -14,6 +14,7 @@ const file_types_1 = require("../data/files/file.types");
|
|
|
14
14
|
const package_versions_1 = require("../data/package-versions");
|
|
15
15
|
const packages_1 = require("../data/packages");
|
|
16
16
|
const keys_1 = require("../keys");
|
|
17
|
+
const package_version_resolver_1 = require("../data/package-version-resolver");
|
|
17
18
|
const package_author_signing_1 = require("./package-author-signing");
|
|
18
19
|
const package_installer_1 = require("./package-installer");
|
|
19
20
|
const package_propagation_1 = require("./package-propagation");
|
|
@@ -84,6 +85,9 @@ async function doRemoteCheck(packageId, updateUrl, tag, http) {
|
|
|
84
85
|
packageBundleFileHash: unpacked.payload.packageBundleFileHash,
|
|
85
86
|
routesBundleFileHash: unpacked.payload.routesBundleFileHash,
|
|
86
87
|
uiBundleFileHash: unpacked.payload.uiBundleFileHash,
|
|
88
|
+
appNavs: unpacked.payload.appNavs,
|
|
89
|
+
createdBy: unpacked.payload.createdBy,
|
|
90
|
+
createdAt: unpacked.payload.createdAt,
|
|
87
91
|
packageAuthorSignature: unpacked.payload.packageAuthorSignature,
|
|
88
92
|
}, unpacked.payload.publicKey);
|
|
89
93
|
if (!sigResult.valid) {
|
|
@@ -160,10 +164,11 @@ async function installRemotePackageVersion(dataContext, result) {
|
|
|
160
164
|
routesBundleFileHash: routesFile?.fileHash,
|
|
161
165
|
uiBundleFileId: uiFile?.fileId,
|
|
162
166
|
uiBundleFileHash: uiFile?.fileHash,
|
|
167
|
+
appNavs: unpacked.payload.appNavs,
|
|
163
168
|
packageAuthorSignature: unpacked.payload.packageAuthorSignature,
|
|
164
169
|
signature: "",
|
|
165
|
-
createdBy: packageId,
|
|
166
|
-
createdAt: new Date().toISOString(),
|
|
170
|
+
createdBy: unpacked.payload.createdBy ?? packageId,
|
|
171
|
+
createdAt: unpacked.payload.createdAt ?? new Date().toISOString(),
|
|
167
172
|
};
|
|
168
173
|
const pvTable = (0, package_versions_1.PackageVersions)(dataContext);
|
|
169
174
|
const savedPv = await pvTable.signAndSave(pv);
|
|
@@ -173,8 +178,11 @@ async function installRemotePackageVersion(dataContext, result) {
|
|
|
173
178
|
await (0, packages_1.Packages)(dataContext).signAndSave(pkg);
|
|
174
179
|
}
|
|
175
180
|
const activationResult = await (0, package_propagation_1.activateBestEligibleVersion)(dataContext, pkg);
|
|
181
|
+
// Also run the device-level resolver so this device picks up the new version
|
|
182
|
+
// (activateBestEligibleVersion only updates the group-level default).
|
|
183
|
+
const resolveResult = await (0, package_version_resolver_1.resolveDevicePackageVersion)(pkg, dataContext, { force: true });
|
|
176
184
|
return {
|
|
177
|
-
activated: activationResult?.activated
|
|
185
|
+
activated: activationResult?.activated || resolveResult.upgraded,
|
|
178
186
|
packageVersion: savedPv,
|
|
179
187
|
};
|
|
180
188
|
}
|
|
@@ -28,6 +28,10 @@ const mockActivateBestEligibleVersion = jest.fn();
|
|
|
28
28
|
jest.mock("./package-propagation", () => ({
|
|
29
29
|
activateBestEligibleVersion: (...args) => mockActivateBestEligibleVersion(...args),
|
|
30
30
|
}));
|
|
31
|
+
const mockResolveDevicePackageVersion = jest.fn();
|
|
32
|
+
jest.mock("../data/package-version-resolver", () => ({
|
|
33
|
+
resolveDevicePackageVersion: (...args) => mockResolveDevicePackageVersion(...args),
|
|
34
|
+
}));
|
|
31
35
|
const file_types_1 = require("../data/files/file.types");
|
|
32
36
|
const keys_1 = require("../keys");
|
|
33
37
|
const package_author_signing_1 = require("./package-author-signing");
|
|
@@ -96,6 +100,7 @@ describe("package-remote-checker", () => {
|
|
|
96
100
|
activated: true,
|
|
97
101
|
activePackageVersionId: PV_ID,
|
|
98
102
|
});
|
|
103
|
+
mockResolveDevicePackageVersion.mockResolvedValue({ loaded: true, upgraded: true });
|
|
99
104
|
});
|
|
100
105
|
describe("checkPackageRemoteForNewVersion", () => {
|
|
101
106
|
it("downloads and verifies tarball when new version available", async () => {
|
|
@@ -258,6 +263,7 @@ describe("package-remote-checker", () => {
|
|
|
258
263
|
expect(installed.activated).toBe(true);
|
|
259
264
|
expect(mockPackagesSignAndSave).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ publishPublicKey: keys.publicKey }));
|
|
260
265
|
expect(mockPackageVersionsSignAndSave).toHaveBeenCalled();
|
|
266
|
+
expect(mockResolveDevicePackageVersion).toHaveBeenCalledWith(expect.objectContaining({ packageId: PKG_ID }), expect.anything(), { force: true });
|
|
261
267
|
});
|
|
262
268
|
});
|
|
263
269
|
});
|