@peers-app/peers-sdk 0.26.3 → 0.26.5
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/contracts/__tests__/contract-resolvers.test.js +24 -0
- package/dist/contracts/__tests__/legacy-removal.test.js +38 -0
- package/dist/contracts/__tests__/remote-contract-transport.test.js +23 -32
- package/dist/contracts/__tests__/system-contracts.test.js +80 -0
- package/dist/contracts/contract-resolvers.d.ts +1 -1
- package/dist/contracts/contract-resolvers.js +3 -2
- package/dist/contracts/create-local-consumer.d.ts +2 -1
- package/dist/contracts/create-local-consumer.js +1 -1
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/remote-contract-transport.d.ts +3 -3
- package/dist/contracts/remote-contract-transport.js +19 -17
- package/dist/contracts/system/host-shell-contract.d.ts +191 -0
- package/dist/contracts/system/host-shell-contract.js +190 -0
- package/dist/contracts/system/index.d.ts +4 -0
- package/dist/contracts/system/index.js +4 -0
- package/dist/contracts/system/packages-contract.d.ts +257 -0
- package/dist/contracts/system/packages-contract.js +213 -0
- package/dist/data/create-group.d.ts +1 -1
- package/dist/data/create-group.js +1 -1
- package/dist/data/package-version-resolver.d.ts +8 -2
- package/dist/data/package-version-resolver.js +28 -2
- package/dist/data/package-version-resolver.test.js +42 -0
- package/dist/package-installer/package-author-signer.d.ts +19 -0
- package/dist/package-installer/package-author-signer.js +24 -0
- package/dist/package-installer/package-creator.d.ts +1 -7
- package/dist/package-installer/package-creator.js +0 -12
- package/dist/package-installer/package-creator.test.js +0 -16
- package/dist/package-installer/package-installer.d.ts +5 -0
- package/dist/package-installer/package-installer.js +60 -37
- package/dist/package-installer/package-installer.test.js +129 -0
- package/dist/package-installer/types.d.ts +5 -11
- package/dist/package-loader/index.d.ts +1 -0
- package/dist/package-loader/index.js +1 -0
- package/dist/package-loader/isolated-runtime.d.ts +18 -0
- package/dist/package-loader/package-loader.d.ts +67 -4
- package/dist/package-loader/package-loader.js +300 -52
- package/dist/package-loader/package-loader.test.js +413 -1
- package/dist/package-loader/package-runtime-state.d.ts +112 -0
- package/dist/package-loader/package-runtime-state.js +18 -0
- package/dist/package-loader/resolve-consumed-contracts.js +5 -4
- package/dist/package-loader/resolve-consumed-contracts.test.js +1 -1
- package/dist/rpc-types.d.ts +0 -121
- package/dist/rpc-types.js +0 -39
- package/dist/system-ids.d.ts +13 -0
- package/dist/system-ids.js +14 -1
- package/package.json +1 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PACKAGES_CONTRACT_VERSION = void 0;
|
|
4
|
+
exports.packageVersionPromotionTargets = packageVersionPromotionTargets;
|
|
5
|
+
exports.isPackageRepositoryUrl = isPackageRepositoryUrl;
|
|
6
|
+
exports.getPackagesContractDefinition = getPackagesContractDefinition;
|
|
7
|
+
const system_ids_1 = require("../../system-ids");
|
|
8
|
+
const field_type_1 = require("../../types/field-type");
|
|
9
|
+
const system_contracts_1 = require("../system-contracts");
|
|
10
|
+
/** Version of the built-in System Packages contract. */
|
|
11
|
+
exports.PACKAGES_CONTRACT_VERSION = 1;
|
|
12
|
+
/**
|
|
13
|
+
* Release channels a version may be promoted to from its current tag: `dev` to `beta` or
|
|
14
|
+
* `stable`, and `beta` to `stable`. Missing or empty tags on legacy versions mean `dev`,
|
|
15
|
+
* matching the Versions screen. Promotion never demotes.
|
|
16
|
+
*
|
|
17
|
+
* @param versionTag - The version's current tag.
|
|
18
|
+
*/
|
|
19
|
+
function packageVersionPromotionTargets(versionTag) {
|
|
20
|
+
switch (versionTag || "dev") {
|
|
21
|
+
case "dev":
|
|
22
|
+
return ["beta", "stable"];
|
|
23
|
+
case "beta":
|
|
24
|
+
return ["stable"];
|
|
25
|
+
default:
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether user input names a remote repository for `clonePackage` (`http(s)://` or
|
|
31
|
+
* `git@host:`) rather than a local folder or package name. The host still validates the
|
|
32
|
+
* URL; this only routes input in the CLI and UI the same way.
|
|
33
|
+
*
|
|
34
|
+
* @param target - Input as typed.
|
|
35
|
+
*/
|
|
36
|
+
function isPackageRepositoryUrl(target) {
|
|
37
|
+
return /^(https?:\/\/|git@[^:/\s]+:)/.test(target.trim());
|
|
38
|
+
}
|
|
39
|
+
const field = (name, type, description, opts = {}) => ({ name, type, description, ...opts });
|
|
40
|
+
const packageIdField = field("packageId", field_type_1.FieldType.id, "Package id.");
|
|
41
|
+
const operationResultFields = [
|
|
42
|
+
packageIdField,
|
|
43
|
+
field("created", field_type_1.FieldType.boolean, "True when a new package was scaffolded.", {
|
|
44
|
+
optional: true,
|
|
45
|
+
}),
|
|
46
|
+
field("warning", field_type_1.FieldType.string, "Non-fatal problem to show the caller.", { optional: true }),
|
|
47
|
+
field("package", field_type_1.FieldType.object, "The package after the operation."),
|
|
48
|
+
];
|
|
49
|
+
let definition;
|
|
50
|
+
/**
|
|
51
|
+
* Return the System Packages contract shape.
|
|
52
|
+
*
|
|
53
|
+
* Tools carry no `remoteAccessLevel`, so only callers this device trusts as `Self` (the
|
|
54
|
+
* user's own devices and users explicitly granted Self) can reach them remotely; group
|
|
55
|
+
* members, Admins included, are refused. Package code and workflows are refused
|
|
56
|
+
* everywhere. The provider binds every call to the route's
|
|
57
|
+
* data context; a package's group is the data boundary. Tools the host cannot perform
|
|
58
|
+
* throw `System Packages <tool> is not supported on this host (...)`; `getCapabilities`
|
|
59
|
+
* reports support up front.
|
|
60
|
+
*/
|
|
61
|
+
function getPackagesContractDefinition() {
|
|
62
|
+
definition ??= {
|
|
63
|
+
contractId: system_ids_1.packagesContractId,
|
|
64
|
+
version: exports.PACKAGES_CONTRACT_VERSION,
|
|
65
|
+
devTag: "dev",
|
|
66
|
+
name: "System Packages",
|
|
67
|
+
description: "Install, inspect, reload, activate, and uninstall packages, with each device's observed runtime state.",
|
|
68
|
+
tables: [],
|
|
69
|
+
tools: [
|
|
70
|
+
{
|
|
71
|
+
name: "getCapabilities",
|
|
72
|
+
usageDescription: "Report which package operations this host supports.",
|
|
73
|
+
inputFields: [],
|
|
74
|
+
outputFields: [field("capabilities", field_type_1.FieldType.object, "Supported operations.")],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "listPackages",
|
|
78
|
+
usageDescription: "List packages in this data context with their requested version and runtime state on this device.",
|
|
79
|
+
inputFields: [],
|
|
80
|
+
outputFields: [
|
|
81
|
+
field("packages", field_type_1.FieldType.object, "Package summaries.", { isArray: true }),
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "getPackage",
|
|
86
|
+
usageDescription: "Return one package with its versions, local folder, and runtime state.",
|
|
87
|
+
inputFields: [packageIdField],
|
|
88
|
+
outputFields: [field("package", field_type_1.FieldType.object, "Package details.")],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "importPackage",
|
|
92
|
+
usageDescription: "Import an existing package folder on this device and load it.",
|
|
93
|
+
inputFields: [
|
|
94
|
+
field("path", field_type_1.FieldType.string, "Absolute or ~-prefixed folder path on the host."),
|
|
95
|
+
field("name", field_type_1.FieldType.string, "Display name; defaults to package.json.", {
|
|
96
|
+
optional: true,
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
outputFields: operationResultFields,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "createPackage",
|
|
103
|
+
usageDescription: "Scaffold a new package from the template, or import the folder when it already exists.",
|
|
104
|
+
inputFields: [
|
|
105
|
+
field("name", field_type_1.FieldType.string, "Package display name."),
|
|
106
|
+
field("location", field_type_1.FieldType.string, "Folder; defaults to the packages directory.", {
|
|
107
|
+
optional: true,
|
|
108
|
+
}),
|
|
109
|
+
],
|
|
110
|
+
outputFields: operationResultFields,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "clonePackage",
|
|
114
|
+
usageDescription: "Clone a package repository, build it, and import it.",
|
|
115
|
+
inputFields: [field("url", field_type_1.FieldType.string, "Git repository URL.")],
|
|
116
|
+
outputFields: operationResultFields,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "installPackage",
|
|
120
|
+
usageDescription: "Re-enable a disabled package, or copy one here from another of your data contexts, then load it.",
|
|
121
|
+
inputFields: [
|
|
122
|
+
packageIdField,
|
|
123
|
+
field("fromDataContextId", field_type_1.FieldType.id, "Context to copy from; searched when omitted.", {
|
|
124
|
+
optional: true,
|
|
125
|
+
}),
|
|
126
|
+
],
|
|
127
|
+
outputFields: operationResultFields,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "activatePackageVersion",
|
|
131
|
+
usageDescription: "Select a stored version for this device or the whole group, then load it on this device.",
|
|
132
|
+
inputFields: [
|
|
133
|
+
packageIdField,
|
|
134
|
+
field("packageVersionId", field_type_1.FieldType.id, "Version to activate; omit with device scope to follow the group again.", { optional: true }),
|
|
135
|
+
field("scope", field_type_1.FieldType.string, "device (default) or group.", { optional: true }),
|
|
136
|
+
],
|
|
137
|
+
outputFields: operationResultFields,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "reloadPackage",
|
|
141
|
+
usageDescription: "Rebuild the dev version from the package folder when it changed, then force-reload the package on this device.",
|
|
142
|
+
inputFields: [packageIdField],
|
|
143
|
+
outputFields: operationResultFields,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "reloadAllPackages",
|
|
147
|
+
usageDescription: "Re-read every package folder on this device and reload the packages whose bundles changed.",
|
|
148
|
+
inputFields: [],
|
|
149
|
+
outputFields: [
|
|
150
|
+
field("skipped", field_type_1.FieldType.string, "Why the host skipped the reload.", {
|
|
151
|
+
optional: true,
|
|
152
|
+
}),
|
|
153
|
+
field("results", field_type_1.FieldType.object, "Per-package outcomes.", { isArray: true }),
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "linkRemoteRepo",
|
|
158
|
+
usageDescription: "Create a remote repository for a local package and link it.",
|
|
159
|
+
inputFields: [packageIdField],
|
|
160
|
+
outputFields: operationResultFields,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "uninstallPackage",
|
|
164
|
+
usageDescription: "Remove a package from this data context for every member device and unload it here. Versions, files, and device preferences are kept.",
|
|
165
|
+
inputFields: [packageIdField],
|
|
166
|
+
outputFields: [
|
|
167
|
+
packageIdField,
|
|
168
|
+
field("removed", field_type_1.FieldType.boolean, "True when the package row was removed."),
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "promotePackageVersion",
|
|
173
|
+
usageDescription: "Promote a stored package version to beta or stable (dev to beta or stable, beta to stable). Re-signs it with the package author key when this device has it, records the promotion in its history, and activates it when this device was running it and the group follows the new tag.",
|
|
174
|
+
inputFields: [
|
|
175
|
+
field("packageVersionId", field_type_1.FieldType.id, "Version to promote."),
|
|
176
|
+
field("versionTag", field_type_1.FieldType.string, "New release channel: beta or stable."),
|
|
177
|
+
],
|
|
178
|
+
outputFields: [
|
|
179
|
+
...operationResultFields,
|
|
180
|
+
field("packageVersionId", field_type_1.FieldType.id, "Promoted version."),
|
|
181
|
+
field("version", field_type_1.FieldType.string, "Its version number."),
|
|
182
|
+
field("versionTag", field_type_1.FieldType.string, "Its new release channel."),
|
|
183
|
+
field("signed", field_type_1.FieldType.boolean, "True when this device signed the promoted version."),
|
|
184
|
+
field("activated", field_type_1.FieldType.boolean, "True when the promotion also activated the version for the group or this device."),
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "installDefaultPackages",
|
|
189
|
+
usageDescription: "Install the default packages into this data context.",
|
|
190
|
+
inputFields: [],
|
|
191
|
+
outputFields: [
|
|
192
|
+
field("installed", field_type_1.FieldType.boolean, "True when anything was installed or updated."),
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
observables: [
|
|
197
|
+
{
|
|
198
|
+
name: "runtimeStates",
|
|
199
|
+
description: "Runtime state of this context's packages on this device, keyed by package id.",
|
|
200
|
+
valueType: field_type_1.FieldType.object,
|
|
201
|
+
writable: false,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
events: [],
|
|
205
|
+
};
|
|
206
|
+
return definition;
|
|
207
|
+
}
|
|
208
|
+
(0, system_contracts_1.registerSystemContract)({
|
|
209
|
+
providerPackageId: system_ids_1.peersSystemPackageId,
|
|
210
|
+
contractId: system_ids_1.packagesContractId,
|
|
211
|
+
version: exports.PACKAGES_CONTRACT_VERSION,
|
|
212
|
+
getDefinition: getPackagesContractDefinition,
|
|
213
|
+
});
|
|
@@ -19,6 +19,6 @@ export declare const GROUP_NAME_MAX_LENGTH = 100;
|
|
|
19
19
|
*
|
|
20
20
|
* Runs on a host with signing enabled (`GroupsTable.enableGroupSigning`); UIs reach it
|
|
21
21
|
* through the host rather than duplicating the steps. Package seeding for the new context
|
|
22
|
-
* (for example `
|
|
22
|
+
* (for example System Packages `installDefaultPackages`) is the caller's responsibility.
|
|
23
23
|
*/
|
|
24
24
|
export declare function createGroup(userContext: UserContext, input: CreateGroupInput): Promise<IGroup>;
|
|
@@ -20,7 +20,7 @@ exports.GROUP_NAME_MAX_LENGTH = 100;
|
|
|
20
20
|
*
|
|
21
21
|
* Runs on a host with signing enabled (`GroupsTable.enableGroupSigning`); UIs reach it
|
|
22
22
|
* through the host rather than duplicating the steps. Package seeding for the new context
|
|
23
|
-
* (for example `
|
|
23
|
+
* (for example System Packages `installDefaultPackages`) is the caller's responsibility.
|
|
24
24
|
*/
|
|
25
25
|
async function createGroup(userContext, input) {
|
|
26
26
|
const name = input.name.trim();
|
|
@@ -81,17 +81,23 @@ export interface IResolveResult {
|
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* Core resolver: evaluates available package versions against the device's
|
|
84
|
-
* follow policy and loads the best one.
|
|
85
|
-
*
|
|
84
|
+
* follow policy and loads the best one. A dev version runs only on a device whose own
|
|
85
|
+
* selection points at it; a group selection of a dev version is left unloaded here.
|
|
86
86
|
*
|
|
87
87
|
* Callers:
|
|
88
88
|
* - `PackagesTrackedDataSource.applyChanges` (on sync)
|
|
89
89
|
* - `PackageLoader.loadAllPackages` (startup)
|
|
90
90
|
* - `package-installer.ts` (after saving new bundles)
|
|
91
|
+
*
|
|
92
|
+
* @param opts.force - Reload even when the package is already loaded.
|
|
93
|
+
* @param opts.localPath - Package folder whose rebuilt dev bundle wins over the stored one.
|
|
94
|
+
* @param opts.onlyIfChanged - With `force`, keep a running package whose selected version and
|
|
95
|
+
* bundle are unchanged instead of restarting it (sync reconciliation).
|
|
91
96
|
*/
|
|
92
97
|
export declare function resolveDevicePackageVersion(pkg: IPackage, dataContext: DataContext, opts?: {
|
|
93
98
|
force?: boolean;
|
|
94
99
|
localPath?: string;
|
|
100
|
+
onlyIfChanged?: boolean;
|
|
95
101
|
}): Promise<IResolveResult>;
|
|
96
102
|
/**
|
|
97
103
|
* Returns true when the device has an explicit follow-policy override
|
|
@@ -85,13 +85,18 @@ function getEffectivePackagePrefs(pkg, devicePrefs) {
|
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
87
|
* Core resolver: evaluates available package versions against the device's
|
|
88
|
-
* follow policy and loads the best one.
|
|
89
|
-
*
|
|
88
|
+
* follow policy and loads the best one. A dev version runs only on a device whose own
|
|
89
|
+
* selection points at it; a group selection of a dev version is left unloaded here.
|
|
90
90
|
*
|
|
91
91
|
* Callers:
|
|
92
92
|
* - `PackagesTrackedDataSource.applyChanges` (on sync)
|
|
93
93
|
* - `PackageLoader.loadAllPackages` (startup)
|
|
94
94
|
* - `package-installer.ts` (after saving new bundles)
|
|
95
|
+
*
|
|
96
|
+
* @param opts.force - Reload even when the package is already loaded.
|
|
97
|
+
* @param opts.localPath - Package folder whose rebuilt dev bundle wins over the stored one.
|
|
98
|
+
* @param opts.onlyIfChanged - With `force`, keep a running package whose selected version and
|
|
99
|
+
* bundle are unchanged instead of restarting it (sync reconciliation).
|
|
95
100
|
*/
|
|
96
101
|
async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
97
102
|
const pvar = packagePrefsVar(pkg.packageId, dataContext);
|
|
@@ -118,6 +123,21 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
118
123
|
if (!effectiveActivePvId) {
|
|
119
124
|
return { loaded: false, upgraded: false };
|
|
120
125
|
}
|
|
126
|
+
// A dev version runs only on a device that activated it. The group's selection can be a
|
|
127
|
+
// dev version (a new package, or one copied from another context); other devices leave it
|
|
128
|
+
// unloaded until they activate it themselves. Once activated here, rebuilds of that same
|
|
129
|
+
// version row keep loading through the dev branch below.
|
|
130
|
+
if (rawPrefs.activePackageVersionId !== effectiveActivePvId) {
|
|
131
|
+
const groupSelection = await (0, package_versions_1.PackageVersions)(dataContext)
|
|
132
|
+
.get(effectiveActivePvId)
|
|
133
|
+
.catch(() => undefined);
|
|
134
|
+
if (groupSelection?.versionTag === "dev") {
|
|
135
|
+
await dataContext.packageLoader.unloadPackage(pkg.packageId, {
|
|
136
|
+
reason: `Only a dev version (v${groupSelection.version}) is selected; activate it on this device to run it`,
|
|
137
|
+
});
|
|
138
|
+
return { loaded: false, pv: groupSelection, upgraded: false };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
121
141
|
const localPath = opts?.localPath ??
|
|
122
142
|
(await readStoredPackageLocalPath(pkg.packageId, dataContext).catch(() => undefined));
|
|
123
143
|
const compatibilityTargetId = rawPrefs.compatibilityFallbackForPackageVersionId;
|
|
@@ -130,6 +150,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
130
150
|
if (compatibilityTarget) {
|
|
131
151
|
try {
|
|
132
152
|
const restored = await dataContext.packageLoader.loadPackage(pkg, {
|
|
153
|
+
onlyIfChanged: opts?.onlyIfChanged,
|
|
133
154
|
force: true,
|
|
134
155
|
localPath,
|
|
135
156
|
packageVersionId: compatibilityTargetId,
|
|
@@ -158,6 +179,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
158
179
|
if (!pv)
|
|
159
180
|
return { loaded: false, pv: undefined, upgraded: false };
|
|
160
181
|
const instance = await dataContext.packageLoader.loadPackage(pkg, {
|
|
182
|
+
onlyIfChanged: opts?.onlyIfChanged,
|
|
161
183
|
force: opts?.force,
|
|
162
184
|
localPath,
|
|
163
185
|
packageVersionId: effectiveActivePvId,
|
|
@@ -182,6 +204,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
182
204
|
// activates a non-dev version.
|
|
183
205
|
if (activePv?.versionTag === "dev") {
|
|
184
206
|
const instance = await dataContext.packageLoader.loadPackage(pkg, {
|
|
207
|
+
onlyIfChanged: opts?.onlyIfChanged,
|
|
185
208
|
force: opts?.force,
|
|
186
209
|
localPath,
|
|
187
210
|
packageVersionId: effectiveActivePvId,
|
|
@@ -216,6 +239,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
216
239
|
if (!bestPv || bestPv.packageVersionId === effectiveActivePvId) {
|
|
217
240
|
if (activePv) {
|
|
218
241
|
const instance = await dataContext.packageLoader.loadPackage(pkg, {
|
|
242
|
+
onlyIfChanged: opts?.onlyIfChanged,
|
|
219
243
|
force: opts?.force,
|
|
220
244
|
localPath,
|
|
221
245
|
packageVersionId: effectiveActivePvId,
|
|
@@ -233,6 +257,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
233
257
|
// A better PV was found - attempt to load it (getFileContents will
|
|
234
258
|
// auto-download missing chunks from peers)
|
|
235
259
|
const instance = await dataContext.packageLoader.loadPackage(pkg, {
|
|
260
|
+
onlyIfChanged: opts?.onlyIfChanged,
|
|
236
261
|
force: true,
|
|
237
262
|
localPath,
|
|
238
263
|
packageVersionId: bestPv.packageVersionId,
|
|
@@ -241,6 +266,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
241
266
|
// Bundle unavailable even after download attempt - keep current version
|
|
242
267
|
if (activePv) {
|
|
243
268
|
const fallbackInstance = await dataContext.packageLoader.loadPackage(pkg, {
|
|
269
|
+
onlyIfChanged: opts?.onlyIfChanged,
|
|
244
270
|
force: opts?.force,
|
|
245
271
|
localPath,
|
|
246
272
|
packageVersionId: effectiveActivePvId,
|
|
@@ -368,6 +368,48 @@ describe("resolveDevicePackageVersion — expected behavior (control)", () => {
|
|
|
368
368
|
expect(loadedPvIds).toContain(stablePv.packageVersionId);
|
|
369
369
|
expect(loadedPvIds).not.toContain(devPv.packageVersionId);
|
|
370
370
|
});
|
|
371
|
+
it.each([
|
|
372
|
+
["no prefs", false],
|
|
373
|
+
["a pin", true],
|
|
374
|
+
])("leaves a group-selected dev version unloaded on a device with %s", async (_label, pinned) => {
|
|
375
|
+
const dc = freshGroupDc();
|
|
376
|
+
const packageId = (0, utils_1.newid)();
|
|
377
|
+
const devPv = makePV(packageId, { version: "0.1.0", versionTag: "dev" });
|
|
378
|
+
await (0, package_versions_1.PackageVersions)(dc).save(devPv);
|
|
379
|
+
const pkg = makePkg({ packageId, activePackageVersionId: devPv.packageVersionId });
|
|
380
|
+
await (0, packages_1.Packages)(dc).save(pkg);
|
|
381
|
+
if (pinned)
|
|
382
|
+
await (0, package_version_resolver_1.updatePackagePrefs)(packageId, { pinned: true }, dc);
|
|
383
|
+
const load = jest.spyOn(dc.packageLoader, "loadPackage");
|
|
384
|
+
const unload = jest
|
|
385
|
+
.spyOn(dc.packageLoader, "unloadPackage")
|
|
386
|
+
.mockImplementation(async () => undefined);
|
|
387
|
+
const result = await (0, package_version_resolver_1.resolveDevicePackageVersion)(pkg, dc, { force: true });
|
|
388
|
+
expect(result).toMatchObject({ loaded: false, upgraded: false });
|
|
389
|
+
expect(result.pv?.packageVersionId).toBe(devPv.packageVersionId);
|
|
390
|
+
expect(load).not.toHaveBeenCalled();
|
|
391
|
+
expect(unload).toHaveBeenCalledWith(packageId, {
|
|
392
|
+
reason: "Only a dev version (v0.1.0) is selected; activate it on this device to run it",
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
it("runs a group-selected dev version once this device activates it", async () => {
|
|
396
|
+
const dc = freshGroupDc();
|
|
397
|
+
const packageId = (0, utils_1.newid)();
|
|
398
|
+
const devPv = makePV(packageId, { version: "0.1.0", versionTag: "dev" });
|
|
399
|
+
await (0, package_versions_1.PackageVersions)(dc).save(devPv);
|
|
400
|
+
const pkg = makePkg({ packageId, activePackageVersionId: devPv.packageVersionId });
|
|
401
|
+
await (0, packages_1.Packages)(dc).save(pkg);
|
|
402
|
+
await (0, package_version_resolver_1.updatePackagePrefs)(packageId, { activePackageVersionId: devPv.packageVersionId }, dc);
|
|
403
|
+
const loadedPvIds = [];
|
|
404
|
+
jest.spyOn(dc.packageLoader, "loadPackage").mockImplementation(async (_pkg, opts) => {
|
|
405
|
+
if (opts?.packageVersionId)
|
|
406
|
+
loadedPvIds.push(opts.packageVersionId);
|
|
407
|
+
return { packageId };
|
|
408
|
+
});
|
|
409
|
+
const result = await (0, package_version_resolver_1.resolveDevicePackageVersion)(pkg, dc, { force: true });
|
|
410
|
+
expect(result.loaded).toBe(true);
|
|
411
|
+
expect(loadedPvIds).toEqual([devPv.packageVersionId]);
|
|
412
|
+
});
|
|
371
413
|
it("installs assistants and workflows into the data context on auto-upgrade", async () => {
|
|
372
414
|
const dc = groupDc();
|
|
373
415
|
const packageId = (0, utils_1.newid)();
|
|
@@ -23,3 +23,22 @@ export declare function loadPackageSigningKey(packageId: string, getUserSecretKe
|
|
|
23
23
|
* @param getUserSecretKey - Platform callback that returns the signed-in user's secret key
|
|
24
24
|
*/
|
|
25
25
|
export declare function trySignPackageAuthorVersion(pvFields: AuthorSignableFields, getUserSecretKey: () => Promise<string>): Promise<string | undefined>;
|
|
26
|
+
/** Host-installed signer for PackageVersion author signatures. */
|
|
27
|
+
export type PackageAuthorVersionSigner = (pvFields: AuthorSignableFields) => Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Install (or clear) the host's PackageVersion author signer.
|
|
30
|
+
*
|
|
31
|
+
* `peers-device` installs one at startup over the signed-in user's secret key, so Runtime
|
|
32
|
+
* code (the installer, System Packages) can re-sign versions without a host RPC.
|
|
33
|
+
*
|
|
34
|
+
* @param signer - Signer to use, or `undefined` to clear it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function setPackageAuthorVersionSigner(signer: PackageAuthorVersionSigner | undefined): void;
|
|
37
|
+
/**
|
|
38
|
+
* Sign PackageVersion fields with the installed host signer.
|
|
39
|
+
*
|
|
40
|
+
* @param pvFields - Author-signable PackageVersion fields.
|
|
41
|
+
* @returns The detached signature, or `undefined` when no signer is installed, the package
|
|
42
|
+
* signing key is unavailable, or signing fails.
|
|
43
|
+
*/
|
|
44
|
+
export declare function signPackageAuthorVersionOnHost(pvFields: AuthorSignableFields): Promise<string | undefined>;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.loadPackageSigningKey = loadPackageSigningKey;
|
|
4
4
|
exports.trySignPackageAuthorVersion = trySignPackageAuthorVersion;
|
|
5
|
+
exports.setPackageAuthorVersionSigner = setPackageAuthorVersionSigner;
|
|
6
|
+
exports.signPackageAuthorVersionOnHost = signPackageAuthorVersionOnHost;
|
|
5
7
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
6
8
|
const persistent_vars_1 = require("../data/persistent-vars");
|
|
7
9
|
const keys_1 = require("../keys");
|
|
@@ -55,3 +57,25 @@ async function trySignPackageAuthorVersion(pvFields, getUserSecretKey) {
|
|
|
55
57
|
return undefined;
|
|
56
58
|
}
|
|
57
59
|
}
|
|
60
|
+
let installedSigner;
|
|
61
|
+
/**
|
|
62
|
+
* Install (or clear) the host's PackageVersion author signer.
|
|
63
|
+
*
|
|
64
|
+
* `peers-device` installs one at startup over the signed-in user's secret key, so Runtime
|
|
65
|
+
* code (the installer, System Packages) can re-sign versions without a host RPC.
|
|
66
|
+
*
|
|
67
|
+
* @param signer - Signer to use, or `undefined` to clear it.
|
|
68
|
+
*/
|
|
69
|
+
function setPackageAuthorVersionSigner(signer) {
|
|
70
|
+
installedSigner = signer;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Sign PackageVersion fields with the installed host signer.
|
|
74
|
+
*
|
|
75
|
+
* @param pvFields - Author-signable PackageVersion fields.
|
|
76
|
+
* @returns The detached signature, or `undefined` when no signer is installed, the package
|
|
77
|
+
* signing key is unavailable, or signing fails.
|
|
78
|
+
*/
|
|
79
|
+
async function signPackageAuthorVersionOnHost(pvFields) {
|
|
80
|
+
return installedSigner?.(pvFields);
|
|
81
|
+
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { DataContext } from "../context/data-context";
|
|
2
|
-
import type {
|
|
3
|
-
/**
|
|
4
|
-
* Normalize `addOrUpdatePackage` RPC results.
|
|
5
|
-
* Older hosts return a bare packageId string; current hosts return
|
|
6
|
-
* {@link IAddOrUpdatePackageResult}.
|
|
7
|
-
*/
|
|
8
|
-
export declare function unwrapAddOrUpdatePackageResult(result: string | IAddOrUpdatePackageResult): IAddOrUpdatePackageResult;
|
|
2
|
+
import type { ICreatePackageOpts, ICreatePackageResult, IPackageInstallerDeps, ITemplateCacheResult, TemplateCacheRefreshFailure } from "./types";
|
|
9
3
|
/**
|
|
10
4
|
* Convert a human-readable package name to a filesystem-safe folder slug.
|
|
11
5
|
* "My Widget" → "my-widget", "camelCase Thing" → "camel-case-thing"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.unwrapAddOrUpdatePackageResult = unwrapAddOrUpdatePackageResult;
|
|
4
3
|
exports.nameToFolderSlug = nameToFolderSlug;
|
|
5
4
|
exports.classifyTemplatePullFailure = classifyTemplatePullFailure;
|
|
6
5
|
exports.ensureTemplateCache = ensureTemplateCache;
|
|
@@ -24,17 +23,6 @@ function patchTemplateTokens(contents, values) {
|
|
|
24
23
|
.replaceAll("<app-screen-id>", values.appScreenId)
|
|
25
24
|
.replaceAll("<contract-id>", values.contractId);
|
|
26
25
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Normalize `addOrUpdatePackage` RPC results.
|
|
29
|
-
* Older hosts return a bare packageId string; current hosts return
|
|
30
|
-
* {@link IAddOrUpdatePackageResult}.
|
|
31
|
-
*/
|
|
32
|
-
function unwrapAddOrUpdatePackageResult(result) {
|
|
33
|
-
if (typeof result === "string") {
|
|
34
|
-
return { packageId: result };
|
|
35
|
-
}
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
26
|
/**
|
|
39
27
|
* Convert a human-readable package name to a filesystem-safe folder slug.
|
|
40
28
|
* "My Widget" → "my-widget", "camelCase Thing" → "camel-case-thing"
|
|
@@ -87,22 +87,6 @@ beforeEach(() => {
|
|
|
87
87
|
mockNewKeys.mockReturnValue(generatedKeys);
|
|
88
88
|
mockPersistentVarsSave.mockReset();
|
|
89
89
|
});
|
|
90
|
-
describe("unwrapAddOrUpdatePackageResult", () => {
|
|
91
|
-
it("accepts a legacy packageId string", () => {
|
|
92
|
-
expect((0, package_creator_1.unwrapAddOrUpdatePackageResult)("00mnkoyo7mizav6tseubjzr3a")).toEqual({
|
|
93
|
-
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
it("passes through a structured result including a template warning", () => {
|
|
97
|
-
expect((0, package_creator_1.unwrapAddOrUpdatePackageResult)({
|
|
98
|
-
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
99
|
-
warning: "stale template",
|
|
100
|
-
})).toEqual({
|
|
101
|
-
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
102
|
-
warning: "stale template",
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
90
|
describe("nameToFolderSlug", () => {
|
|
107
91
|
it("converts a simple name", () => {
|
|
108
92
|
expect((0, package_creator_1.nameToFolderSlug)("My Widget")).toBe("my-widget");
|
|
@@ -8,6 +8,11 @@ import type { IInstallOpts, IInstallResult, IPackageInfo, IPackageInstallerDeps
|
|
|
8
8
|
* bundles from `dist/`, hashes them for deduplication, saves File records
|
|
9
9
|
* only when content has changed, upserts the PackageVersion, and optionally
|
|
10
10
|
* triggers the package loader and device version resolution.
|
|
11
|
+
*
|
|
12
|
+
* When the bundle hash matches the existing PackageVersion, no files or PV are
|
|
13
|
+
* rewritten. The Packages row still gets a missing `activePackageVersionId`
|
|
14
|
+
* (and description), and when the id was missing the version is activated on
|
|
15
|
+
* this device unless `skipResolve`.
|
|
11
16
|
*/
|
|
12
17
|
export declare function installPackageFromBundles(dataContext: DataContext, deps: IPackageInstallerDeps, packageId: string, opts?: IInstallOpts): Promise<IInstallResult>;
|
|
13
18
|
/**
|