@dxos/cli-util 0.9.1-staging.ee54ba693a → 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/chunk-command-config.mjs +18 -0
- package/dist/lib/chunk-command-config.mjs.map +1 -0
- package/dist/lib/index.mjs +601 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/testing.mjs +69 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/util/format.d.ts +5 -0
- package/dist/types/src/util/format.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/space-format.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -17
- package/src/util/format.ts +19 -0
- package/src/util/index.ts +1 -0
- package/src/util/space-format.ts +7 -3
- package/dist/lib/node-esm/chunk-N5LOOWPE.mjs +0 -27
- package/dist/lib/node-esm/chunk-N5LOOWPE.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -695
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -82
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
//#region src/services/command-config.ts
|
|
5
|
+
var CommandConfig = class CommandConfig extends Context.Tag("CommandConfig")() {
|
|
6
|
+
static isJson = CommandConfig.pipe(Effect.map((config) => config.json));
|
|
7
|
+
static isVerbose = CommandConfig.pipe(Effect.map((config) => config.verbose));
|
|
8
|
+
static layerTest = Layer.succeed(CommandConfig, {
|
|
9
|
+
json: true,
|
|
10
|
+
verbose: false,
|
|
11
|
+
profile: "default",
|
|
12
|
+
logLevel: "info"
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { CommandConfig as t };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=chunk-command-config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-command-config.mjs","names":[],"sources":["../../src/services/command-config.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nexport class CommandConfig extends Context.Tag('CommandConfig')<\n CommandConfig,\n {\n json: boolean;\n verbose: boolean;\n profile: string;\n logLevel: string;\n }\n>() {\n static isJson: Effect.Effect<boolean, never, CommandConfig> = CommandConfig.pipe(Effect.map((config) => config.json));\n static isVerbose: Effect.Effect<boolean, never, CommandConfig> = CommandConfig.pipe(\n Effect.map((config) => config.verbose),\n );\n\n static layerTest = Layer.succeed(CommandConfig, {\n json: true,\n verbose: false,\n profile: 'default',\n logLevel: 'info',\n });\n}\n"],"mappings":";;;;AAQA,IAAa,gBAAb,MAAa,sBAAsB,QAAQ,IAAI,eAAe,CAAC,CAQ7D,CAAC,CAAC;CACF,OAAO,SAAuD,cAAc,KAAK,OAAO,KAAK,WAAW,OAAO,IAAI,CAAC;CACpH,OAAO,YAA0D,cAAc,KAC7E,OAAO,KAAK,WAAW,OAAO,OAAO,CACvC;CAEA,OAAO,YAAY,MAAM,QAAQ,eAAe;EAC9C,MAAM;EACN,SAAS;EACT,SAAS;EACT,UAAU;CACZ,CAAC;AACH"}
|
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import { t as CommandConfig } from "./chunk-command-config.mjs";
|
|
2
|
+
import * as BunHttpServer from "@effect/platform-bun/BunHttpServer";
|
|
3
|
+
import * as HttpRouter from "@effect/platform/HttpRouter";
|
|
4
|
+
import * as HttpServer from "@effect/platform/HttpServer";
|
|
5
|
+
import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
|
|
6
|
+
import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Exit from "effect/Exit";
|
|
9
|
+
import * as Layer from "effect/Layer";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import * as Ref from "effect/Ref";
|
|
12
|
+
import * as Scope from "effect/Scope";
|
|
13
|
+
import { getPort } from "get-port-please";
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { EntityId, SpaceId } from "@dxos/keys";
|
|
16
|
+
import * as Ansi from "@effect/printer-ansi/Ansi";
|
|
17
|
+
import * as Doc from "@effect/printer/Doc";
|
|
18
|
+
import * as Pipeable from "effect/Pipeable";
|
|
19
|
+
import * as Options from "@effect/cli/Options";
|
|
20
|
+
import { Database, Key } from "@dxos/echo";
|
|
21
|
+
import * as AnsiDoc from "@effect/printer-ansi/AnsiDoc";
|
|
22
|
+
import { ClientService } from "@dxos/client";
|
|
23
|
+
import * as Console from "effect/Console";
|
|
24
|
+
import { AppSpace } from "@dxos/app-toolkit";
|
|
25
|
+
import { BaseError } from "@dxos/errors";
|
|
26
|
+
import { log } from "@dxos/log";
|
|
27
|
+
import { EdgeReplicationSetting } from "@dxos/protocols/proto/dxos/echo/metadata";
|
|
28
|
+
import { isBun } from "@dxos/util";
|
|
29
|
+
import * as Duration from "effect/Duration";
|
|
30
|
+
import { SpaceState } from "@dxos/client/echo";
|
|
31
|
+
import * as Config from "effect/Config";
|
|
32
|
+
//#region \0rolldown/runtime.js
|
|
33
|
+
var __defProp = Object.defineProperty;
|
|
34
|
+
var __exportAll = (all, no_symbols) => {
|
|
35
|
+
let target = {};
|
|
36
|
+
for (var name in all) __defProp(target, name, {
|
|
37
|
+
get: all[name],
|
|
38
|
+
enumerable: true
|
|
39
|
+
});
|
|
40
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
41
|
+
return target;
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/util/platform.ts
|
|
45
|
+
/**
|
|
46
|
+
* Copy text to the system clipboard.
|
|
47
|
+
* Supports macOS (pbcopy), Windows (clip), and Linux (xclip/xsel).
|
|
48
|
+
*/
|
|
49
|
+
var copyToClipboard = (text) => Effect.tryPromise({
|
|
50
|
+
try: () => {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const platform = process.platform;
|
|
53
|
+
let command;
|
|
54
|
+
let args;
|
|
55
|
+
if (platform === "darwin") {
|
|
56
|
+
command = "pbcopy";
|
|
57
|
+
args = [];
|
|
58
|
+
} else if (platform === "win32") {
|
|
59
|
+
command = "clip";
|
|
60
|
+
args = [];
|
|
61
|
+
} else {
|
|
62
|
+
command = "xclip";
|
|
63
|
+
args = ["-selection", "clipboard"];
|
|
64
|
+
}
|
|
65
|
+
const proc = spawn(command, args);
|
|
66
|
+
proc.stdin?.write(text);
|
|
67
|
+
proc.stdin?.end();
|
|
68
|
+
proc.on("close", (code) => {
|
|
69
|
+
if (code === 0) resolve();
|
|
70
|
+
else if (platform === "linux") {
|
|
71
|
+
const proc2 = spawn("xsel", ["--clipboard", "--input"]);
|
|
72
|
+
proc2.stdin?.write(text);
|
|
73
|
+
proc2.stdin?.end();
|
|
74
|
+
proc2.on("close", (code2) => {
|
|
75
|
+
if (code2 === 0) resolve();
|
|
76
|
+
else reject(/* @__PURE__ */ new Error("Failed to copy to clipboard"));
|
|
77
|
+
});
|
|
78
|
+
proc2.on("error", reject);
|
|
79
|
+
} else reject(/* @__PURE__ */ new Error("Failed to copy to clipboard"));
|
|
80
|
+
});
|
|
81
|
+
proc.on("error", reject);
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
catch: (error) => /* @__PURE__ */ new Error(`Failed to copy to clipboard: ${error}`)
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* Open a URL in the system's default browser.
|
|
88
|
+
* Supports macOS (open), Windows (start), and Linux (xdg-open).
|
|
89
|
+
*/
|
|
90
|
+
var openBrowser = (url) => Effect.tryPromise({
|
|
91
|
+
try: () => {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const platform = process.platform;
|
|
94
|
+
let command;
|
|
95
|
+
let args;
|
|
96
|
+
if (platform === "darwin") {
|
|
97
|
+
command = "open";
|
|
98
|
+
args = [url];
|
|
99
|
+
} else if (platform === "win32") {
|
|
100
|
+
command = "start";
|
|
101
|
+
args = [url];
|
|
102
|
+
} else {
|
|
103
|
+
command = "xdg-open";
|
|
104
|
+
args = [url];
|
|
105
|
+
}
|
|
106
|
+
const proc = spawn(command, args);
|
|
107
|
+
proc.on("close", (code) => {
|
|
108
|
+
if (code === 0) resolve();
|
|
109
|
+
else reject(/* @__PURE__ */ new Error("Failed to open browser"));
|
|
110
|
+
});
|
|
111
|
+
proc.on("error", reject);
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
catch: (error) => /* @__PURE__ */ new Error(`Failed to open browser: ${error}`)
|
|
115
|
+
});
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/oauth/server.ts
|
|
118
|
+
/** Default timeout for a full OAuth browser round-trip. */
|
|
119
|
+
var OAUTH_TIMEOUT_MS = 300 * 1e3;
|
|
120
|
+
/**
|
|
121
|
+
* Relay page that performs a top-level redirect to Edge's `authUrl`. Edge finalizes the flow
|
|
122
|
+
* by redirecting the browser back to this server's callback path (bsky.social nullifies
|
|
123
|
+
* `window.opener`, so a popup + postMessage relay can't be used).
|
|
124
|
+
*/
|
|
125
|
+
var getRelayPageHtml = (authUrl) => `
|
|
126
|
+
<!DOCTYPE html>
|
|
127
|
+
<html>
|
|
128
|
+
<head>
|
|
129
|
+
<meta charset="UTF-8">
|
|
130
|
+
<script>
|
|
131
|
+
window.location.href = '${authUrl.replace(/'/g, "\\'")}';
|
|
132
|
+
<\/script>
|
|
133
|
+
</head>
|
|
134
|
+
<body></body>
|
|
135
|
+
</html>
|
|
136
|
+
`;
|
|
137
|
+
/**
|
|
138
|
+
* Starts a local Bun HTTP server that relays the browser to Edge's auth URL and captures Edge's
|
|
139
|
+
* eventual top-level redirect back to `callbackPath`. Generic over the callback shape: all query
|
|
140
|
+
* params are captured and returned; an `error` query param fails the wait.
|
|
141
|
+
*
|
|
142
|
+
* Used by both the integration-connect flow (`/redirect/oauth`) and the identity-recovery login
|
|
143
|
+
* flow (`/redirect/oauth-recovery`).
|
|
144
|
+
*/
|
|
145
|
+
var startOAuthCallbackServer = (callbackPath) => Effect.gen(function* () {
|
|
146
|
+
const port = yield* Effect.promise(() => getPort({ random: true }));
|
|
147
|
+
const origin = `http://localhost:${port}`;
|
|
148
|
+
const received = yield* Ref.make(false);
|
|
149
|
+
const outcome = yield* Ref.make(Option.none());
|
|
150
|
+
const parseUrl = (rawUrl) => new URL(rawUrl.startsWith("http") ? rawUrl : `http://localhost${rawUrl}`);
|
|
151
|
+
const serverLayer = HttpRouter.empty.pipe(HttpRouter.get("/oauth-relay", Effect.gen(function* () {
|
|
152
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
153
|
+
const authUrl = parseUrl(request.url).searchParams.get("authUrl");
|
|
154
|
+
if (!authUrl) return yield* HttpServerResponse.text("Missing authUrl parameter", { status: 400 });
|
|
155
|
+
return yield* HttpServerResponse.text(getRelayPageHtml(authUrl), { headers: { "Content-Type": "text/html" } });
|
|
156
|
+
})), HttpRouter.get(callbackPath, Effect.gen(function* () {
|
|
157
|
+
if (Option.isSome(yield* Ref.get(outcome))) return yield* HttpServerResponse.text("Already received.", { status: 400 });
|
|
158
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
159
|
+
const params = parseUrl(request.url).searchParams;
|
|
160
|
+
const error = params.get("error");
|
|
161
|
+
if (error) {
|
|
162
|
+
yield* Ref.set(outcome, Option.some({
|
|
163
|
+
success: false,
|
|
164
|
+
reason: error
|
|
165
|
+
}));
|
|
166
|
+
yield* Ref.set(received, true);
|
|
167
|
+
return yield* HttpServerResponse.text(`<html><body><h1>Authentication failed</h1><p>${error}</p></body></html>`, {
|
|
168
|
+
status: 400,
|
|
169
|
+
headers: { "Content-Type": "text/html" }
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const captured = {};
|
|
173
|
+
for (const [key, value] of params.entries()) captured[key] = value;
|
|
174
|
+
yield* Ref.set(outcome, Option.some({
|
|
175
|
+
success: true,
|
|
176
|
+
params: captured
|
|
177
|
+
}));
|
|
178
|
+
yield* Ref.set(received, true);
|
|
179
|
+
return yield* HttpServerResponse.text("<html><body><h1>Authentication successful! You can close this window.</h1></body></html>", { headers: { "Content-Type": "text/html" } });
|
|
180
|
+
}))).pipe(HttpServer.serve()).pipe(Layer.provide(BunHttpServer.layer({ port })));
|
|
181
|
+
const scope = yield* Scope.make();
|
|
182
|
+
yield* Layer.build(serverLayer).pipe(Scope.extend(scope));
|
|
183
|
+
const waitForResult = (timeoutMs = OAUTH_TIMEOUT_MS) => Effect.race(Effect.gen(function* () {
|
|
184
|
+
while (true) {
|
|
185
|
+
if (yield* Ref.get(received)) break;
|
|
186
|
+
yield* Effect.sleep("500 millis");
|
|
187
|
+
}
|
|
188
|
+
const result = yield* Ref.get(outcome);
|
|
189
|
+
return yield* Option.match(result, {
|
|
190
|
+
onNone: () => Effect.fail(/* @__PURE__ */ new Error("OAuth callback received but no result")),
|
|
191
|
+
onSome: (value) => value.success ? Effect.succeed(value.params) : Effect.fail(/* @__PURE__ */ new Error(`OAuth flow failed: ${value.reason}`))
|
|
192
|
+
});
|
|
193
|
+
}), Effect.sleep(`${timeoutMs} millis`).pipe(Effect.flatMap(() => Effect.fail(/* @__PURE__ */ new Error("OAuth flow timed out")))));
|
|
194
|
+
return {
|
|
195
|
+
port,
|
|
196
|
+
origin,
|
|
197
|
+
open: (authUrl) => openBrowser(`${origin}/oauth-relay?authUrl=${encodeURIComponent(authUrl)}`),
|
|
198
|
+
waitForResult,
|
|
199
|
+
stop: () => Scope.close(scope, Exit.void).pipe(Effect.catchAll(() => Effect.void))
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/oauth/recovery.ts
|
|
204
|
+
/** Path Edge redirects the browser to after a recovery/register OAuth round-trip. */
|
|
205
|
+
var RECOVERY_CALLBACK_PATH = "/redirect/oauth-recovery";
|
|
206
|
+
/**
|
|
207
|
+
* Drives the gate's OAuth identity-recovery flow from a CLI: starts a local callback server, asks
|
|
208
|
+
* Edge to begin a `recovery`-purpose OAuth flow (advertising the local server as the redirect
|
|
209
|
+
* origin via the `Origin` header), opens the browser, and resolves with the one-time
|
|
210
|
+
* `recoveryProof` Edge carries back in its redirect. The caller redeems the proof via
|
|
211
|
+
* `client.halo.recoverIdentity({ recoveryProof })`.
|
|
212
|
+
*
|
|
213
|
+
* Recovery runs before any identity exists, so no auth header is sent — Edge resolves the user's
|
|
214
|
+
* space / token server-side from the recovery binding, and the `spaceId` / `accessTokenId` in the
|
|
215
|
+
* request are unused (random values satisfy request validation).
|
|
216
|
+
*/
|
|
217
|
+
var performRecoveryOAuthFlow = Effect.fn(function* (params) {
|
|
218
|
+
const server = yield* startOAuthCallbackServer(RECOVERY_CALLBACK_PATH);
|
|
219
|
+
return yield* Effect.gen(function* () {
|
|
220
|
+
const initiateUrl = new URL("/oauth/initiate", params.edgeBaseUrl).toString();
|
|
221
|
+
const response = yield* Effect.tryPromise({
|
|
222
|
+
try: () => fetch(initiateUrl, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: {
|
|
225
|
+
"Content-Type": "application/json",
|
|
226
|
+
"Origin": server.origin
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
provider: params.provider,
|
|
230
|
+
scopes: params.scopes,
|
|
231
|
+
spaceId: SpaceId.random(),
|
|
232
|
+
accessTokenId: EntityId.random(),
|
|
233
|
+
purpose: "recovery",
|
|
234
|
+
loginHint: params.loginHint
|
|
235
|
+
})
|
|
236
|
+
}),
|
|
237
|
+
catch: (error) => /* @__PURE__ */ new Error(`OAuth initiate request failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
238
|
+
});
|
|
239
|
+
const envelope = yield* Effect.tryPromise({
|
|
240
|
+
try: () => response.json(),
|
|
241
|
+
catch: (error) => /* @__PURE__ */ new Error(`OAuth initiate response parse failed: ${String(error)}`)
|
|
242
|
+
});
|
|
243
|
+
if (!envelope.success || !envelope.data?.authUrl) return yield* Effect.fail(/* @__PURE__ */ new Error(`OAuth initiation failed: ${envelope.error?.message ?? "unknown error"}`));
|
|
244
|
+
yield* server.open(envelope.data.authUrl);
|
|
245
|
+
const recoveryProof = (yield* server.waitForResult(OAUTH_TIMEOUT_MS)).recoveryProof;
|
|
246
|
+
if (!recoveryProof) return yield* Effect.fail(/* @__PURE__ */ new Error("OAuth recovery completed but no recoveryProof was returned."));
|
|
247
|
+
return { recoveryProof };
|
|
248
|
+
}).pipe(Effect.ensuring(server.stop()));
|
|
249
|
+
});
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/util/form-builder.ts
|
|
252
|
+
var form_builder_exports = /* @__PURE__ */ __exportAll({
|
|
253
|
+
build: () => build,
|
|
254
|
+
each: () => each,
|
|
255
|
+
make: () => make,
|
|
256
|
+
nest: () => nest,
|
|
257
|
+
nestedOption: () => nestedOption,
|
|
258
|
+
option: () => option,
|
|
259
|
+
set: () => set,
|
|
260
|
+
when: () => when
|
|
261
|
+
});
|
|
262
|
+
var FormBuilderImpl = class {
|
|
263
|
+
entries = [];
|
|
264
|
+
title;
|
|
265
|
+
prefix;
|
|
266
|
+
constructor(options = {}) {
|
|
267
|
+
this.title = options.title;
|
|
268
|
+
this.prefix = options.prefix ?? "- ";
|
|
269
|
+
}
|
|
270
|
+
pipe() {
|
|
271
|
+
return Pipeable.pipeArguments(this, arguments);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Creates a new FormBuilder instance.
|
|
276
|
+
*/
|
|
277
|
+
var make = (props) => new FormBuilderImpl(props);
|
|
278
|
+
var calculateDimensions = (entries, prefix) => {
|
|
279
|
+
const maxKeyLen = Math.max(0, ...entries.map((entry) => entry.key.length));
|
|
280
|
+
return {
|
|
281
|
+
maxKeyLen,
|
|
282
|
+
targetWidth: prefix.length + maxKeyLen + 2
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
var buildKeyLine = (prefix, key, targetWidth) => {
|
|
286
|
+
return Doc.annotate(Doc.fill(targetWidth)(Doc.text(prefix + key + ": ")), Ansi.blackBright);
|
|
287
|
+
};
|
|
288
|
+
var setImpl = (builder, key, value, color) => {
|
|
289
|
+
if (value !== void 0) {
|
|
290
|
+
let valueDoc;
|
|
291
|
+
if (typeof value === "object" && value !== null) valueDoc = value;
|
|
292
|
+
else valueDoc = Doc.text(String(value));
|
|
293
|
+
if (color) {
|
|
294
|
+
const ansi = typeof color === "function" ? color(value) : color;
|
|
295
|
+
valueDoc = Doc.annotate(valueDoc, ansi);
|
|
296
|
+
}
|
|
297
|
+
builder.entries.push({
|
|
298
|
+
key,
|
|
299
|
+
value: valueDoc,
|
|
300
|
+
isNested: false
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return builder;
|
|
304
|
+
};
|
|
305
|
+
function set(builderOrKey, keyOrValue, valueOrColor, color) {
|
|
306
|
+
if (builderOrKey instanceof FormBuilderImpl) return setImpl(builderOrKey, keyOrValue, valueOrColor, color);
|
|
307
|
+
else {
|
|
308
|
+
const key = builderOrKey;
|
|
309
|
+
const value = keyOrValue;
|
|
310
|
+
const color = valueOrColor;
|
|
311
|
+
return (builder) => setImpl(builder, key, value, color);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
var nestImpl = (parent, key, builder) => {
|
|
315
|
+
let valueDoc = build({
|
|
316
|
+
...builder,
|
|
317
|
+
title: void 0,
|
|
318
|
+
entries: builder.entries
|
|
319
|
+
});
|
|
320
|
+
valueDoc = Doc.indent(valueDoc, 2);
|
|
321
|
+
parent.entries.push({
|
|
322
|
+
key,
|
|
323
|
+
value: valueDoc,
|
|
324
|
+
isNested: true
|
|
325
|
+
});
|
|
326
|
+
return parent;
|
|
327
|
+
};
|
|
328
|
+
function nest(parentOrKey, keyOrBuilder, builder) {
|
|
329
|
+
if (parentOrKey instanceof FormBuilderImpl) return nestImpl(parentOrKey, keyOrBuilder, builder);
|
|
330
|
+
else {
|
|
331
|
+
const key = parentOrKey;
|
|
332
|
+
const builder = keyOrBuilder;
|
|
333
|
+
return (parent) => nestImpl(parent, key, builder);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
var optionImpl = (builder, key, value, color) => {
|
|
337
|
+
if (Option.isSome(value)) return setImpl(builder, key, value.value, color);
|
|
338
|
+
return builder;
|
|
339
|
+
};
|
|
340
|
+
function option(builderOrKey, keyOrValue, valueOrColor, color) {
|
|
341
|
+
if (builderOrKey instanceof FormBuilderImpl) return optionImpl(builderOrKey, keyOrValue, valueOrColor, color);
|
|
342
|
+
else {
|
|
343
|
+
const key = builderOrKey;
|
|
344
|
+
const value = keyOrValue;
|
|
345
|
+
const color = valueOrColor;
|
|
346
|
+
return (builder) => optionImpl(builder, key, value, color);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
var nestedOptionImpl = (builder, key, value) => {
|
|
350
|
+
if (Option.isSome(value)) return nestImpl(builder, key, value.value);
|
|
351
|
+
return builder;
|
|
352
|
+
};
|
|
353
|
+
function nestedOption(builderOrKey, keyOrValue, value) {
|
|
354
|
+
if (builderOrKey instanceof FormBuilderImpl) return nestedOptionImpl(builderOrKey, keyOrValue, value);
|
|
355
|
+
else {
|
|
356
|
+
const key = builderOrKey;
|
|
357
|
+
const value = keyOrValue;
|
|
358
|
+
return (builder) => nestedOptionImpl(builder, key, value);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
var whenImpl = (builder, condition, ...ops) => {
|
|
362
|
+
if (condition) for (const op of ops) op(builder);
|
|
363
|
+
return builder;
|
|
364
|
+
};
|
|
365
|
+
function when(builderOrCondition, conditionOrOp, ...ops) {
|
|
366
|
+
if (builderOrCondition instanceof FormBuilderImpl) return whenImpl(builderOrCondition, conditionOrOp, ...ops);
|
|
367
|
+
else {
|
|
368
|
+
const condition = builderOrCondition;
|
|
369
|
+
const allOps = [conditionOrOp, ...ops].filter((op) => typeof op === "function");
|
|
370
|
+
return (builder) => whenImpl(builder, condition, ...allOps);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
var eachImpl = (builder, items, fn) => {
|
|
374
|
+
items.forEach((item) => fn(item)(builder));
|
|
375
|
+
return builder;
|
|
376
|
+
};
|
|
377
|
+
function each(builderOrItems, itemsOrFn, fn) {
|
|
378
|
+
if (builderOrItems instanceof FormBuilderImpl) return eachImpl(builderOrItems, itemsOrFn, fn);
|
|
379
|
+
else {
|
|
380
|
+
const items = builderOrItems;
|
|
381
|
+
const fn = itemsOrFn;
|
|
382
|
+
return (builder) => eachImpl(builder, items, fn);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Builds the final document.
|
|
387
|
+
*/
|
|
388
|
+
var build = (builder) => {
|
|
389
|
+
const { targetWidth } = calculateDimensions(builder.entries, builder.prefix);
|
|
390
|
+
const entryLines = [];
|
|
391
|
+
builder.entries.forEach(({ key, value, isNested }) => {
|
|
392
|
+
const keyLine = buildKeyLine(builder.prefix, key, targetWidth);
|
|
393
|
+
if (isNested) entryLines.push(Doc.hcat([
|
|
394
|
+
keyLine,
|
|
395
|
+
Doc.hardLine,
|
|
396
|
+
value
|
|
397
|
+
]));
|
|
398
|
+
else entryLines.push(Doc.hcat([keyLine, value]));
|
|
399
|
+
});
|
|
400
|
+
const entriesDoc = Doc.vsep(entryLines);
|
|
401
|
+
if (builder.title) {
|
|
402
|
+
const titleDoc = Doc.hcat([Doc.annotate(Doc.text(builder.title), Ansi.combine(Ansi.bold, Ansi.cyan))]);
|
|
403
|
+
return Doc.cat(titleDoc, Doc.cat(Doc.line, entriesDoc));
|
|
404
|
+
}
|
|
405
|
+
return entriesDoc;
|
|
406
|
+
};
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region src/util/format.ts
|
|
409
|
+
var BYTE_UNITS = [
|
|
410
|
+
"B",
|
|
411
|
+
"KB",
|
|
412
|
+
"MB",
|
|
413
|
+
"GB",
|
|
414
|
+
"TB"
|
|
415
|
+
];
|
|
416
|
+
/**
|
|
417
|
+
* Format a byte count using binary units.
|
|
418
|
+
*/
|
|
419
|
+
var formatBytes = (bytes) => {
|
|
420
|
+
let value = bytes;
|
|
421
|
+
let unit = 0;
|
|
422
|
+
while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
|
|
423
|
+
value /= 1024;
|
|
424
|
+
unit++;
|
|
425
|
+
}
|
|
426
|
+
return `${unit === 0 ? value : value.toFixed(1)} ${BYTE_UNITS[unit]}`;
|
|
427
|
+
};
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region src/util/options.ts
|
|
430
|
+
var Common = {
|
|
431
|
+
functionId: Options.text("function-id").pipe(Options.withDescription("EDGE Function ID.")),
|
|
432
|
+
spaceId: Options.text("space-id").pipe(Options.withSchema(Key.SpaceId), Options.withDescription("Space ID."))
|
|
433
|
+
};
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/util/printer.ts
|
|
436
|
+
/**
|
|
437
|
+
* Pretty print document.
|
|
438
|
+
*/
|
|
439
|
+
var print = (doc) => AnsiDoc.render(doc, { style: "pretty" });
|
|
440
|
+
/**
|
|
441
|
+
* Pretty prints a list of documents with ANSI colors.
|
|
442
|
+
*/
|
|
443
|
+
var printList = (items) => AnsiDoc.render(Doc.vsep(items.map((item) => Doc.cat(item, Doc.hardLine))), { style: "pretty" });
|
|
444
|
+
//#endregion
|
|
445
|
+
//#region src/util/runtime.ts
|
|
446
|
+
/** @deprecated Migrate to providing types via plugin capabilities. */
|
|
447
|
+
var withTypes = (...types) => Effect.gen(function* () {
|
|
448
|
+
const client = yield* ClientService;
|
|
449
|
+
yield* Effect.promise(() => client.addTypes(types));
|
|
450
|
+
});
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/util/space.ts
|
|
453
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/devtools/cli-util/src/util/space.ts";
|
|
454
|
+
var getSpace = (spaceId) => Effect.gen(function* () {
|
|
455
|
+
const client = yield* ClientService;
|
|
456
|
+
return yield* Option.fromNullable(client.spaces.get(spaceId));
|
|
457
|
+
}).pipe(Effect.catchTag("NoSuchElementException", () => Effect.fail(new SpaceNotFoundError(spaceId))));
|
|
458
|
+
var spaceIdWithDefault = (spaceId) => Effect.gen(function* () {
|
|
459
|
+
const client = yield* ClientService;
|
|
460
|
+
return Option.getOrElse(spaceId, () => {
|
|
461
|
+
const personal = AppSpace.getPersonalSpace(client);
|
|
462
|
+
if (!personal) throw new Error("No space ID provided and no personal space found.");
|
|
463
|
+
return personal.id;
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
var spaceLayer = (spaceId$, fallbackToPersonalSpace = false) => {
|
|
467
|
+
const getSpace = Effect.fn(function* () {
|
|
468
|
+
const client = yield* ClientService;
|
|
469
|
+
const resolveSpace = () => {
|
|
470
|
+
if (!fallbackToPersonalSpace) return spaceId$.pipe(Option.flatMap((id) => Option.fromNullable(client.spaces.get(id))));
|
|
471
|
+
return spaceId$.pipe(Option.flatMap((id) => Option.fromNullable(client.spaces.get(id))), Option.orElse(() => Option.fromNullable(AppSpace.getPersonalSpace(client))), Option.orElse(() => Option.fromNullable(client.spaces.get()[0])));
|
|
472
|
+
};
|
|
473
|
+
const space = resolveSpace().pipe(Option.getOrUndefined);
|
|
474
|
+
if (space) yield* Effect.promise(() => space.waitUntilReady());
|
|
475
|
+
return space;
|
|
476
|
+
});
|
|
477
|
+
const NO_DB_STUB = { get db() {
|
|
478
|
+
throw new Error("Space not found");
|
|
479
|
+
} };
|
|
480
|
+
return Layer.scoped(Database.Service, Effect.acquireRelease(Effect.gen(function* () {
|
|
481
|
+
const space = yield* getSpace();
|
|
482
|
+
if (!space) return NO_DB_STUB;
|
|
483
|
+
return { db: space.db };
|
|
484
|
+
}), (holder) => holder === NO_DB_STUB ? Effect.void : Effect.promise(() => holder.db.flush())));
|
|
485
|
+
};
|
|
486
|
+
var waitForSync = Effect.fn(function* (space) {
|
|
487
|
+
if (!isBun()) return;
|
|
488
|
+
if (space.internal.data.edgeReplication !== EdgeReplicationSetting.ENABLED) {
|
|
489
|
+
yield* Console.log("Edge replication is disabled, enabling...");
|
|
490
|
+
yield* Effect.promise(() => space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED));
|
|
491
|
+
}
|
|
492
|
+
yield* Effect.promise(() => space.internal.syncToEdge({ onProgress: (state) => log.info("syncing", { state: state ?? "no connection to edge" }, {
|
|
493
|
+
"~LogMeta": "~LogMeta",
|
|
494
|
+
F: __dxlog_file,
|
|
495
|
+
L: 114,
|
|
496
|
+
S: this
|
|
497
|
+
}) }));
|
|
498
|
+
yield* Console.log("Sync complete");
|
|
499
|
+
});
|
|
500
|
+
var flushAndSync = Effect.fn(function* (opts) {
|
|
501
|
+
yield* Database.flush(opts);
|
|
502
|
+
yield* waitForSync(yield* getSpace(yield* Database.spaceId));
|
|
503
|
+
});
|
|
504
|
+
var SpaceNotFoundError = class extends BaseError.extend("SpaceNotFoundError", "Space not found") {
|
|
505
|
+
constructor(spaceId, options) {
|
|
506
|
+
super({
|
|
507
|
+
context: { spaceId },
|
|
508
|
+
...options
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/util/space-format.ts
|
|
514
|
+
var DEFAULT_OPTIONS = {
|
|
515
|
+
verbose: false,
|
|
516
|
+
truncateKeys: false,
|
|
517
|
+
waitSeconds: 0
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* Per-async-read internal timeout. Some `space.internal.*` getters do
|
|
521
|
+
* filesystem / network IO and can themselves hang on a partially-loaded
|
|
522
|
+
* space; cap each one so the command can never be held hostage by SDK
|
|
523
|
+
* internals.
|
|
524
|
+
*/
|
|
525
|
+
var READ_TIMEOUT_SECONDS = 2;
|
|
526
|
+
var tryWithFallback = (label, run, fallback) => Effect.tryPromise(run).pipe(Effect.timeoutFail({
|
|
527
|
+
duration: Duration.seconds(READ_TIMEOUT_SECONDS),
|
|
528
|
+
onTimeout: () => /* @__PURE__ */ new Error(`${label} timed out`)
|
|
529
|
+
}), Effect.catchAll(() => Effect.succeed(fallback)));
|
|
530
|
+
var tryWithFallbackSync = (read, fallback) => {
|
|
531
|
+
try {
|
|
532
|
+
return read();
|
|
533
|
+
} catch {
|
|
534
|
+
return fallback;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
var formatSpace = Effect.fn(function* (space, options = {}) {
|
|
538
|
+
const { waitSeconds } = {
|
|
539
|
+
...DEFAULT_OPTIONS,
|
|
540
|
+
...options
|
|
541
|
+
};
|
|
542
|
+
if (waitSeconds > 0) yield* Effect.tryPromise(() => space.waitUntilReady()).pipe(Effect.timeoutFail({
|
|
543
|
+
duration: Duration.seconds(waitSeconds),
|
|
544
|
+
onTimeout: () => /* @__PURE__ */ new Error("waitUntilReady timed out")
|
|
545
|
+
}), Effect.catchAll(() => Effect.void));
|
|
546
|
+
const state = tryWithFallbackSync(() => space.state.get(), SpaceState.SPACE_INITIALIZING);
|
|
547
|
+
const ready = state === SpaceState.SPACE_READY;
|
|
548
|
+
const metrics = tryWithFallbackSync(() => space.internal.data.metrics, void 0);
|
|
549
|
+
const startup = metrics?.open && metrics?.ready ? metrics.ready.getTime() - metrics.open.getTime() : void 0;
|
|
550
|
+
const pipeline = tryWithFallbackSync(() => space.internal.data.pipeline, void 0);
|
|
551
|
+
const epoch = pipeline?.currentEpoch?.subject.assertion.number;
|
|
552
|
+
const syncState = aggregateSyncState(yield* tryWithFallback("getAutomergeSyncState", () => space.internal.db.getAutomergeSyncState(), { peers: {} }));
|
|
553
|
+
const name = ready ? tryWithFallbackSync(() => space.properties.name, void 0) : "loading...";
|
|
554
|
+
const members = tryWithFallbackSync(() => space.members.get().length, 0);
|
|
555
|
+
const objects = tryWithFallbackSync(() => space.internal.db.getAllObjectIds().length, 0);
|
|
556
|
+
const key = options.truncateKeys ? tryWithFallbackSync(() => space.key.truncate(), "") : tryWithFallbackSync(() => space.key.toHex(), "");
|
|
557
|
+
return {
|
|
558
|
+
id: space.id,
|
|
559
|
+
state: SpaceState[state],
|
|
560
|
+
name,
|
|
561
|
+
members,
|
|
562
|
+
objects,
|
|
563
|
+
key,
|
|
564
|
+
epoch,
|
|
565
|
+
startup,
|
|
566
|
+
automergeRoot: pipeline?.spaceRootUrl,
|
|
567
|
+
syncState: `${syncState.count} ${getSyncIndicator(syncState.up, syncState.down)} (${syncState.peers} peers)`
|
|
568
|
+
};
|
|
569
|
+
});
|
|
570
|
+
var aggregateSyncState = (syncState) => {
|
|
571
|
+
const peers = Object.keys(syncState.peers ?? {}).length;
|
|
572
|
+
const missingOnLocal = Math.max(...Object.values(syncState.peers ?? {}).map((peer) => peer.missingOnLocal), 0);
|
|
573
|
+
const missingOnRemote = Math.max(...Object.values(syncState.peers ?? {}).map((peer) => peer.missingOnRemote), 0);
|
|
574
|
+
const differentDocuments = Math.max(...Object.values(syncState.peers ?? {}).map((peer) => peer.differentDocuments), 0);
|
|
575
|
+
return {
|
|
576
|
+
count: missingOnLocal + missingOnRemote + differentDocuments,
|
|
577
|
+
peers,
|
|
578
|
+
up: missingOnRemote > 0 || differentDocuments > 0,
|
|
579
|
+
down: missingOnLocal > 0 || differentDocuments > 0
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
var getSyncIndicator = (up, down) => {
|
|
583
|
+
if (up) if (down) return "⇅";
|
|
584
|
+
else return "↑";
|
|
585
|
+
else if (down) return "↓";
|
|
586
|
+
else return "✓";
|
|
587
|
+
};
|
|
588
|
+
/**
|
|
589
|
+
* Pretty prints a space with ANSI colors.
|
|
590
|
+
*/
|
|
591
|
+
var printSpace = (spaceData) => make({ title: spaceData.name ?? spaceData.id }).pipe(set("id", spaceData.id), set("state", spaceData.state), set("key", spaceData.key), set("members", spaceData.members), set("objects", spaceData.objects), set("epoch", spaceData.epoch ?? "<none>"), set("startup", spaceData.startup ? `${spaceData.startup}ms` : "<none>"), set("syncState", spaceData.syncState), set("automergeRoot", spaceData.automergeRoot ?? "<none>"), build);
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/util/timeout.ts
|
|
594
|
+
var withTimeout = Effect.fnUntraced(function* (effect) {
|
|
595
|
+
const duration = (yield* Config.integer("TIMEOUT").pipe(Config.option)).pipe(Option.map(Duration.millis), Option.getOrElse(() => Duration.infinity));
|
|
596
|
+
return yield* effect.pipe(Effect.timeout(duration));
|
|
597
|
+
});
|
|
598
|
+
//#endregion
|
|
599
|
+
export { CommandConfig, Common, form_builder_exports as FormBuilder, OAUTH_TIMEOUT_MS, SpaceNotFoundError, copyToClipboard, flushAndSync, formatBytes, formatSpace, getSpace, openBrowser, performRecoveryOAuthFlow, print, printList, printSpace, spaceIdWithDefault, spaceLayer, startOAuthCallbackServer, waitForSync, withTimeout, withTypes };
|
|
600
|
+
|
|
601
|
+
//# sourceMappingURL=index.mjs.map
|