@dxos/plugin-registry 0.10.0 → 0.11.0
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/lib/RegistryPlugin.mjs +15 -0
- package/dist/lib/RegistryPlugin.mjs.map +1 -0
- package/dist/lib/RegistryPlugin.node.mjs +770 -0
- package/dist/lib/RegistryPlugin.node.mjs.map +1 -0
- package/dist/lib/RegistryPlugin.workerd.mjs +8 -0
- package/dist/lib/RegistryPlugin.workerd.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +11 -0
- package/dist/lib/capabilities.mjs.map +1 -0
- package/dist/lib/chunk-BaseRegistryArticle.mjs +103 -0
- package/dist/lib/chunk-BaseRegistryArticle.mjs.map +1 -0
- package/dist/lib/chunk-LoadPluginDialog.mjs +2 -0
- package/dist/lib/chunk-PluginArticle.mjs +87 -0
- package/dist/lib/chunk-PluginArticle.mjs.map +1 -0
- package/dist/lib/chunk-PublicRegistryArticle.mjs +152 -0
- package/dist/lib/chunk-PublicRegistryArticle.mjs.map +1 -0
- package/dist/lib/chunk-RegistryArticle.mjs +29 -0
- package/dist/lib/chunk-RegistryArticle.mjs.map +1 -0
- package/dist/lib/chunk-RegistrySettingsContainer.mjs +37 -0
- package/dist/lib/chunk-RegistrySettingsContainer.mjs.map +1 -0
- package/dist/lib/chunk-app-graph-builder.mjs +202 -0
- package/dist/lib/chunk-app-graph-builder.mjs.map +1 -0
- package/dist/lib/chunk-categories.mjs +23 -0
- package/dist/lib/chunk-categories.mjs.map +1 -0
- package/dist/lib/chunk-components.mjs +537 -0
- package/dist/lib/chunk-components.mjs.map +1 -0
- package/dist/lib/chunk-containers.mjs +80 -0
- package/dist/lib/chunk-containers.mjs.map +1 -0
- package/dist/lib/chunk-dev-plugin-loader.mjs +40 -0
- package/dist/lib/chunk-dev-plugin-loader.mjs.map +1 -0
- package/dist/lib/chunk-hooks.mjs +345 -0
- package/dist/lib/chunk-hooks.mjs.map +1 -0
- package/dist/lib/chunk-meta.mjs +40 -0
- package/dist/lib/chunk-meta.mjs.map +1 -0
- package/dist/lib/chunk-open-plugin-registry.mjs +12 -0
- package/dist/lib/chunk-open-plugin-registry.mjs.map +1 -0
- package/dist/lib/chunk-operation-handler.mjs +11 -0
- package/dist/lib/chunk-operation-handler.mjs.map +1 -0
- package/dist/lib/chunk-react-surface.mjs +109 -0
- package/dist/lib/chunk-react-surface.mjs.map +1 -0
- package/dist/lib/chunk-settings.mjs +24 -0
- package/dist/lib/chunk-settings.mjs.map +1 -0
- package/dist/lib/chunk-storage.mjs +36 -0
- package/dist/lib/chunk-storage.mjs.map +1 -0
- package/dist/lib/components.mjs +2 -0
- package/dist/lib/containers.mjs +2 -0
- package/dist/lib/index.mjs +3 -0
- package/dist/lib/meta.mjs +2 -0
- package/dist/lib/operations.mjs +7 -0
- package/dist/lib/operations.mjs.map +1 -0
- package/dist/lib/plugin.mjs +9 -0
- package/dist/lib/plugin.mjs.map +1 -0
- package/dist/lib/translations.mjs +66 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +29 -0
- package/dist/lib/types.mjs.map +1 -0
- package/dist/types/src/capabilities/RegistryCategoryArticle.d.ts +11 -0
- package/dist/types/src/capabilities/RegistryCategoryArticle.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
- package/dist/types/src/capabilities/dev-plugin-loader.d.ts +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/settings.d.ts +1 -1
- package/dist/types/src/categories.d.ts.map +1 -1
- package/dist/types/src/categories.test.d.ts +2 -0
- package/dist/types/src/categories.test.d.ts.map +1 -0
- package/dist/types/src/commands/registry/util.d.ts.map +1 -1
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +1 -1
- package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
- package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +1 -1
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +1 -1
- package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -1
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +4 -1
- package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -1
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts +4 -1
- package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +1 -1
- package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -1
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +1 -1
- package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -1
- package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +4 -7
- package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -1
- package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +0 -3
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +2 -2
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +38 -37
- package/src/capabilities/RegistryCategoryArticle.tsx +46 -0
- package/src/capabilities/app-graph-builder.ts +80 -74
- package/src/capabilities/react-surface.ts +85 -0
- package/src/categories.test.ts +45 -0
- package/src/categories.ts +10 -7
- package/src/commands/registry/util.ts +1 -1
- package/src/components/PluginList/PluginItem.tsx +5 -3
- package/src/components/PluginList/PluginList.stories.tsx +1 -1
- package/src/components/RegistrySettings/RegistrySettings.tsx +2 -2
- package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +2 -2
- package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +2 -0
- package/src/containers/PluginArticle/PluginArticle.tsx +5 -1
- package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +2 -0
- package/src/hooks/usePluginActions.ts +8 -4
- package/src/meta.ts +5 -8
- package/src/operations/open-plugin-registry.ts +2 -2
- package/src/types.ts +2 -1
- package/dist/lib/neutral/RegistryPlugin.mjs +0 -27
- package/dist/lib/neutral/RegistryPlugin.mjs.map +0 -7
- package/dist/lib/neutral/RegistryPlugin.node.mjs +0 -844
- package/dist/lib/neutral/RegistryPlugin.node.mjs.map +0 -7
- package/dist/lib/neutral/RegistryPlugin.workerd.mjs +0 -10
- package/dist/lib/neutral/RegistryPlugin.workerd.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XK2JMH3H.mjs +0 -33
- package/dist/lib/neutral/chunk-XK2JMH3H.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ZY37XLVF.mjs +0 -46
- package/dist/lib/neutral/chunk-ZY37XLVF.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -25
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/open-plugin-registry-ID7LV3SF.mjs +0 -15
- package/dist/lib/neutral/open-plugin-registry-ID7LV3SF.mjs.map +0 -7
- package/dist/lib/neutral/plugin.mjs +0 -18
- package/dist/lib/neutral/plugin.mjs.map +0 -7
- package/dist/lib/neutral/translations.mjs +0 -72
- package/dist/lib/neutral/translations.mjs.map +0 -7
- package/src/capabilities/react-surface.tsx +0 -123
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
import { n as saveEnabledPlugins } from "./chunk-storage.mjs";
|
|
2
|
+
import { Plugin } from "@dxos/app-framework";
|
|
3
|
+
import { AppPlugin, AppSpace } from "@dxos/app-toolkit";
|
|
4
|
+
import * as FileSystem from "@effect/platform/FileSystem";
|
|
5
|
+
import * as Path from "@effect/platform/Path";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Schema from "effect/Schema";
|
|
8
|
+
import { meta } from "#meta";
|
|
9
|
+
import * as Command from "@effect/cli/Command";
|
|
10
|
+
import * as Args from "@effect/cli/Args";
|
|
11
|
+
import * as Console from "effect/Console";
|
|
12
|
+
import { CommandConfig, FormBuilder, printList } from "@dxos/cli-util";
|
|
13
|
+
import { invariant } from "@dxos/invariant";
|
|
14
|
+
import * as Options from "@effect/cli/Options";
|
|
15
|
+
import * as PlatformCommand from "@effect/platform/Command";
|
|
16
|
+
import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
|
|
17
|
+
import * as Function from "effect/Function";
|
|
18
|
+
import * as Option from "effect/Option";
|
|
19
|
+
import { findDxConfigFile, loadDxConfig } from "@dxos/app-framework/vite-plugin";
|
|
20
|
+
import { ClientService } from "@dxos/client";
|
|
21
|
+
import { Context } from "@dxos/context";
|
|
22
|
+
import { EdgeHttpClient } from "@dxos/edge-client";
|
|
23
|
+
import { ALL_NSIDS, Config2, NSID } from "@dxos/protocols";
|
|
24
|
+
import * as HttpClient from "@effect/platform/HttpClient";
|
|
25
|
+
import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
|
|
26
|
+
import * as Config from "effect/Config";
|
|
27
|
+
import { Filter } from "@dxos/echo";
|
|
28
|
+
import { AccessToken } from "@dxos/link";
|
|
29
|
+
//#region src/commands/plugin/disable.ts
|
|
30
|
+
var __dxlog_file$2 = "/__w/dxos/dxos/packages/plugins/plugin-registry/src/commands/plugin/disable.ts";
|
|
31
|
+
var handler$2 = Effect.fn(function* ({ id }) {
|
|
32
|
+
const { json, profile } = yield* CommandConfig;
|
|
33
|
+
const manager = yield* Plugin.Service;
|
|
34
|
+
const plugin = manager.getPlugins().find((p) => p.meta.profile.key === id);
|
|
35
|
+
invariant(plugin, `Plugin not found: ${id}`, {
|
|
36
|
+
"~LogMeta": "~LogMeta",
|
|
37
|
+
F: __dxlog_file$2,
|
|
38
|
+
L: 22,
|
|
39
|
+
S: this,
|
|
40
|
+
A: ["plugin", "`Plugin not found: ${id}`"]
|
|
41
|
+
});
|
|
42
|
+
invariant(!manager.getCore().includes(id), `Cannot disable core plugin: ${id}`, {
|
|
43
|
+
"~LogMeta": "~LogMeta",
|
|
44
|
+
F: __dxlog_file$2,
|
|
45
|
+
L: 25,
|
|
46
|
+
S: this,
|
|
47
|
+
A: ["!core.includes(id)", "`Cannot disable core plugin: ${id}`"]
|
|
48
|
+
});
|
|
49
|
+
invariant(yield* manager.disable(id), `Failed to disable plugin: ${id}`, {
|
|
50
|
+
"~LogMeta": "~LogMeta",
|
|
51
|
+
F: __dxlog_file$2,
|
|
52
|
+
L: 28,
|
|
53
|
+
S: this,
|
|
54
|
+
A: ["disabled", "`Failed to disable plugin: ${id}`"]
|
|
55
|
+
});
|
|
56
|
+
yield* saveEnabledPlugins({
|
|
57
|
+
profile,
|
|
58
|
+
enabled: [...manager.getEnabled()]
|
|
59
|
+
});
|
|
60
|
+
if (json) yield* Console.log(JSON.stringify({
|
|
61
|
+
id,
|
|
62
|
+
enabled: false
|
|
63
|
+
}, null, 2));
|
|
64
|
+
else yield* Console.log(`Plugin "${plugin.meta.profile.name ?? id}" disabled.`);
|
|
65
|
+
});
|
|
66
|
+
var disable = Command.make("disable", { id: Args.text({ name: "id" }).pipe(Args.withDescription("The ID of the plugin to disable.")) }, handler$2).pipe(Command.withDescription("Disable a plugin."));
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/commands/plugin/enable.ts
|
|
69
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/plugins/plugin-registry/src/commands/plugin/enable.ts";
|
|
70
|
+
var handler$1 = Effect.fn(function* ({ id }) {
|
|
71
|
+
const { json, profile } = yield* CommandConfig;
|
|
72
|
+
const manager = yield* Plugin.Service;
|
|
73
|
+
const plugin = manager.getPlugins().find((p) => p.meta.profile.key === id);
|
|
74
|
+
invariant(plugin, `Plugin not found: ${id}`, {
|
|
75
|
+
"~LogMeta": "~LogMeta",
|
|
76
|
+
F: __dxlog_file$1,
|
|
77
|
+
L: 22,
|
|
78
|
+
S: this,
|
|
79
|
+
A: ["plugin", "`Plugin not found: ${id}`"]
|
|
80
|
+
});
|
|
81
|
+
invariant(yield* manager.enable(id), `Failed to enable plugin: ${id}`, {
|
|
82
|
+
"~LogMeta": "~LogMeta",
|
|
83
|
+
F: __dxlog_file$1,
|
|
84
|
+
L: 25,
|
|
85
|
+
S: this,
|
|
86
|
+
A: ["enabled", "`Failed to enable plugin: ${id}`"]
|
|
87
|
+
});
|
|
88
|
+
yield* saveEnabledPlugins({
|
|
89
|
+
profile,
|
|
90
|
+
enabled: [...manager.getEnabled()]
|
|
91
|
+
});
|
|
92
|
+
if (json) yield* Console.log(JSON.stringify({
|
|
93
|
+
id,
|
|
94
|
+
enabled: true
|
|
95
|
+
}, null, 2));
|
|
96
|
+
else yield* Console.log(`Plugin "${plugin.meta.profile.name ?? id}" enabled.`);
|
|
97
|
+
});
|
|
98
|
+
var enable = Command.make("enable", { id: Args.text({ name: "id" }).pipe(Args.withDescription("The ID of the plugin to enable.")) }, handler$1).pipe(Command.withDescription("Enable a plugin."));
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/commands/plugin/util.ts
|
|
101
|
+
/**
|
|
102
|
+
* Pretty prints a plugin with ANSI colors.
|
|
103
|
+
*/
|
|
104
|
+
var printPlugin = (plugin) => {
|
|
105
|
+
const status = plugin.core ? "core" : plugin.enabled ? "enabled" : "disabled";
|
|
106
|
+
return FormBuilder.make({ title: plugin.name }).pipe(FormBuilder.set("id", plugin.id), FormBuilder.set("status", status), FormBuilder.build);
|
|
107
|
+
};
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/commands/plugin/list.ts
|
|
110
|
+
var handler = Effect.fn(function* () {
|
|
111
|
+
const { json } = yield* CommandConfig;
|
|
112
|
+
const manager = yield* Plugin.Service;
|
|
113
|
+
const plugins = manager.getPlugins();
|
|
114
|
+
const enabled = manager.getEnabled();
|
|
115
|
+
const core = manager.getCore();
|
|
116
|
+
const formattedPlugins = plugins.map((plugin) => {
|
|
117
|
+
const isEnabled = enabled.includes(plugin.meta.profile.key);
|
|
118
|
+
const isCore = core.includes(plugin.meta.profile.key);
|
|
119
|
+
return {
|
|
120
|
+
id: plugin.meta.profile.key,
|
|
121
|
+
name: plugin.meta.profile.name ?? plugin.meta.profile.key,
|
|
122
|
+
enabled: isEnabled,
|
|
123
|
+
core: isCore
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
if (json) yield* Console.log(JSON.stringify(formattedPlugins, null, 2));
|
|
127
|
+
else {
|
|
128
|
+
const formatted = formattedPlugins.map(printPlugin);
|
|
129
|
+
yield* Console.log(printList(formatted));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
var list = Command.make("list", {}, handler).pipe(Command.withDescription("List all available plugins."));
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/commands/plugin/index.ts
|
|
135
|
+
var plugin = Command.make("plugin").pipe(Command.withDescription("Manage plugins."), Command.withSubcommands([
|
|
136
|
+
enable,
|
|
137
|
+
disable,
|
|
138
|
+
list
|
|
139
|
+
]));
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/commands/registry/util.ts
|
|
142
|
+
/**
|
|
143
|
+
* Shared helpers for the `dx registry *` commands.
|
|
144
|
+
*
|
|
145
|
+
* These commands write `org.dxos.experimental.*` records to a publisher's PDS via AT Protocol
|
|
146
|
+
* XRPC. Auth has two modes (see {@link resolveSession}):
|
|
147
|
+
*
|
|
148
|
+
* - **Personal-space (default):** when logged in (`dx account login`), the publisher's atproto
|
|
149
|
+
* session lives in their personal space as the "Atmosphere" `AccessToken`. Record writes are
|
|
150
|
+
* signed by Edge's DPoP proxy (`/atproto/proxy`) — no app password required.
|
|
151
|
+
* - **App password (fallback):** explicit `--handle` / `--app-password` (or `$ATPROTO_HANDLE` /
|
|
152
|
+
* `$ATPROTO_APP_PASSWORD`) authenticate directly against the PDS with a session token.
|
|
153
|
+
*/
|
|
154
|
+
var ATMOSPHERE_SOURCE = "atproto.local";
|
|
155
|
+
var BSKY_PUBLIC_API = "https://public.api.bsky.app/xrpc";
|
|
156
|
+
var PLC_DIRECTORY = "https://plc.directory";
|
|
157
|
+
var CreateSessionResponseSchema = Schema.Struct({
|
|
158
|
+
did: Schema.String,
|
|
159
|
+
handle: Schema.String,
|
|
160
|
+
accessJwt: Schema.String,
|
|
161
|
+
refreshJwt: Schema.String
|
|
162
|
+
});
|
|
163
|
+
var ResolveHandleResponseSchema = Schema.Struct({ did: Schema.String });
|
|
164
|
+
var DidServiceSchema = Schema.Struct({
|
|
165
|
+
id: Schema.String,
|
|
166
|
+
type: Schema.optional(Schema.String),
|
|
167
|
+
serviceEndpoint: Schema.Unknown
|
|
168
|
+
});
|
|
169
|
+
var DidDocumentSchema = Schema.Struct({ service: Schema.optional(Schema.Array(DidServiceSchema)) });
|
|
170
|
+
var PutRecordResponseSchema = Schema.Struct({
|
|
171
|
+
uri: Schema.String,
|
|
172
|
+
cid: Schema.String
|
|
173
|
+
});
|
|
174
|
+
var ListRecordsEntrySchema = Schema.Struct({
|
|
175
|
+
uri: Schema.String,
|
|
176
|
+
cid: Schema.String,
|
|
177
|
+
value: Schema.Unknown
|
|
178
|
+
});
|
|
179
|
+
var ListRecordsResponseSchema = Schema.Struct({
|
|
180
|
+
cursor: Schema.optional(Schema.String),
|
|
181
|
+
records: Schema.Array(ListRecordsEntrySchema)
|
|
182
|
+
});
|
|
183
|
+
var decodeJson = (schema) => (response) => Effect.flatMap(response.json, Schema.decodeUnknown(schema));
|
|
184
|
+
/** GET an XRPC endpoint and decode the JSON response. */
|
|
185
|
+
var xrpcGet = (url, query, schema) => Effect.gen(function* () {
|
|
186
|
+
const client = yield* HttpClient.HttpClient;
|
|
187
|
+
return yield* HttpClientRequest.get(url).pipe(HttpClientRequest.setUrlParams(query), client.execute, Effect.flatMap(decodeJson(schema)), Effect.scoped);
|
|
188
|
+
});
|
|
189
|
+
/** POST a JSON body to an XRPC endpoint and decode the JSON response. */
|
|
190
|
+
var xrpcPost = (url, body, schema, headers = {}) => Effect.gen(function* () {
|
|
191
|
+
const client = yield* HttpClient.HttpClient;
|
|
192
|
+
const response = yield* HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyJson(body), Effect.flatMap((req) => client.execute(req)), Effect.scoped);
|
|
193
|
+
if (schema === void 0) return;
|
|
194
|
+
return yield* decodeJson(schema)(response);
|
|
195
|
+
});
|
|
196
|
+
/** Resolve a handle (e.g. `alice.bsky.social`) to a DID via the public XRPC. */
|
|
197
|
+
var resolveHandle = (handle) => xrpcGet(`${BSKY_PUBLIC_API}/com.atproto.identity.resolveHandle`, { handle }, ResolveHandleResponseSchema).pipe(Effect.map((response) => response.did));
|
|
198
|
+
/** Resolve a DID to its PDS service endpoint via the PLC directory (or did:web well-known). */
|
|
199
|
+
var resolvePds = (did) => Effect.gen(function* () {
|
|
200
|
+
let doc;
|
|
201
|
+
if (did.startsWith("did:plc:")) doc = yield* xrpcGet(`${PLC_DIRECTORY}/${did}`, {}, DidDocumentSchema);
|
|
202
|
+
else if (did.startsWith("did:web:")) {
|
|
203
|
+
const [host, ...segments] = did.slice(8).split(":").map((part) => decodeURIComponent(part));
|
|
204
|
+
doc = yield* xrpcGet(`https://${host}${segments.length > 0 ? `/${segments.join("/")}/did.json` : "/.well-known/did.json"}`, {}, DidDocumentSchema);
|
|
205
|
+
} else return yield* Effect.fail(/* @__PURE__ */ new Error(`Unsupported DID method: ${did}`));
|
|
206
|
+
const endpoint = (doc.service?.find((entry) => entry.id === "#atproto_pds" || entry.type === "AtprotoPersonalDataServer"))?.serviceEndpoint;
|
|
207
|
+
if (typeof endpoint !== "string" || endpoint.length === 0) return yield* Effect.fail(/* @__PURE__ */ new Error(`PDS endpoint missing in DID document for ${did}`));
|
|
208
|
+
return endpoint;
|
|
209
|
+
});
|
|
210
|
+
/** Resolve a handle or DID to its `{ did, pdsBaseUrl }`. */
|
|
211
|
+
var resolveRepo = (handleOrDid) => Effect.gen(function* () {
|
|
212
|
+
const did = handleOrDid.startsWith("did:") ? handleOrDid : yield* resolveHandle(handleOrDid);
|
|
213
|
+
return {
|
|
214
|
+
did,
|
|
215
|
+
pdsBaseUrl: yield* resolvePds(did)
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
/**
|
|
219
|
+
* App-password session — call `com.atproto.server.createSession` against the caller's resolved
|
|
220
|
+
* PDS, then return the access JWT, DID, and PDS endpoint needed for subsequent writes.
|
|
221
|
+
*/
|
|
222
|
+
var createSession = (handle, appPassword) => Effect.gen(function* () {
|
|
223
|
+
const { did, pdsBaseUrl } = yield* resolveRepo(handle);
|
|
224
|
+
const response = yield* xrpcPost(`${pdsBaseUrl}/xrpc/com.atproto.server.createSession`, {
|
|
225
|
+
identifier: handle,
|
|
226
|
+
password: appPassword
|
|
227
|
+
}, CreateSessionResponseSchema);
|
|
228
|
+
return {
|
|
229
|
+
mode: "app-password",
|
|
230
|
+
did: response.did,
|
|
231
|
+
handle: response.handle,
|
|
232
|
+
accessJwt: response.accessJwt,
|
|
233
|
+
pdsBaseUrl
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
/**
|
|
237
|
+
* Resolve an authenticated {@link Session} for the registry commands, in precedence order:
|
|
238
|
+
*
|
|
239
|
+
* 1. Explicit `--handle` + `--app-password` (or `$ATPROTO_HANDLE` / `$ATPROTO_APP_PASSWORD`) →
|
|
240
|
+
* app-password session (works without a DXOS identity).
|
|
241
|
+
* 2. The logged-in identity's personal-space "Atmosphere" `AccessToken` → DPoP-proxy session.
|
|
242
|
+
* 3. A context-specific error guiding the user to log in / connect an integration / pass creds.
|
|
243
|
+
*/
|
|
244
|
+
var resolveSession = (options) => Effect.gen(function* () {
|
|
245
|
+
const handle = options.handle ?? Option.getOrUndefined(yield* Config.option(Config.string("ATPROTO_HANDLE")));
|
|
246
|
+
const appPassword = options.appPassword ?? Option.getOrUndefined(yield* Config.option(Config.string("ATPROTO_APP_PASSWORD")));
|
|
247
|
+
if (handle && appPassword) return yield* createSession(handle, appPassword);
|
|
248
|
+
const fromPersonalSpace = yield* resolvePersonalSpaceSession(options.client);
|
|
249
|
+
if (fromPersonalSpace) return fromPersonalSpace;
|
|
250
|
+
if (options.client.halo.identity.get()) return yield* Effect.fail(/* @__PURE__ */ new Error("No atproto integration connected. Connect one with `dx integration add`, or pass --handle/--app-password. (`dx account login` cannot add an integration to an existing identity.)"));
|
|
251
|
+
return yield* Effect.fail(/* @__PURE__ */ new Error("No DXOS identity. Run `dx account login` first, or pass --handle/--app-password."));
|
|
252
|
+
});
|
|
253
|
+
/**
|
|
254
|
+
* Build a DPoP-proxy session from the personal space's "Atmosphere" `AccessToken`, or `undefined`
|
|
255
|
+
* if the user isn't logged in or hasn't connected an atproto integration.
|
|
256
|
+
*/
|
|
257
|
+
var resolvePersonalSpaceSession = (client) => Effect.gen(function* () {
|
|
258
|
+
const space = AppSpace.getPersonalSpace(client);
|
|
259
|
+
if (!space) return;
|
|
260
|
+
const token = (yield* Effect.promise(() => space.db.query(Filter.type(AccessToken.AccessToken)).run())).find((object) => object.source === ATMOSPHERE_SOURCE && !!object.account && !!object.token);
|
|
261
|
+
if (!token?.account) return;
|
|
262
|
+
const { did, pdsBaseUrl } = yield* resolveRepo(token.account);
|
|
263
|
+
return {
|
|
264
|
+
mode: "dpop-proxy",
|
|
265
|
+
did,
|
|
266
|
+
handle: token.account,
|
|
267
|
+
pdsBaseUrl,
|
|
268
|
+
edgeBaseUrl: client.edge.http.baseUrl,
|
|
269
|
+
spaceId: space.id,
|
|
270
|
+
accessTokenId: token.id,
|
|
271
|
+
token: token.token,
|
|
272
|
+
authHeader: client.edge.http._authHeader
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
/**
|
|
276
|
+
* Proxy an XRPC call through Edge's `/atproto/proxy`, which attaches the DPoP proof signed with
|
|
277
|
+
* the key Edge stored at OAuth time. The proxy forwards `request` verbatim and returns the
|
|
278
|
+
* upstream PDS response body, so callers decode the same schema as the direct path.
|
|
279
|
+
*/
|
|
280
|
+
var proxyCall = (session, params, schema) => {
|
|
281
|
+
const query = params.query ? `?${new URLSearchParams(params.query).toString()}` : "";
|
|
282
|
+
const innerHeaders = {
|
|
283
|
+
Accept: "application/json",
|
|
284
|
+
Authorization: `DPoP ${session.token}`
|
|
285
|
+
};
|
|
286
|
+
if (params.jsonBody) innerHeaders["Content-Type"] = "application/json";
|
|
287
|
+
const outerHeaders = {};
|
|
288
|
+
if (session.authHeader) outerHeaders.Authorization = session.authHeader;
|
|
289
|
+
return xrpcPost(`${session.edgeBaseUrl.replace(/\/$/, "")}/atproto/proxy`, {
|
|
290
|
+
spaceId: session.spaceId,
|
|
291
|
+
accessTokenId: session.accessTokenId,
|
|
292
|
+
request: {
|
|
293
|
+
endpoint: `${session.pdsBaseUrl}/xrpc/${params.xrpcMethod}${query}`,
|
|
294
|
+
method: params.method,
|
|
295
|
+
headers: innerHeaders,
|
|
296
|
+
body: params.jsonBody ? JSON.stringify(params.jsonBody) : null
|
|
297
|
+
}
|
|
298
|
+
}, schema, outerHeaders);
|
|
299
|
+
};
|
|
300
|
+
/** Write a record at `com.atproto.repo.putRecord` (idempotent on rkey). */
|
|
301
|
+
var putRecord = (session, collection, rkey, record) => {
|
|
302
|
+
const body = {
|
|
303
|
+
repo: session.did,
|
|
304
|
+
collection,
|
|
305
|
+
rkey,
|
|
306
|
+
record: {
|
|
307
|
+
$type: collection,
|
|
308
|
+
...record
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
return session.mode === "app-password" ? xrpcPost(`${session.pdsBaseUrl}/xrpc/com.atproto.repo.putRecord`, body, PutRecordResponseSchema, { Authorization: `Bearer ${session.accessJwt}` }) : proxyCall(session, {
|
|
312
|
+
xrpcMethod: "com.atproto.repo.putRecord",
|
|
313
|
+
method: "POST",
|
|
314
|
+
jsonBody: body
|
|
315
|
+
}, PutRecordResponseSchema);
|
|
316
|
+
};
|
|
317
|
+
/** Delete a record at `com.atproto.repo.deleteRecord`. */
|
|
318
|
+
var deleteRecord = (session, collection, rkey) => {
|
|
319
|
+
const body = {
|
|
320
|
+
repo: session.did,
|
|
321
|
+
collection,
|
|
322
|
+
rkey
|
|
323
|
+
};
|
|
324
|
+
return session.mode === "app-password" ? xrpcPost(`${session.pdsBaseUrl}/xrpc/com.atproto.repo.deleteRecord`, body, void 0, { Authorization: `Bearer ${session.accessJwt}` }) : proxyCall(session, {
|
|
325
|
+
xrpcMethod: "com.atproto.repo.deleteRecord",
|
|
326
|
+
method: "POST",
|
|
327
|
+
jsonBody: body
|
|
328
|
+
}, void 0);
|
|
329
|
+
};
|
|
330
|
+
/**
|
|
331
|
+
* List records in a single collection on the authenticated repo. Paging is left to the caller (the
|
|
332
|
+
* four registry collections are tiny in practice); the cursor is surfaced on the return.
|
|
333
|
+
*/
|
|
334
|
+
var listRecords = (session, collection, options = {}) => {
|
|
335
|
+
const query = {
|
|
336
|
+
repo: session.did,
|
|
337
|
+
collection,
|
|
338
|
+
...options.limit !== void 0 ? { limit: String(options.limit) } : {},
|
|
339
|
+
...options.cursor !== void 0 ? { cursor: options.cursor } : {}
|
|
340
|
+
};
|
|
341
|
+
return session.mode === "app-password" ? xrpcGet(`${session.pdsBaseUrl}/xrpc/com.atproto.repo.listRecords`, query, ListRecordsResponseSchema) : proxyCall(session, {
|
|
342
|
+
xrpcMethod: "com.atproto.repo.listRecords",
|
|
343
|
+
method: "GET",
|
|
344
|
+
query
|
|
345
|
+
}, ListRecordsResponseSchema);
|
|
346
|
+
};
|
|
347
|
+
var AUTH_OPTION_DESCRIPTIONS = {
|
|
348
|
+
handle: "AT Protocol handle (e.g. alice.bsky.social). Defaults to the logged-in identity; falls back to $ATPROTO_HANDLE.",
|
|
349
|
+
appPassword: "AT Protocol app password (https://bsky.app/settings/app-passwords). Falls back to $ATPROTO_APP_PASSWORD. Not needed when logged in via `dx account login`."
|
|
350
|
+
};
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/commands/registry/publish.ts
|
|
353
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-registry/src/commands/registry/publish.ts";
|
|
354
|
+
/** Manifest emitted by the build (subset consumed here). Extends `Config2.Plugin` with build-time fields. */
|
|
355
|
+
var ManifestSchema = Schema.Struct({
|
|
356
|
+
...Config2.Plugin.fields,
|
|
357
|
+
version: Schema.String.pipe(Schema.nonEmptyString()),
|
|
358
|
+
dependencies: Schema.optional(Schema.Record({
|
|
359
|
+
key: Schema.String,
|
|
360
|
+
value: Schema.String
|
|
361
|
+
}))
|
|
362
|
+
});
|
|
363
|
+
var ensureTrailingSlash = (url) => url.endsWith("/") ? url : `${url}/`;
|
|
364
|
+
var sha256Base64 = async (bytes) => {
|
|
365
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
366
|
+
return Buffer.from(new Uint8Array(digest)).toString("base64");
|
|
367
|
+
};
|
|
368
|
+
/**
|
|
369
|
+
* `dx registry publish` — config-driven build + publish.
|
|
370
|
+
*
|
|
371
|
+
* Reads the build/publish orchestration from `dx.config.ts`, runs the declared build command,
|
|
372
|
+
* reads the emitted `manifest.json`, hosts the bundle (default: upload the build
|
|
373
|
+
* output to the DXOS edge registry; override with `publish.assetBaseUrl` to point
|
|
374
|
+
* at your own already-hosted directory), then writes the `plugin.profile` and
|
|
375
|
+
* `plugin.release` records to the authenticated publisher's PDS. Release
|
|
376
|
+
* integrity is anchored by `manifestHash` (sha256 of `manifest.json`) in the
|
|
377
|
+
* signed release record.
|
|
378
|
+
*/
|
|
379
|
+
var publish = Command.make("publish", {
|
|
380
|
+
handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
|
|
381
|
+
appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
|
|
382
|
+
dir: Options.text("dir").pipe(Options.withDescription("Project directory containing dx.config.ts. Defaults to the current directory."), Options.withDefault(".")),
|
|
383
|
+
noBuild: Options.boolean("no-build").pipe(Options.withDescription("Skip running the build command (publish a pre-built dist).")),
|
|
384
|
+
assetBaseUrl: Options.text("asset-base-url").pipe(Options.withDescription("Skip upload and point the release at an already-hosted bundle directory."), Options.optional),
|
|
385
|
+
edgeUrl: Options.text("edge-url").pipe(Options.withDescription("Edge base URL for bundle upload (e.g. http://localhost:8787). Bypasses profile config; auth is skipped (requires WORKER_ENV=dev on the server)."), Options.optional)
|
|
386
|
+
}, (options) => Function.pipe(Effect.gen(function* () {
|
|
387
|
+
const fs = yield* FileSystem.FileSystem;
|
|
388
|
+
const path = yield* Path.Path;
|
|
389
|
+
const dir = path.resolve(options.dir);
|
|
390
|
+
const configFile = findDxConfigFile(dir);
|
|
391
|
+
if (!configFile) return yield* Effect.fail(/* @__PURE__ */ new Error(`No dx.config.ts found in ${dir}.`));
|
|
392
|
+
const config = yield* Effect.tryPromise({
|
|
393
|
+
try: () => loadDxConfig(configFile),
|
|
394
|
+
catch: (error) => /* @__PURE__ */ new Error(`Failed to load dx.config.ts in ${dir}: ${error}`)
|
|
395
|
+
});
|
|
396
|
+
const buildCommand = config.publish?.buildCommand;
|
|
397
|
+
if (!options.noBuild && buildCommand) {
|
|
398
|
+
yield* Console.log(`Building: ${buildCommand}`);
|
|
399
|
+
const binDir = path.join(dir, "node_modules", ".bin");
|
|
400
|
+
const exitCode = yield* PlatformCommand.make("sh", "-c", `export PATH="${binDir}:$PATH"; ${buildCommand}`).pipe(PlatformCommand.workingDirectory(dir), PlatformCommand.stdout("inherit"), PlatformCommand.stderr("inherit"), PlatformCommand.exitCode);
|
|
401
|
+
if (exitCode !== 0) return yield* Effect.fail(/* @__PURE__ */ new Error(`Build failed (exit ${exitCode}): ${buildCommand}`));
|
|
402
|
+
}
|
|
403
|
+
const outdir = path.join(dir, config.publish?.outputDirectory ?? "dist");
|
|
404
|
+
const manifestPath = path.join(outdir, "manifest.json");
|
|
405
|
+
if (!(yield* fs.exists(manifestPath))) return yield* Effect.fail(/* @__PURE__ */ new Error(`manifest.json not found in ${outdir}. Did the build run?`));
|
|
406
|
+
const manifestRaw = yield* fs.readFileString(manifestPath);
|
|
407
|
+
const manifest = yield* Schema.decodeUnknown(ManifestSchema)(JSON.parse(manifestRaw));
|
|
408
|
+
const key = manifest.key;
|
|
409
|
+
const version = manifest.version;
|
|
410
|
+
const manifestHash = `sha256-${yield* Effect.promise(() => sha256Base64(new TextEncoder().encode(manifestRaw)))}`;
|
|
411
|
+
const assetBaseUrl = Option.getOrUndefined(options.assetBaseUrl) ?? config.publish?.assetBaseUrl;
|
|
412
|
+
let moduleUrl;
|
|
413
|
+
if (assetBaseUrl) {
|
|
414
|
+
moduleUrl = new URL("manifest.json", ensureTrailingSlash(assetBaseUrl)).toString();
|
|
415
|
+
yield* Console.log(`Self-hosted: ${moduleUrl}`);
|
|
416
|
+
} else {
|
|
417
|
+
const explicitEdgeUrl = Option.getOrUndefined(options.edgeUrl);
|
|
418
|
+
if (explicitEdgeUrl) moduleUrl = yield* uploadBundleDirect({
|
|
419
|
+
http: new EdgeHttpClient(explicitEdgeUrl),
|
|
420
|
+
key,
|
|
421
|
+
version,
|
|
422
|
+
outdir
|
|
423
|
+
});
|
|
424
|
+
else {
|
|
425
|
+
const client = yield* ClientService;
|
|
426
|
+
moduleUrl = yield* uploadBundle({
|
|
427
|
+
client,
|
|
428
|
+
key,
|
|
429
|
+
version,
|
|
430
|
+
outdir,
|
|
431
|
+
auth: !!client.halo.identity.get()
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
yield* Console.log(`Uploaded: ${moduleUrl}`);
|
|
435
|
+
}
|
|
436
|
+
const client = yield* ClientService;
|
|
437
|
+
const session = yield* resolveSession({
|
|
438
|
+
handle: Option.getOrUndefined(options.handle),
|
|
439
|
+
appPassword: Option.getOrUndefined(options.appPassword),
|
|
440
|
+
client
|
|
441
|
+
});
|
|
442
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
443
|
+
const profile = {
|
|
444
|
+
key,
|
|
445
|
+
name: manifest.name,
|
|
446
|
+
createdAt
|
|
447
|
+
};
|
|
448
|
+
if (manifest.description !== void 0) profile.description = manifest.description;
|
|
449
|
+
if (manifest.homePage !== void 0) profile.homePage = manifest.homePage;
|
|
450
|
+
if (manifest.source !== void 0) profile.source = manifest.source;
|
|
451
|
+
if (manifest.icon !== void 0) profile.icon = manifest.icon;
|
|
452
|
+
if (manifest.tags && manifest.tags.length > 0) profile.tags = manifest.tags;
|
|
453
|
+
if (manifest.screenshots && manifest.screenshots.length > 0) profile.screenshots = manifest.screenshots;
|
|
454
|
+
if (manifest.dependsOn?.length) profile.dependsOn = manifest.dependsOn;
|
|
455
|
+
if (manifest.spec !== void 0) profile.spec = manifest.spec;
|
|
456
|
+
const profileResult = yield* putRecord(session, NSID.PluginProfile, key, profile);
|
|
457
|
+
yield* Console.log(`Profile ${profileResult.uri}`);
|
|
458
|
+
const releaseResult = yield* putRecord(session, NSID.PluginRelease, `${key}:${version}`, {
|
|
459
|
+
pluginKey: key,
|
|
460
|
+
version,
|
|
461
|
+
moduleUrl,
|
|
462
|
+
manifestHash,
|
|
463
|
+
createdAt,
|
|
464
|
+
...manifest.dependencies ? { dependencies: manifest.dependencies } : {}
|
|
465
|
+
});
|
|
466
|
+
yield* Console.log(`Release ${releaseResult.uri}`);
|
|
467
|
+
}), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Build, host, and publish the plugin in the current directory to the registry."));
|
|
468
|
+
/**
|
|
469
|
+
* Upload the build output to the edge registry via the authenticated edge client.
|
|
470
|
+
* The edge gates `/registry/upload` on the caller's hub identity (verifiable
|
|
471
|
+
* presentation; bypassed for local dev). Release integrity is anchored by the
|
|
472
|
+
* signed record's `manifestHash`, not the transport. Returns the canonical
|
|
473
|
+
* `moduleUrl` (the hosted `manifest.json`), targeting the client's configured edge.
|
|
474
|
+
*/
|
|
475
|
+
var uploadBundle = ({ client, key, version, outdir, auth = true }) => Effect.gen(function* () {
|
|
476
|
+
const fs = yield* FileSystem.FileSystem;
|
|
477
|
+
const path = yield* Path.Path;
|
|
478
|
+
const entries = yield* fs.readDirectory(outdir, { recursive: true });
|
|
479
|
+
const files = [];
|
|
480
|
+
for (const entry of entries) {
|
|
481
|
+
const full = path.join(outdir, entry);
|
|
482
|
+
if ((yield* fs.stat(full)).type !== "File") continue;
|
|
483
|
+
const bytes = yield* fs.readFile(full);
|
|
484
|
+
files.push({
|
|
485
|
+
path: entry.split(path.sep).join("/"),
|
|
486
|
+
content: Buffer.from(bytes).toString("base64")
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
const { moduleUrl } = yield* Effect.tryPromise(() => client.edge.http.uploadPluginBundle(Context.default(void 0, {
|
|
490
|
+
"~LogMeta": "~LogMeta",
|
|
491
|
+
F: __dxlog_file,
|
|
492
|
+
L: 238
|
|
493
|
+
}), {
|
|
494
|
+
slug: key,
|
|
495
|
+
version,
|
|
496
|
+
files
|
|
497
|
+
}, { auth }));
|
|
498
|
+
return moduleUrl;
|
|
499
|
+
});
|
|
500
|
+
/**
|
|
501
|
+
* Upload using a standalone EdgeHttpClient (no DXOS profile required).
|
|
502
|
+
* Auth is skipped — the server must have WORKER_ENV=dev to accept unauthenticated uploads.
|
|
503
|
+
*/
|
|
504
|
+
var uploadBundleDirect = ({ http, key, version, outdir }) => Effect.gen(function* () {
|
|
505
|
+
const fs = yield* FileSystem.FileSystem;
|
|
506
|
+
const path = yield* Path.Path;
|
|
507
|
+
const entries = yield* fs.readDirectory(outdir, { recursive: true });
|
|
508
|
+
const files = [];
|
|
509
|
+
for (const entry of entries) {
|
|
510
|
+
const full = path.join(outdir, entry);
|
|
511
|
+
if ((yield* fs.stat(full)).type !== "File") continue;
|
|
512
|
+
const bytes = yield* fs.readFile(full);
|
|
513
|
+
files.push({
|
|
514
|
+
path: entry.split(path.sep).join("/"),
|
|
515
|
+
content: Buffer.from(bytes).toString("base64")
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
const { moduleUrl } = yield* Effect.tryPromise(() => http.uploadPluginBundle(Context.default(void 0, {
|
|
519
|
+
"~LogMeta": "~LogMeta",
|
|
520
|
+
F: __dxlog_file,
|
|
521
|
+
L: 275
|
|
522
|
+
}), {
|
|
523
|
+
slug: key,
|
|
524
|
+
version,
|
|
525
|
+
files
|
|
526
|
+
}, { auth: false }));
|
|
527
|
+
return moduleUrl;
|
|
528
|
+
});
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/commands/registry/publish-package.ts
|
|
531
|
+
/**
|
|
532
|
+
* `dx registry publish-package` — publishes both the mutable
|
|
533
|
+
* `plugin.profile` (rkey = key) and a `plugin.release`
|
|
534
|
+
* (rkey = `<key>:<version>`) record to the authenticated user's PDS.
|
|
535
|
+
*
|
|
536
|
+
* The two writes are independent `putRecord` (upsert) calls; if the second fails the
|
|
537
|
+
* first is left in place (the indexer treats a profile with no releases as
|
|
538
|
+
* present-but-unpublishable, so a half-success is benign). Releases are conceptually
|
|
539
|
+
* single-write per version but not enforced — see docs/registry-spec.md (Integrity).
|
|
540
|
+
*/
|
|
541
|
+
var publishPackage = Command.make("publish-package", {
|
|
542
|
+
handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
|
|
543
|
+
appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
|
|
544
|
+
key: Options.text("key").pipe(Options.withDescription("Plugin key — a reverse-domain NSID (e.g. org.dxos.plugin.excalidraw); also the profile record rkey.")),
|
|
545
|
+
name: Options.text("name").pipe(Options.withDescription("Human-readable package name.")),
|
|
546
|
+
description: Options.text("description").pipe(Options.withDescription("Short description shown in the registry."), Options.withDefault("")),
|
|
547
|
+
version: Options.text("version").pipe(Options.withDescription("Semver version (no build metadata). The release rkey is `<key>:<version>`.")),
|
|
548
|
+
moduleUrl: Options.text("module-url").pipe(Options.withDescription("HTTPS URL to the bundle or manifest for this release.")),
|
|
549
|
+
homepage: Options.text("homepage").pipe(Options.withDescription("Homepage URL."), Options.optional),
|
|
550
|
+
source: Options.text("source").pipe(Options.withDescription("Source repository URL."), Options.optional),
|
|
551
|
+
tag: Options.text("tag").pipe(Options.withDescription("Tag (repeatable). Categorizes the package for discovery."), Options.repeated),
|
|
552
|
+
iconKey: Options.text("icon-key").pipe(Options.withDescription("Icon identifier (e.g. ph--sparkle--regular) resolvable by @ch-ui/icons."), Options.optional),
|
|
553
|
+
iconHue: Options.text("icon-hue").pipe(Options.withDescription("Icon palette hue (e.g. blue)."), Options.optional),
|
|
554
|
+
manifestHash: Options.text("manifest-hash").pipe(Options.withDescription("Optional content hash for the bundle/manifest."), Options.optional)
|
|
555
|
+
}, (options) => Function.pipe(Effect.gen(function* () {
|
|
556
|
+
const client = yield* ClientService;
|
|
557
|
+
const session = yield* resolveSession({
|
|
558
|
+
handle: Option.getOrUndefined(options.handle),
|
|
559
|
+
appPassword: Option.getOrUndefined(options.appPassword),
|
|
560
|
+
client
|
|
561
|
+
});
|
|
562
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
563
|
+
const homepage = Option.getOrUndefined(options.homepage);
|
|
564
|
+
const source = Option.getOrUndefined(options.source);
|
|
565
|
+
const iconKey = Option.getOrUndefined(options.iconKey);
|
|
566
|
+
const iconHue = Option.getOrUndefined(options.iconHue);
|
|
567
|
+
const icon = iconKey !== void 0 ? {
|
|
568
|
+
key: iconKey,
|
|
569
|
+
...iconHue !== void 0 && { hue: iconHue }
|
|
570
|
+
} : void 0;
|
|
571
|
+
const profile = {
|
|
572
|
+
key: options.key,
|
|
573
|
+
name: options.name,
|
|
574
|
+
...options.description.length > 0 && { description: options.description },
|
|
575
|
+
...homepage !== void 0 && { homePage: homepage },
|
|
576
|
+
...source !== void 0 && { source },
|
|
577
|
+
...options.tag.length > 0 && { tags: options.tag },
|
|
578
|
+
...icon !== void 0 && { icon },
|
|
579
|
+
createdAt
|
|
580
|
+
};
|
|
581
|
+
const profileResult = yield* putRecord(session, NSID.PluginProfile, options.key, profile);
|
|
582
|
+
yield* Console.log(`Profile ${profileResult.uri}`);
|
|
583
|
+
const manifestHash = Option.getOrUndefined(options.manifestHash);
|
|
584
|
+
const release = {
|
|
585
|
+
pluginKey: options.key,
|
|
586
|
+
version: options.version,
|
|
587
|
+
moduleUrl: options.moduleUrl,
|
|
588
|
+
createdAt,
|
|
589
|
+
...manifestHash !== void 0 && { manifestHash }
|
|
590
|
+
};
|
|
591
|
+
const releaseResult = yield* putRecord(session, NSID.PluginRelease, `${options.key}:${options.version}`, release);
|
|
592
|
+
yield* Console.log(`Release ${releaseResult.uri}`);
|
|
593
|
+
}), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Publish a package profile + release to the authenticated user's PDS."));
|
|
594
|
+
//#endregion
|
|
595
|
+
//#region src/commands/registry/publish-publisher.ts
|
|
596
|
+
/**
|
|
597
|
+
* `dx registry publish-publisher` — publishes the authenticated user's own
|
|
598
|
+
* `org.dxos.experimental.publisher.profile` record (rkey = `self`).
|
|
599
|
+
*
|
|
600
|
+
* This is metadata about the publisher (the human/org), shown alongside their
|
|
601
|
+
* packages once a verifier has attested to their identity.
|
|
602
|
+
*/
|
|
603
|
+
var publishPublisher = Command.make("publish-publisher", {
|
|
604
|
+
handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
|
|
605
|
+
appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
|
|
606
|
+
displayName: Options.text("display-name").pipe(Options.withDescription("Publisher display name (the human/org name shown in UIs).")),
|
|
607
|
+
bio: Options.text("bio").pipe(Options.withDescription("Short bio."), Options.optional),
|
|
608
|
+
homepageUrl: Options.text("homepage-url").pipe(Options.withDescription("Publisher homepage URL."), Options.optional),
|
|
609
|
+
contact: Options.text("contact").pipe(Options.withDescription("Contact (email, handle, etc.)."), Options.optional)
|
|
610
|
+
}, (options) => Function.pipe(Effect.gen(function* () {
|
|
611
|
+
const client = yield* ClientService;
|
|
612
|
+
const session = yield* resolveSession({
|
|
613
|
+
handle: Option.getOrUndefined(options.handle),
|
|
614
|
+
appPassword: Option.getOrUndefined(options.appPassword),
|
|
615
|
+
client
|
|
616
|
+
});
|
|
617
|
+
const record = { displayName: options.displayName };
|
|
618
|
+
const bio = Option.getOrUndefined(options.bio);
|
|
619
|
+
if (bio !== void 0) record.bio = bio;
|
|
620
|
+
const homepageUrl = Option.getOrUndefined(options.homepageUrl);
|
|
621
|
+
if (homepageUrl !== void 0) record.homepageUrl = homepageUrl;
|
|
622
|
+
const contact = Option.getOrUndefined(options.contact);
|
|
623
|
+
if (contact !== void 0) record.contact = contact;
|
|
624
|
+
const result = yield* putRecord(session, NSID.PublisherProfile, "self", record);
|
|
625
|
+
yield* Console.log(`Publisher ${result.uri}`);
|
|
626
|
+
}), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Publish the authenticated user's publisher.profile record (rkey = self)."));
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region src/commands/registry/records.ts
|
|
629
|
+
/**
|
|
630
|
+
* `dx registry records` — lists all `org.dxos.experimental.*` records on the
|
|
631
|
+
* authenticated user's repo. Useful for double-checking what's been published
|
|
632
|
+
* and what the registry indexer is about to ingest.
|
|
633
|
+
*/
|
|
634
|
+
var records = Command.make("records", {
|
|
635
|
+
handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
|
|
636
|
+
appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional)
|
|
637
|
+
}, (options) => Function.pipe(Effect.gen(function* () {
|
|
638
|
+
const { json } = yield* CommandConfig;
|
|
639
|
+
const client = yield* ClientService;
|
|
640
|
+
const session = yield* resolveSession({
|
|
641
|
+
handle: Option.getOrUndefined(options.handle),
|
|
642
|
+
appPassword: Option.getOrUndefined(options.appPassword),
|
|
643
|
+
client
|
|
644
|
+
});
|
|
645
|
+
const results = yield* Effect.all(ALL_NSIDS.map((collection) => listRecords(session, collection).pipe(Effect.map(({ records: entries }) => ({
|
|
646
|
+
collection,
|
|
647
|
+
records: entries
|
|
648
|
+
})), Effect.catchAll(() => Effect.succeed({
|
|
649
|
+
collection,
|
|
650
|
+
records: []
|
|
651
|
+
})))), { concurrency: "unbounded" });
|
|
652
|
+
if (json) {
|
|
653
|
+
yield* Console.log(JSON.stringify({
|
|
654
|
+
did: session.did,
|
|
655
|
+
results
|
|
656
|
+
}, null, 2));
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
yield* Console.log(`Repo: ${session.did} (${session.handle})`);
|
|
660
|
+
for (const { collection, records: entries } of results) {
|
|
661
|
+
yield* Console.log(`\n${collection} (${entries.length})`);
|
|
662
|
+
for (const entry of entries) yield* Console.log(` ${entry.uri}`);
|
|
663
|
+
}
|
|
664
|
+
}), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("List org.dxos.experimental.* records on the authenticated user's repo."));
|
|
665
|
+
//#endregion
|
|
666
|
+
//#region src/commands/registry/unpublish.ts
|
|
667
|
+
var rkeyOf = (uri) => uri.split("/").pop() ?? "";
|
|
668
|
+
/**
|
|
669
|
+
* `dx registry unpublish` — removes a plugin from the authenticated user's PDS:
|
|
670
|
+
* deletes the `plugin.profile` (rkey = key) and every `plugin.release`
|
|
671
|
+
* (rkey = `<key>:<version>`). The registry indexer unindexes on its next sweep.
|
|
672
|
+
* Hosted bundles in edge R2 are immutable and are not removed.
|
|
673
|
+
*/
|
|
674
|
+
var unpublish = Command.make("unpublish", {
|
|
675
|
+
handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
|
|
676
|
+
appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
|
|
677
|
+
key: Options.text("key").pipe(Options.withDescription("Plugin key (the profile rkey). Removes its profile and all releases."))
|
|
678
|
+
}, (options) => Function.pipe(Effect.gen(function* () {
|
|
679
|
+
const client = yield* ClientService;
|
|
680
|
+
const session = yield* resolveSession({
|
|
681
|
+
handle: Option.getOrUndefined(options.handle),
|
|
682
|
+
appPassword: Option.getOrUndefined(options.appPassword),
|
|
683
|
+
client
|
|
684
|
+
});
|
|
685
|
+
const key = options.key;
|
|
686
|
+
let cursor;
|
|
687
|
+
let deletedReleases = 0;
|
|
688
|
+
do {
|
|
689
|
+
const releases = yield* listRecords(session, NSID.PluginRelease, {
|
|
690
|
+
limit: 100,
|
|
691
|
+
cursor
|
|
692
|
+
});
|
|
693
|
+
cursor = releases.cursor;
|
|
694
|
+
for (const record of releases.records) {
|
|
695
|
+
const rkey = rkeyOf(record.uri);
|
|
696
|
+
if (rkey.startsWith(`${key}:`)) {
|
|
697
|
+
yield* deleteRecord(session, NSID.PluginRelease, rkey);
|
|
698
|
+
yield* Console.log(`Deleted release ${rkey}`);
|
|
699
|
+
deletedReleases += 1;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
} while (cursor !== void 0);
|
|
703
|
+
yield* deleteRecord(session, NSID.PluginProfile, key);
|
|
704
|
+
yield* Console.log(`Deleted profile ${key} (${deletedReleases} release(s) removed)`);
|
|
705
|
+
}), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Remove a package (profile + releases) from the authenticated user's PDS."));
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region src/commands/registry/verify.ts
|
|
708
|
+
/**
|
|
709
|
+
* `dx registry verify` — publishes a `org.dxos.experimental.publisher.verification`
|
|
710
|
+
* record on the authenticated repo, with rkey = subject DID. Subject is given as a
|
|
711
|
+
* handle or DID; handles are resolved through the public XRPC.
|
|
712
|
+
*
|
|
713
|
+
* Verification records are public and anyone may author them. The registry indexer only
|
|
714
|
+
* honors records authored by the configured verifier (`REGISTRY_CURATOR_DID`), so this
|
|
715
|
+
* command only affects discovery when run with that verifier's credentials.
|
|
716
|
+
*/
|
|
717
|
+
var verify = Command.make("verify", {
|
|
718
|
+
handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
|
|
719
|
+
appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
|
|
720
|
+
subject: Options.text("subject").pipe(Options.withDescription("Handle or DID of the publisher being verified.")),
|
|
721
|
+
subjectHandle: Options.text("subject-handle").pipe(Options.withDescription("Optional human-readable handle to record for the verified subject (defaults to --subject if it is a handle)."), Options.optional),
|
|
722
|
+
displayName: Options.text("display-name").pipe(Options.withDescription("Display name to record for the verified subject."))
|
|
723
|
+
}, (options) => Function.pipe(Effect.gen(function* () {
|
|
724
|
+
const client = yield* ClientService;
|
|
725
|
+
const session = yield* resolveSession({
|
|
726
|
+
handle: Option.getOrUndefined(options.handle),
|
|
727
|
+
appPassword: Option.getOrUndefined(options.appPassword),
|
|
728
|
+
client
|
|
729
|
+
});
|
|
730
|
+
const subjectDid = options.subject.startsWith("did:") ? options.subject : yield* resolveHandle(options.subject);
|
|
731
|
+
const subjectHandle = Option.getOrUndefined(options.subjectHandle) ?? (options.subject.startsWith("did:") ? "" : options.subject);
|
|
732
|
+
const record = {
|
|
733
|
+
subject: subjectDid,
|
|
734
|
+
...subjectHandle ? { handle: subjectHandle } : {},
|
|
735
|
+
displayName: options.displayName,
|
|
736
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
737
|
+
};
|
|
738
|
+
const result = yield* putRecord(session, NSID.PublisherVerification, subjectDid, record);
|
|
739
|
+
yield* Console.log(`Verified ${subjectDid} -> ${result.uri}`);
|
|
740
|
+
}), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Write a publisher.verification record (indexed only if authored by the configured verifier, REGISTRY_CURATOR_DID)."));
|
|
741
|
+
//#endregion
|
|
742
|
+
//#region src/commands/registry/index.ts
|
|
743
|
+
/**
|
|
744
|
+
* `dx registry *` — commands for publishing and inspecting records that the
|
|
745
|
+
* DXOS plugin registry indexes from AT Protocol. Auth uses an App Password
|
|
746
|
+
* (per-user secret minted at https://bsky.app/settings/app-passwords); OAuth
|
|
747
|
+
* support is a follow-up.
|
|
748
|
+
*
|
|
749
|
+
* - `publish` — config-driven: build from dx.yml, host the bundle, write records.
|
|
750
|
+
* - `publish-package` — low-level: write profile + release records from flags.
|
|
751
|
+
* - `publish-publisher` — write the publisher's own profile record.
|
|
752
|
+
* - `unpublish` — remove a package (profile + releases) from your PDS.
|
|
753
|
+
* - `verify` — write a publisher.verification record (indexed only from the configured verifier).
|
|
754
|
+
* - `records` — list everything the authenticated repo has published under `org.dxos.experimental.*`.
|
|
755
|
+
*/
|
|
756
|
+
var registry = Command.make("registry").pipe(Command.withDescription("Publish to and inspect the AT Protocol-backed DXOS plugin registry."), Command.withSubcommands([
|
|
757
|
+
publish,
|
|
758
|
+
publishPackage,
|
|
759
|
+
publishPublisher,
|
|
760
|
+
unpublish,
|
|
761
|
+
verify,
|
|
762
|
+
records
|
|
763
|
+
]));
|
|
764
|
+
//#endregion
|
|
765
|
+
//#region src/RegistryPlugin.node.ts
|
|
766
|
+
var RegistryPlugin = Plugin.define(meta).pipe(AppPlugin.addCommandModule({ commands: [plugin, registry] }), Plugin.make);
|
|
767
|
+
//#endregion
|
|
768
|
+
export { RegistryPlugin, RegistryPlugin as default };
|
|
769
|
+
|
|
770
|
+
//# sourceMappingURL=RegistryPlugin.node.mjs.map
|