@frockbot/plugin-package-publisher 0.0.0 → 0.1.1
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/frockbot.json +25 -0
- package/package.json +44 -6
- package/src/agent.test.ts +159 -0
- package/src/agent.ts +266 -0
- package/src/backend.test.ts +129 -0
- package/src/backend.ts +125 -0
- package/src/client/PackagePublisherSection.vue +79 -0
- package/src/client/PackagePublisherSurface.vue +122 -0
- package/src/client/index.test.ts +99 -0
- package/src/client/index.ts +84 -0
- package/src/client/state.ts +14 -0
- package/src/env.d.ts +6 -0
- package/src/index.ts +3 -0
- package/src/manifest.ts +3 -0
- package/src/shared.ts +309 -0
- package/src/user.test.ts +288 -0
- package/src/user.ts +472 -0
- package/tsconfig.json +15 -0
- package/vite.config.ts +31 -0
- package/README.md +0 -3
package/src/backend.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Plugin } from "cordis";
|
|
2
|
+
import {
|
|
3
|
+
PackagePublisherConflictError,
|
|
4
|
+
PackagePublisherDecodeError,
|
|
5
|
+
decodeRollbackPackageCommandV1,
|
|
6
|
+
type PackagePublicationReceiptV1,
|
|
7
|
+
type PackageRevisionHistoryV1,
|
|
8
|
+
type RollbackPackageCommandV1,
|
|
9
|
+
} from "./shared.js";
|
|
10
|
+
|
|
11
|
+
export interface PackagePublisherGatewayHost {
|
|
12
|
+
read(userId: string): Promise<PackageRevisionHistoryV1>;
|
|
13
|
+
rollback(
|
|
14
|
+
userId: string,
|
|
15
|
+
command: RollbackPackageCommandV1,
|
|
16
|
+
): Promise<PackagePublicationReceiptV1>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PackagePublisherBackendRouteContribution {
|
|
20
|
+
packageId: string;
|
|
21
|
+
route(
|
|
22
|
+
request: Request,
|
|
23
|
+
url: URL,
|
|
24
|
+
context: { userId?: string; client: "browser" | "desktop" },
|
|
25
|
+
): Promise<Response | undefined>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function errorResponse(error: unknown): Response {
|
|
29
|
+
if (
|
|
30
|
+
error instanceof PackagePublisherDecodeError ||
|
|
31
|
+
(typeof error === "object" &&
|
|
32
|
+
error !== null &&
|
|
33
|
+
"name" in error &&
|
|
34
|
+
error.name === "PackagePublisherDecodeError")
|
|
35
|
+
) {
|
|
36
|
+
return Response.json(
|
|
37
|
+
{
|
|
38
|
+
error:
|
|
39
|
+
error instanceof Error ? error.message : "package request is invalid",
|
|
40
|
+
code: "invalid-request",
|
|
41
|
+
definitive: true,
|
|
42
|
+
},
|
|
43
|
+
{ status: 400 },
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (
|
|
47
|
+
error instanceof PackagePublisherConflictError ||
|
|
48
|
+
(typeof error === "object" &&
|
|
49
|
+
error !== null &&
|
|
50
|
+
"name" in error &&
|
|
51
|
+
error.name === "PackagePublisherConflictError")
|
|
52
|
+
) {
|
|
53
|
+
const currentRevision =
|
|
54
|
+
typeof error === "object" &&
|
|
55
|
+
error !== null &&
|
|
56
|
+
"currentRevision" in error &&
|
|
57
|
+
typeof error.currentRevision === "number"
|
|
58
|
+
? error.currentRevision
|
|
59
|
+
: 0;
|
|
60
|
+
return Response.json(
|
|
61
|
+
{
|
|
62
|
+
error: `package revision is ${currentRevision}`,
|
|
63
|
+
code: "revision-conflict",
|
|
64
|
+
currentRevision,
|
|
65
|
+
definitive: true,
|
|
66
|
+
},
|
|
67
|
+
{ status: 409 },
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return Response.json(
|
|
71
|
+
{
|
|
72
|
+
error:
|
|
73
|
+
error instanceof Error ? error.message : "package publication failed",
|
|
74
|
+
},
|
|
75
|
+
{ status: 500 },
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createPackagePublisherBackendContribution(
|
|
80
|
+
host: PackagePublisherGatewayHost,
|
|
81
|
+
): PackagePublisherBackendRouteContribution {
|
|
82
|
+
return {
|
|
83
|
+
packageId: "package-publisher",
|
|
84
|
+
async route(request, url, context) {
|
|
85
|
+
if (!context.userId) return undefined;
|
|
86
|
+
const revisions = url.pathname === "/api/package-revisions";
|
|
87
|
+
const rollback = url.pathname === "/api/package-revisions/rollback";
|
|
88
|
+
if (!revisions && !rollback) return undefined;
|
|
89
|
+
try {
|
|
90
|
+
if (revisions && request.method === "GET") {
|
|
91
|
+
return Response.json(await host.read(context.userId));
|
|
92
|
+
}
|
|
93
|
+
if (request.method !== "POST") {
|
|
94
|
+
return Response.json(
|
|
95
|
+
{ error: "method not allowed" },
|
|
96
|
+
{ status: 405 },
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (rollback) {
|
|
100
|
+
return Response.json(
|
|
101
|
+
await host.rollback(
|
|
102
|
+
context.userId,
|
|
103
|
+
decodeRollbackPackageCommandV1(await request.json()),
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return Response.json({ error: "method not allowed" }, { status: 405 });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return errorResponse(error);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export namespace createPackagePublisherBackendContribution {
|
|
116
|
+
export function plugin(
|
|
117
|
+
host: PackagePublisherGatewayHost,
|
|
118
|
+
lifecycle: {
|
|
119
|
+
mount(value: PackagePublisherBackendRouteContribution): () => void;
|
|
120
|
+
},
|
|
121
|
+
): Plugin {
|
|
122
|
+
return () =>
|
|
123
|
+
lifecycle.mount(createPackagePublisherBackendContribution(host));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { clientSurfaceRegistryKey } from "@frockbot/client-core";
|
|
3
|
+
import { UiButton, UiIcon } from "@frockbot/client-ui";
|
|
4
|
+
import { computed, inject, onMounted } from "vue";
|
|
5
|
+
import { packagePublisherStateKey } from "./state.js";
|
|
6
|
+
|
|
7
|
+
const surfaces = inject(clientSurfaceRegistryKey);
|
|
8
|
+
const state = inject(packagePublisherStateKey);
|
|
9
|
+
if (!surfaces || !state) {
|
|
10
|
+
throw new Error("Package Publisher client services were not provided");
|
|
11
|
+
}
|
|
12
|
+
const publisher = state;
|
|
13
|
+
const summary = computed(() => {
|
|
14
|
+
const history = publisher.value.history;
|
|
15
|
+
if (!history) return "Not published yet";
|
|
16
|
+
const active = history.activePackageRevision;
|
|
17
|
+
if (active === undefined) return `${history.revisions.length} revisions`;
|
|
18
|
+
return `Revision ${active} · active`;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
onMounted(() => publisher.value.load());
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div class="publisher-section">
|
|
26
|
+
<span class="publisher-section__icon" aria-hidden="true"
|
|
27
|
+
><UiIcon name="history"
|
|
28
|
+
/></span>
|
|
29
|
+
<span class="publisher-section__text">
|
|
30
|
+
<strong>Setup revisions</strong>
|
|
31
|
+
<small>{{ summary }}</small>
|
|
32
|
+
</span>
|
|
33
|
+
<UiButton type="button" @click="surfaces.open('package-publisher')">
|
|
34
|
+
Open
|
|
35
|
+
</UiButton>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<style scoped>
|
|
40
|
+
.publisher-section {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: 10px;
|
|
44
|
+
padding: 12px;
|
|
45
|
+
border: 1px solid var(--frock-border);
|
|
46
|
+
border-radius: var(--frock-radius-card);
|
|
47
|
+
background: var(--frock-surface-subtle);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.publisher-section__icon {
|
|
51
|
+
display: grid;
|
|
52
|
+
width: var(--frock-avatar-sm);
|
|
53
|
+
height: var(--frock-avatar-sm);
|
|
54
|
+
flex: 0 0 auto;
|
|
55
|
+
place-items: center;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
color: var(--frock-action-primary);
|
|
58
|
+
background: var(--frock-surface);
|
|
59
|
+
box-shadow: inset 0 0 0 1px var(--frock-border);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.publisher-section__text {
|
|
63
|
+
display: flex;
|
|
64
|
+
min-width: 0;
|
|
65
|
+
flex: 1 1 auto;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.publisher-section__text strong {
|
|
70
|
+
color: var(--frock-text);
|
|
71
|
+
font-size: var(--frock-text-md);
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.publisher-section__text small {
|
|
76
|
+
color: var(--frock-text-muted);
|
|
77
|
+
font-size: var(--frock-text-sm);
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { UiButton } from "@frockbot/client-ui";
|
|
3
|
+
import { computed, inject, onMounted } from "vue";
|
|
4
|
+
import { packagePublisherStateKey } from "./state.js";
|
|
5
|
+
|
|
6
|
+
const providedState = inject(packagePublisherStateKey);
|
|
7
|
+
if (!providedState) throw new Error("package publisher state was not provided");
|
|
8
|
+
const state = providedState;
|
|
9
|
+
const revisions = computed(() =>
|
|
10
|
+
[...(state.value.history?.revisions ?? [])].toReversed(),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
onMounted(() => state.value.load());
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="publisher-surface">
|
|
18
|
+
<header>
|
|
19
|
+
<h2>Published setup</h2>
|
|
20
|
+
<p>
|
|
21
|
+
Publishing activates one immutable setup for all of your Bots. Rollback
|
|
22
|
+
changes the shared active revision.
|
|
23
|
+
</p>
|
|
24
|
+
</header>
|
|
25
|
+
|
|
26
|
+
<div v-if="revisions.length" class="revision-list">
|
|
27
|
+
<article
|
|
28
|
+
v-for="revision in revisions"
|
|
29
|
+
:key="revision.packageRevision"
|
|
30
|
+
class="revision-card"
|
|
31
|
+
>
|
|
32
|
+
<div>
|
|
33
|
+
<strong>Revision {{ revision.packageRevision }}</strong>
|
|
34
|
+
<small>{{ new Date(revision.publishedAt).toLocaleString() }}</small>
|
|
35
|
+
<code>{{ revision.applicationHash.slice(0, 22) }}…</code>
|
|
36
|
+
</div>
|
|
37
|
+
<span
|
|
38
|
+
v-if="
|
|
39
|
+
state.history?.activePackageRevision === revision.packageRevision
|
|
40
|
+
"
|
|
41
|
+
class="active-revision"
|
|
42
|
+
>
|
|
43
|
+
Active
|
|
44
|
+
</span>
|
|
45
|
+
<UiButton
|
|
46
|
+
v-else
|
|
47
|
+
:disabled="state.busy"
|
|
48
|
+
@click="state.rollback(revision.packageRevision)"
|
|
49
|
+
>
|
|
50
|
+
Roll back
|
|
51
|
+
</UiButton>
|
|
52
|
+
</article>
|
|
53
|
+
</div>
|
|
54
|
+
<p v-else-if="!state.error" class="empty-revisions">
|
|
55
|
+
No custom setup has been published yet.
|
|
56
|
+
</p>
|
|
57
|
+
<p v-if="state.error" class="publisher-error" role="alert">
|
|
58
|
+
{{ state.error }}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<style scoped>
|
|
64
|
+
.publisher-surface {
|
|
65
|
+
padding: 24px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.publisher-surface h2 {
|
|
69
|
+
margin: 0;
|
|
70
|
+
font-family: var(--frock-font-display);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.publisher-surface header p,
|
|
74
|
+
.empty-revisions {
|
|
75
|
+
color: var(--frock-text-muted);
|
|
76
|
+
font-size: var(--frock-text-base);
|
|
77
|
+
line-height: 1.5;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.revision-list {
|
|
81
|
+
display: grid;
|
|
82
|
+
gap: 12px;
|
|
83
|
+
margin-top: 20px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.revision-card {
|
|
87
|
+
display: flex;
|
|
88
|
+
min-width: 0;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: space-between;
|
|
91
|
+
gap: 16px;
|
|
92
|
+
padding: 14px;
|
|
93
|
+
border: 1px solid var(--frock-border);
|
|
94
|
+
border-radius: var(--frock-radius-card);
|
|
95
|
+
background: var(--frock-surface-raised);
|
|
96
|
+
box-shadow: var(--frock-shadow-card);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.revision-card strong,
|
|
100
|
+
.revision-card small,
|
|
101
|
+
.revision-card code {
|
|
102
|
+
display: block;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.revision-card small,
|
|
106
|
+
.revision-card code {
|
|
107
|
+
margin-top: 4px;
|
|
108
|
+
color: var(--frock-text-muted);
|
|
109
|
+
font-size: var(--frock-text-sm);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.active-revision {
|
|
113
|
+
color: var(--frock-success);
|
|
114
|
+
font-size: var(--frock-text-sm);
|
|
115
|
+
font-weight: 700;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.publisher-error {
|
|
119
|
+
color: var(--frock-danger-text);
|
|
120
|
+
font-size: var(--frock-text-sm);
|
|
121
|
+
}
|
|
122
|
+
</style>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
clientSurfaceRegistryKey,
|
|
4
|
+
type ClientPluginContext,
|
|
5
|
+
type ClientSlotRegistration,
|
|
6
|
+
} from "@frockbot/client-core";
|
|
7
|
+
import { createClientSurfaceRegistry } from "@frockbot/client-ui";
|
|
8
|
+
import { packagePublisherClientPlugin } from "./index.js";
|
|
9
|
+
import { packagePublisherStateKey } from "./state.js";
|
|
10
|
+
|
|
11
|
+
const history = {
|
|
12
|
+
schemaVersion: 1 as const,
|
|
13
|
+
revision: 2,
|
|
14
|
+
activePackageRevision: 2,
|
|
15
|
+
revisions: [
|
|
16
|
+
{
|
|
17
|
+
packageRevision: 1,
|
|
18
|
+
applicationHash: "sha256:one",
|
|
19
|
+
publishedAt: "2026-09-01T00:00:00.000Z",
|
|
20
|
+
checks: [{ name: "test", status: "passed" as const }],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
packageRevision: 2,
|
|
24
|
+
applicationHash: "sha256:two",
|
|
25
|
+
publishedAt: "2026-09-02T00:00:00.000Z",
|
|
26
|
+
checks: [{ name: "test", status: "passed" as const }],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe("Package Publisher client contribution", () => {
|
|
32
|
+
test("registers revision UI and rolls back through the hosted protocol", async () => {
|
|
33
|
+
const surfaces = createClientSurfaceRegistry();
|
|
34
|
+
const slots: ClientSlotRegistration[] = [];
|
|
35
|
+
const calls: Array<[string, string | undefined, string | undefined]> = [];
|
|
36
|
+
let state: unknown;
|
|
37
|
+
const context: ClientPluginContext = {
|
|
38
|
+
transport: {
|
|
39
|
+
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
40
|
+
hostedRequest: (path, method, body) => {
|
|
41
|
+
calls.push([path, method, body]);
|
|
42
|
+
if (path.endsWith("/rollback")) {
|
|
43
|
+
return Promise.resolve({
|
|
44
|
+
schemaVersion: 1,
|
|
45
|
+
commandId: JSON.parse(body ?? "{}").commandId,
|
|
46
|
+
status: "active",
|
|
47
|
+
revision: 3,
|
|
48
|
+
packageRevision: 1,
|
|
49
|
+
applicationHash: "sha256:one",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return Promise.resolve(history);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
inject: (key) => {
|
|
56
|
+
if (key !== clientSurfaceRegistryKey) {
|
|
57
|
+
throw new Error("unexpected client provider");
|
|
58
|
+
}
|
|
59
|
+
return surfaces as never;
|
|
60
|
+
},
|
|
61
|
+
provide: (key, value) => {
|
|
62
|
+
if (key === packagePublisherStateKey) state = value;
|
|
63
|
+
return () => {};
|
|
64
|
+
},
|
|
65
|
+
slot: (registration) => {
|
|
66
|
+
slots.push(registration);
|
|
67
|
+
return () => slots.splice(slots.indexOf(registration), 1);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const disposers = packagePublisherClientPlugin(context);
|
|
72
|
+
if (!Array.isArray(disposers)) throw new Error("expected registrations");
|
|
73
|
+
expect(surfaces.has("package-publisher")).toBe(true);
|
|
74
|
+
expect(slots.map((slot) => slot.slot)).toEqual([
|
|
75
|
+
"frockbot.user-settings-sections",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const publisher = state as {
|
|
79
|
+
value: {
|
|
80
|
+
load(): Promise<void>;
|
|
81
|
+
rollback(revision: number): Promise<void>;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
await publisher.value.load();
|
|
85
|
+
await publisher.value.rollback(1);
|
|
86
|
+
expect(calls.map(([path]) => path)).toEqual([
|
|
87
|
+
"/api/package-revisions",
|
|
88
|
+
"/api/package-revisions/rollback",
|
|
89
|
+
"/api/package-revisions",
|
|
90
|
+
]);
|
|
91
|
+
expect(JSON.parse(calls[1]![2] ?? "{}")).toMatchObject({
|
|
92
|
+
expectedRevision: 2,
|
|
93
|
+
packageRevision: 1,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
for (const dispose of disposers.toReversed()) dispose();
|
|
97
|
+
expect(surfaces.has("package-publisher")).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/// <reference path="../env.d.ts" />
|
|
2
|
+
|
|
3
|
+
// The immutable hosted client mounts this built-in Package Contribution.
|
|
4
|
+
import {
|
|
5
|
+
clientSurfaceRegistryKey,
|
|
6
|
+
type ClientPlugin,
|
|
7
|
+
} from "@frockbot/client-core";
|
|
8
|
+
import { ref } from "vue";
|
|
9
|
+
import {
|
|
10
|
+
decodePackagePublicationReceiptV1,
|
|
11
|
+
decodePackageRevisionHistoryV1,
|
|
12
|
+
} from "../shared.js";
|
|
13
|
+
import PackagePublisherSection from "./PackagePublisherSection.vue";
|
|
14
|
+
import PackagePublisherSurface from "./PackagePublisherSurface.vue";
|
|
15
|
+
import {
|
|
16
|
+
packagePublisherStateKey,
|
|
17
|
+
type PackagePublisherClientState,
|
|
18
|
+
} from "./state.js";
|
|
19
|
+
|
|
20
|
+
export const packagePublisherClientPlugin: ClientPlugin = (ctx) => {
|
|
21
|
+
const surfaces = ctx.inject(clientSurfaceRegistryKey);
|
|
22
|
+
const state = ref<PackagePublisherClientState>({
|
|
23
|
+
busy: false,
|
|
24
|
+
async load() {
|
|
25
|
+
if (!ctx.transport.hostedRequest) {
|
|
26
|
+
state.value.error = "Package revisions are unavailable";
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
state.value.history = decodePackageRevisionHistoryV1(
|
|
31
|
+
await ctx.transport.hostedRequest("/api/package-revisions"),
|
|
32
|
+
);
|
|
33
|
+
state.value.error = undefined;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
state.value.error =
|
|
36
|
+
error instanceof Error ? error.message : "Could not load revisions";
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async rollback(packageRevision: number) {
|
|
40
|
+
if (!ctx.transport.hostedRequest || !state.value.history) {
|
|
41
|
+
throw new Error("Package revisions are unavailable");
|
|
42
|
+
}
|
|
43
|
+
state.value.busy = true;
|
|
44
|
+
try {
|
|
45
|
+
decodePackagePublicationReceiptV1(
|
|
46
|
+
await ctx.transport.hostedRequest(
|
|
47
|
+
"/api/package-revisions/rollback",
|
|
48
|
+
"POST",
|
|
49
|
+
JSON.stringify({
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
commandId: crypto.randomUUID(),
|
|
52
|
+
expectedRevision: state.value.history.revision,
|
|
53
|
+
packageRevision,
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
await state.value.load();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
state.value.error =
|
|
60
|
+
error instanceof Error ? error.message : "Rollback failed";
|
|
61
|
+
} finally {
|
|
62
|
+
state.value.busy = false;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return [
|
|
68
|
+
ctx.provide(packagePublisherStateKey, state),
|
|
69
|
+
surfaces.register({
|
|
70
|
+
id: "package-publisher",
|
|
71
|
+
title: "Published setup",
|
|
72
|
+
component: PackagePublisherSurface,
|
|
73
|
+
}),
|
|
74
|
+
// Revisions are an internal detail: they live inside the User profile's
|
|
75
|
+
// advanced section rather than the sidebar.
|
|
76
|
+
ctx.slot({
|
|
77
|
+
slot: "frockbot.user-settings-sections",
|
|
78
|
+
order: 20,
|
|
79
|
+
component: PackagePublisherSection,
|
|
80
|
+
}),
|
|
81
|
+
];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default packagePublisherClientPlugin;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PackageRevisionHistoryV1 } from "../shared.js";
|
|
2
|
+
import type { InjectionKey, Ref } from "vue";
|
|
3
|
+
|
|
4
|
+
export interface PackagePublisherClientState {
|
|
5
|
+
history?: PackageRevisionHistoryV1;
|
|
6
|
+
busy: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
load(): Promise<void>;
|
|
9
|
+
rollback(packageRevision: number): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const packagePublisherStateKey: InjectionKey<
|
|
13
|
+
Ref<PackagePublisherClientState>
|
|
14
|
+
> = Symbol("package-publisher-state");
|
package/src/env.d.ts
ADDED
package/src/index.ts
ADDED
package/src/manifest.ts
ADDED