@peers-app/peers-ui 0.21.0 → 0.21.2
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/screens/packages/package-helpers.d.ts +21 -0
- package/dist/screens/packages/package-helpers.js +32 -0
- package/dist/screens/packages/package-versions.d.ts +1 -0
- package/dist/screens/packages/package-versions.js +48 -12
- package/package.json +3 -3
- package/src/screens/packages/package-helpers.ts +39 -0
- package/src/screens/packages/package-versions.tsx +52 -13
|
@@ -29,5 +29,26 @@ export declare function isGroupAdmin(): Promise<boolean>;
|
|
|
29
29
|
* @param pv - The package version to activate.
|
|
30
30
|
*/
|
|
31
31
|
export declare function activatePackageVersion(packageId: string, pv: IPackageVersion): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* After promoting a version that is currently effective-active on this device,
|
|
34
|
+
* activate it when the new tag matches the group's auto-activate / follow rules.
|
|
35
|
+
*
|
|
36
|
+
* This closes the gotcha where a device is already running a `dev` build, promotes
|
|
37
|
+
* it to `beta`/`stable`, and expects the group active version to advance without a
|
|
38
|
+
* separate Activate click — but only when the group would accept that channel
|
|
39
|
+
* (e.g. default follow is `stable`; `beta` only when the group follows beta).
|
|
40
|
+
*
|
|
41
|
+
* @param packageId - Package that was promoted
|
|
42
|
+
* @param promotedPv - PV after the tag change (must include the new `versionTag`)
|
|
43
|
+
* @param opts.wasEffectiveActive - True when this PV was this device's effective active before promote
|
|
44
|
+
* @param opts.groupFollowTags - Group `IPackage.followVersionTags` (not device override)
|
|
45
|
+
* @param opts.groupPinned - True when group `versionFollowRange` is `pinned`
|
|
46
|
+
* @returns Whether activation was attempted
|
|
47
|
+
*/
|
|
48
|
+
export declare function maybeActivateAfterPromote(packageId: string, promotedPv: IPackageVersion, opts: {
|
|
49
|
+
wasEffectiveActive: boolean;
|
|
50
|
+
groupFollowTags: string | undefined;
|
|
51
|
+
groupPinned: boolean;
|
|
52
|
+
}): Promise<boolean>;
|
|
32
53
|
/** Numeric rank for sorting packages by update urgency. */
|
|
33
54
|
export declare const updateLevelRank: Record<string, number>;
|
|
@@ -4,6 +4,7 @@ exports.updateLevelRank = void 0;
|
|
|
4
4
|
exports.checkVersionStatus = checkVersionStatus;
|
|
5
5
|
exports.isGroupAdmin = isGroupAdmin;
|
|
6
6
|
exports.activatePackageVersion = activatePackageVersion;
|
|
7
|
+
exports.maybeActivateAfterPromote = maybeActivateAfterPromote;
|
|
7
8
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
8
9
|
/**
|
|
9
10
|
* Check a package's version status: what's currently active, whether a newer
|
|
@@ -106,5 +107,36 @@ async function activatePackageVersion(packageId, pv) {
|
|
|
106
107
|
await (0, peers_sdk_1.updatePackagePrefs)(packageId, { activePackageVersionId: pv.packageVersionId });
|
|
107
108
|
}
|
|
108
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* After promoting a version that is currently effective-active on this device,
|
|
112
|
+
* activate it when the new tag matches the group's auto-activate / follow rules.
|
|
113
|
+
*
|
|
114
|
+
* This closes the gotcha where a device is already running a `dev` build, promotes
|
|
115
|
+
* it to `beta`/`stable`, and expects the group active version to advance without a
|
|
116
|
+
* separate Activate click — but only when the group would accept that channel
|
|
117
|
+
* (e.g. default follow is `stable`; `beta` only when the group follows beta).
|
|
118
|
+
*
|
|
119
|
+
* @param packageId - Package that was promoted
|
|
120
|
+
* @param promotedPv - PV after the tag change (must include the new `versionTag`)
|
|
121
|
+
* @param opts.wasEffectiveActive - True when this PV was this device's effective active before promote
|
|
122
|
+
* @param opts.groupFollowTags - Group `IPackage.followVersionTags` (not device override)
|
|
123
|
+
* @param opts.groupPinned - True when group `versionFollowRange` is `pinned`
|
|
124
|
+
* @returns Whether activation was attempted
|
|
125
|
+
*/
|
|
126
|
+
async function maybeActivateAfterPromote(packageId, promotedPv, opts) {
|
|
127
|
+
if (!opts.wasEffectiveActive)
|
|
128
|
+
return false;
|
|
129
|
+
if (opts.groupPinned)
|
|
130
|
+
return false;
|
|
131
|
+
if (!promotedPv.versionTag || promotedPv.versionTag === "dev")
|
|
132
|
+
return false;
|
|
133
|
+
// Docs default: groups follow `stable` when followVersionTags is unset.
|
|
134
|
+
const followTags = opts.groupFollowTags ?? "stable";
|
|
135
|
+
if (!(0, peers_sdk_1.doesTagMatch)(undefined, promotedPv.versionTag, followTags, undefined)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
await activatePackageVersion(packageId, promotedPv);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
109
141
|
/** Numeric rank for sorting packages by update urgency. */
|
|
110
142
|
exports.updateLevelRank = { major: 3, minor: 2, patch: 1 };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type IDoc, type IPackage } from "@peers-app/peers-sdk";
|
|
2
|
+
/** Lists package versions with activate, promote, pin, edit-semver, and delete actions. */
|
|
2
3
|
export declare const PackageVersionsList: (props: {
|
|
3
4
|
pkg: IDoc<IPackage>;
|
|
4
5
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -39,6 +39,7 @@ function tagBadgeClass(tag) {
|
|
|
39
39
|
return "text-bg-success";
|
|
40
40
|
return "text-bg-secondary";
|
|
41
41
|
}
|
|
42
|
+
/** Lists package versions with activate, promote, pin, edit-semver, and delete actions. */
|
|
42
43
|
const PackageVersionsList = (props) => {
|
|
43
44
|
const { pkg } = props;
|
|
44
45
|
const refreshKey = (0, hooks_1.useObservableState)(0);
|
|
@@ -115,24 +116,41 @@ const PackageVersionsList = (props) => {
|
|
|
115
116
|
signature: "",
|
|
116
117
|
});
|
|
117
118
|
const pvHash = (0, peers_sdk_1.computePackageVersionHash)(pv.version, newTag, pv.packageBundleFileHash, pv.routesBundleFileHash, pv.uiBundleFileHash);
|
|
119
|
+
// Auto-sign with package author key if available (server/PWA host — key never
|
|
120
|
+
// exposed to UI). Soft-fail when the key is missing so group admins can still
|
|
121
|
+
// promote, but never keep a signature that was computed for the old tag.
|
|
122
|
+
const authorSig = await peers_sdk_1.rpcServerCalls.signPackageAuthorVersion({
|
|
123
|
+
packageId: pv.packageId,
|
|
124
|
+
packageVersionId: pv.packageVersionId,
|
|
125
|
+
version: pv.version,
|
|
126
|
+
versionTag: newTag,
|
|
127
|
+
packageBundleFileHash: pv.packageBundleFileHash,
|
|
128
|
+
routesBundleFileHash: pv.routesBundleFileHash,
|
|
129
|
+
uiBundleFileHash: pv.uiBundleFileHash,
|
|
130
|
+
appNavs: pv.appNavs,
|
|
131
|
+
createdBy: pv.createdBy,
|
|
132
|
+
createdAt: pv.createdAt,
|
|
133
|
+
});
|
|
118
134
|
const updated = {
|
|
119
135
|
...pv,
|
|
120
136
|
versionTag: newTag,
|
|
121
137
|
packageVersionHash: pvHash,
|
|
122
138
|
history,
|
|
139
|
+
packageAuthorSignature: authorSig,
|
|
123
140
|
};
|
|
124
|
-
// Auto-sign with package author key if available (server-side, key never exposed)
|
|
125
|
-
const authorSig = await peers_sdk_1.rpcServerCalls.signPackageAuthorVersion({
|
|
126
|
-
packageId: updated.packageId,
|
|
127
|
-
packageVersionId: updated.packageVersionId,
|
|
128
|
-
version: updated.version,
|
|
129
|
-
versionTag: newTag,
|
|
130
|
-
packageBundleFileHash: updated.packageBundleFileHash,
|
|
131
|
-
routesBundleFileHash: updated.routesBundleFileHash,
|
|
132
|
-
uiBundleFileHash: updated.uiBundleFileHash,
|
|
133
|
-
});
|
|
134
|
-
updated.packageAuthorSignature = authorSig;
|
|
135
141
|
await (0, peers_sdk_1.PackageVersions)().signAndSave(updated, { saveAsSnapshot: true });
|
|
142
|
+
// Promoting the version this device is already running should advance the
|
|
143
|
+
// group active version when the new tag matches group follow policy
|
|
144
|
+
// (e.g. promote active → stable with default follow settings).
|
|
145
|
+
const pkgJs = pkg.toJS();
|
|
146
|
+
const activated = await (0, package_helpers_1.maybeActivateAfterPromote)(pkg.packageId, updated, {
|
|
147
|
+
wasEffectiveActive: pv.packageVersionId === effectiveActiveVersionId,
|
|
148
|
+
groupFollowTags: pkgJs.followVersionTags,
|
|
149
|
+
groupPinned: pkgJs.versionFollowRange === "pinned",
|
|
150
|
+
});
|
|
151
|
+
if (activated) {
|
|
152
|
+
await pkg.load();
|
|
153
|
+
}
|
|
136
154
|
refreshKey(refreshKey() + 1);
|
|
137
155
|
}
|
|
138
156
|
catch (err) {
|
|
@@ -142,7 +160,25 @@ const PackageVersionsList = (props) => {
|
|
|
142
160
|
async function updateVersion(pv, newSemver) {
|
|
143
161
|
try {
|
|
144
162
|
const pvHash = (0, peers_sdk_1.computePackageVersionHash)(newSemver, pv.versionTag || "dev", pv.packageBundleFileHash, pv.routesBundleFileHash, pv.uiBundleFileHash);
|
|
145
|
-
|
|
163
|
+
// Semver is part of the author-signed payload — re-sign or clear the old signature.
|
|
164
|
+
const authorSig = await peers_sdk_1.rpcServerCalls.signPackageAuthorVersion({
|
|
165
|
+
packageId: pv.packageId,
|
|
166
|
+
packageVersionId: pv.packageVersionId,
|
|
167
|
+
version: newSemver,
|
|
168
|
+
versionTag: pv.versionTag,
|
|
169
|
+
packageBundleFileHash: pv.packageBundleFileHash,
|
|
170
|
+
routesBundleFileHash: pv.routesBundleFileHash,
|
|
171
|
+
uiBundleFileHash: pv.uiBundleFileHash,
|
|
172
|
+
appNavs: pv.appNavs,
|
|
173
|
+
createdBy: pv.createdBy,
|
|
174
|
+
createdAt: pv.createdAt,
|
|
175
|
+
});
|
|
176
|
+
const updated = {
|
|
177
|
+
...pv,
|
|
178
|
+
version: newSemver,
|
|
179
|
+
packageVersionHash: pvHash,
|
|
180
|
+
packageAuthorSignature: authorSig,
|
|
181
|
+
};
|
|
146
182
|
await (0, peers_sdk_1.PackageVersions)().signAndSave(updated, { saveAsSnapshot: true });
|
|
147
183
|
refreshKey(refreshKey() + 1);
|
|
148
184
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"lint:fix": "biome check --write ."
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@peers-app/peers-sdk": "^0.21.
|
|
31
|
+
"@peers-app/peers-sdk": "^0.21.2",
|
|
32
32
|
"bootstrap": "^5.3.3",
|
|
33
33
|
"react": "^18.0.0",
|
|
34
34
|
"react-dom": "^18.0.0"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@babel/preset-react": "^7.24.1",
|
|
40
40
|
"@babel/preset-typescript": "^7.27.1",
|
|
41
41
|
"@biomejs/biome": "2.3.11",
|
|
42
|
-
"@peers-app/peers-sdk": "0.21.
|
|
42
|
+
"@peers-app/peers-sdk": "0.21.2",
|
|
43
43
|
"@testing-library/dom": "^10.4.0",
|
|
44
44
|
"@testing-library/jest-dom": "^6.6.3",
|
|
45
45
|
"@testing-library/react": "^16.3.0",
|
|
@@ -136,5 +136,44 @@ export async function activatePackageVersion(
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* After promoting a version that is currently effective-active on this device,
|
|
141
|
+
* activate it when the new tag matches the group's auto-activate / follow rules.
|
|
142
|
+
*
|
|
143
|
+
* This closes the gotcha where a device is already running a `dev` build, promotes
|
|
144
|
+
* it to `beta`/`stable`, and expects the group active version to advance without a
|
|
145
|
+
* separate Activate click — but only when the group would accept that channel
|
|
146
|
+
* (e.g. default follow is `stable`; `beta` only when the group follows beta).
|
|
147
|
+
*
|
|
148
|
+
* @param packageId - Package that was promoted
|
|
149
|
+
* @param promotedPv - PV after the tag change (must include the new `versionTag`)
|
|
150
|
+
* @param opts.wasEffectiveActive - True when this PV was this device's effective active before promote
|
|
151
|
+
* @param opts.groupFollowTags - Group `IPackage.followVersionTags` (not device override)
|
|
152
|
+
* @param opts.groupPinned - True when group `versionFollowRange` is `pinned`
|
|
153
|
+
* @returns Whether activation was attempted
|
|
154
|
+
*/
|
|
155
|
+
export async function maybeActivateAfterPromote(
|
|
156
|
+
packageId: string,
|
|
157
|
+
promotedPv: IPackageVersion,
|
|
158
|
+
opts: {
|
|
159
|
+
wasEffectiveActive: boolean;
|
|
160
|
+
groupFollowTags: string | undefined;
|
|
161
|
+
groupPinned: boolean;
|
|
162
|
+
},
|
|
163
|
+
): Promise<boolean> {
|
|
164
|
+
if (!opts.wasEffectiveActive) return false;
|
|
165
|
+
if (opts.groupPinned) return false;
|
|
166
|
+
if (!promotedPv.versionTag || promotedPv.versionTag === "dev") return false;
|
|
167
|
+
|
|
168
|
+
// Docs default: groups follow `stable` when followVersionTags is unset.
|
|
169
|
+
const followTags = opts.groupFollowTags ?? "stable";
|
|
170
|
+
if (!doesTagMatch(undefined, promotedPv.versionTag, followTags, undefined)) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await activatePackageVersion(packageId, promotedPv);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
139
178
|
/** Numeric rank for sorting packages by update urgency. */
|
|
140
179
|
export const updateLevelRank: Record<string, number> = { major: 3, minor: 2, patch: 1 };
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "@peers-app/peers-sdk";
|
|
16
16
|
import { useState } from "react";
|
|
17
17
|
import { useObservable, useObservableState, usePromise } from "../../hooks";
|
|
18
|
-
import { activatePackageVersion, isGroupAdmin } from "./package-helpers";
|
|
18
|
+
import { activatePackageVersion, isGroupAdmin, maybeActivateAfterPromote } from "./package-helpers";
|
|
19
19
|
|
|
20
20
|
function formatDate(iso: string): string {
|
|
21
21
|
try {
|
|
@@ -43,6 +43,7 @@ function tagBadgeClass(tag?: string): string {
|
|
|
43
43
|
return "text-bg-secondary";
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** Lists package versions with activate, promote, pin, edit-semver, and delete actions. */
|
|
46
47
|
export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
47
48
|
const { pkg } = props;
|
|
48
49
|
const refreshKey = useObservableState(0);
|
|
@@ -127,26 +128,46 @@ export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
|
127
128
|
pv.routesBundleFileHash,
|
|
128
129
|
pv.uiBundleFileHash,
|
|
129
130
|
);
|
|
131
|
+
|
|
132
|
+
// Auto-sign with package author key if available (server/PWA host — key never
|
|
133
|
+
// exposed to UI). Soft-fail when the key is missing so group admins can still
|
|
134
|
+
// promote, but never keep a signature that was computed for the old tag.
|
|
135
|
+
const authorSig = await rpcServerCalls.signPackageAuthorVersion({
|
|
136
|
+
packageId: pv.packageId,
|
|
137
|
+
packageVersionId: pv.packageVersionId,
|
|
138
|
+
version: pv.version,
|
|
139
|
+
versionTag: newTag,
|
|
140
|
+
packageBundleFileHash: pv.packageBundleFileHash,
|
|
141
|
+
routesBundleFileHash: pv.routesBundleFileHash,
|
|
142
|
+
uiBundleFileHash: pv.uiBundleFileHash,
|
|
143
|
+
appNavs: pv.appNavs,
|
|
144
|
+
createdBy: pv.createdBy,
|
|
145
|
+
createdAt: pv.createdAt,
|
|
146
|
+
});
|
|
147
|
+
|
|
130
148
|
const updated: IPackageVersion = {
|
|
131
149
|
...pv,
|
|
132
150
|
versionTag: newTag,
|
|
133
151
|
packageVersionHash: pvHash,
|
|
134
152
|
history,
|
|
153
|
+
packageAuthorSignature: authorSig,
|
|
135
154
|
};
|
|
136
155
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
await PackageVersions().signAndSave(updated, { saveAsSnapshot: true });
|
|
157
|
+
|
|
158
|
+
// Promoting the version this device is already running should advance the
|
|
159
|
+
// group active version when the new tag matches group follow policy
|
|
160
|
+
// (e.g. promote active → stable with default follow settings).
|
|
161
|
+
const pkgJs = pkg.toJS();
|
|
162
|
+
const activated = await maybeActivateAfterPromote(pkg.packageId, updated, {
|
|
163
|
+
wasEffectiveActive: pv.packageVersionId === effectiveActiveVersionId,
|
|
164
|
+
groupFollowTags: pkgJs.followVersionTags,
|
|
165
|
+
groupPinned: pkgJs.versionFollowRange === "pinned",
|
|
146
166
|
});
|
|
147
|
-
|
|
167
|
+
if (activated) {
|
|
168
|
+
await pkg.load();
|
|
169
|
+
}
|
|
148
170
|
|
|
149
|
-
await PackageVersions().signAndSave(updated, { saveAsSnapshot: true });
|
|
150
171
|
refreshKey(refreshKey() + 1);
|
|
151
172
|
} catch (err: unknown) {
|
|
152
173
|
alert(`Failed to promote version: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -162,7 +183,25 @@ export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
|
162
183
|
pv.routesBundleFileHash,
|
|
163
184
|
pv.uiBundleFileHash,
|
|
164
185
|
);
|
|
165
|
-
|
|
186
|
+
// Semver is part of the author-signed payload — re-sign or clear the old signature.
|
|
187
|
+
const authorSig = await rpcServerCalls.signPackageAuthorVersion({
|
|
188
|
+
packageId: pv.packageId,
|
|
189
|
+
packageVersionId: pv.packageVersionId,
|
|
190
|
+
version: newSemver,
|
|
191
|
+
versionTag: pv.versionTag,
|
|
192
|
+
packageBundleFileHash: pv.packageBundleFileHash,
|
|
193
|
+
routesBundleFileHash: pv.routesBundleFileHash,
|
|
194
|
+
uiBundleFileHash: pv.uiBundleFileHash,
|
|
195
|
+
appNavs: pv.appNavs,
|
|
196
|
+
createdBy: pv.createdBy,
|
|
197
|
+
createdAt: pv.createdAt,
|
|
198
|
+
});
|
|
199
|
+
const updated: IPackageVersion = {
|
|
200
|
+
...pv,
|
|
201
|
+
version: newSemver,
|
|
202
|
+
packageVersionHash: pvHash,
|
|
203
|
+
packageAuthorSignature: authorSig,
|
|
204
|
+
};
|
|
166
205
|
await PackageVersions().signAndSave(updated, { saveAsSnapshot: true });
|
|
167
206
|
refreshKey(refreshKey() + 1);
|
|
168
207
|
} catch (err: unknown) {
|