@akterjs/cli 0.0.0-stage → 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/LICENSE +201 -0
- package/README.md +47 -2
- package/dist/Argument-D81dqizB.mjs +57 -0
- package/dist/Array-B4kuTQeW.mjs +2323 -0
- package/dist/Boolean-kILIvmhE.mjs +26 -0
- package/dist/ChildProcess-BCIwxPGc.mjs +577 -0
- package/dist/CliError-Cf5xqkkj.mjs +633 -0
- package/dist/Command-Dzq9KM-h.mjs +5135 -0
- package/dist/Config-B6OND9WR.mjs +854 -0
- package/dist/Console-BKHUkH6a.mjs +141 -0
- package/dist/Data-CeCULr0-.mjs +251 -0
- package/dist/Digest-CBd3uv5B.mjs +348 -0
- package/dist/Duration-EH9Vy4U6.mjs +791 -0
- package/dist/Effect-CKFpqbmw.mjs +12911 -0
- package/dist/FileSystem-4pPcEb_i.mjs +1366 -0
- package/dist/HttpClientError-BUeMWx70.mjs +303 -0
- package/dist/HttpClientResponse-D5ZKICVV.mjs +855 -0
- package/dist/Match-CGPLZYrV.mjs +434 -0
- package/dist/MutableHashMap-f8nW8AJR.mjs +376 -0
- package/dist/MutableHashSet-_HI572Ya.mjs +155 -0
- package/dist/MutableRef-D4Qv592C.mjs +107 -0
- package/dist/Number-BvkRkfc-.mjs +137 -0
- package/dist/Option-DkxSnBhp.mjs +3967 -0
- package/dist/Order-C1JC991I.mjs +481 -0
- package/dist/Path-Djdv_eF5.mjs +130 -0
- package/dist/Queue-CO5UQ6os.mjs +1394 -0
- package/dist/Redacted-RkJrG5Me.mjs +160 -0
- package/dist/Ref-BIPOxJNH.mjs +267 -0
- package/dist/Runtime-BInIAwK3.mjs +297 -0
- package/dist/Schema-MiKt5K1v.mjs +10999 -0
- package/dist/SecureRandom-B_U4ysO3.mjs +279 -0
- package/dist/Semaphore-DOVJ4ZGS.mjs +177 -0
- package/dist/Stdio-KylmOBX_.mjs +74 -0
- package/dist/Stream-bK9QIETN.mjs +4012 -0
- package/dist/String-q5dcsVe4.mjs +351 -0
- package/dist/TerminalCapabilities-w_44MAca.mjs +1769 -0
- package/dist/WebCrypto-ZI_GNVlw.mjs +33 -0
- package/dist/actor-call.service-CGM_vD9u.mjs +1269 -0
- package/dist/actor-client.adapter-Coem7gF1.mjs +3691 -0
- package/dist/akter-program.config-D8yb9xQP.mjs +7034 -0
- package/dist/akter.cli.mjs +5 -0
- package/dist/app-cli.config.mjs +49 -0
- package/dist/app-entry.pure.mjs +31 -0
- package/dist/archive/akter-archive.schema.mjs +65 -0
- package/dist/archive/akter-archive.service.mjs +309 -0
- package/dist/archive/archive-cli.config.mjs +45 -0
- package/dist/archive/archive-invalid.errors.mjs +5 -0
- package/dist/archive/archived-lineage.pure.mjs +13 -0
- package/dist/archive/tar.pure.mjs +291 -0
- package/dist/archive/zstd.adapter.mjs +46 -0
- package/dist/bench/bench-report.pure.mjs +120 -0
- package/dist/bench/bench-runner.pure.mjs +89 -0
- package/dist/bench/bench.service.mjs +301 -0
- package/dist/cli-runtime.layer-BqK2mmVL.mjs +1191 -0
- package/dist/cli-runtime.layer.mjs +1 -0
- package/dist/cloud-client.adapter-Cy1BjDbG.mjs +3186 -0
- package/dist/cloud-client.adapter-DOVMoYAj.mjs +304 -0
- package/dist/command/akter-commands.config.mjs +32 -0
- package/dist/command/akter-program.config.mjs +1 -0
- package/dist/credentials/credentials.service.mjs +226 -0
- package/dist/credentials/keychain.adapter.mjs +92 -0
- package/dist/credentials/login.config.mjs +251 -0
- package/dist/deploy/auth/api-attempt.service.mjs +30 -0
- package/dist/deploy/auth/token-refresh.errors.mjs +4 -0
- package/dist/deploy/build-bundle.adapter.mjs +133 -0
- package/dist/deploy/build-manifest.pure.mjs +44 -0
- package/dist/deploy/cloud-client.adapter.mjs +1 -0
- package/dist/deploy/cloud-url.pure.mjs +17 -0
- package/dist/deploy/deploy-failed.errors.mjs +17 -0
- package/dist/deploy/deploy.service.mjs +156 -0
- package/dist/deploy/request-failure.pure.mjs +87 -0
- package/dist/dev-runner.pure.mjs +30 -0
- package/dist/docs/doc-topics.pure.mjs +21 -0
- package/dist/docs/docs.config.mjs +1 -0
- package/dist/docs.config-D2amjsje.mjs +190 -0
- package/dist/docs.config-D3zzLzLR.mjs +190 -0
- package/dist/export/actor-export.service.mjs +267 -0
- package/dist/export/export-failed.errors.mjs +4 -0
- package/dist/export/export-failure.pure.mjs +18 -0
- package/dist/export/ordered-lines.pure.mjs +43 -0
- package/dist/export/ranged-download.pure.mjs +66 -0
- package/dist/export/snapshot-download.pure.mjs +86 -0
- package/dist/history-runner.pure.mjs +186 -0
- package/dist/history-since.pure.mjs +17 -0
- package/dist/hosted-runtime.layer-Cx3IPsEd.mjs +16358 -0
- package/dist/local/environment-cli.config.mjs +90 -0
- package/dist/local/environment-runner.pure.mjs +75 -0
- package/dist/local/local-cli.config.mjs +159 -0
- package/dist/logs/log-follow.config.mjs +21 -0
- package/dist/logs/log-follow.service.mjs +87 -0
- package/dist/logs/log-lines.pure.mjs +44 -0
- package/dist/migrate-runner.pure.mjs +28 -0
- package/dist/operation-input.pure-CS9iUhqK.mjs +1383 -0
- package/dist/operation-registry.pure-DVC8EDgj.mjs +283 -0
- package/dist/registry/actor-call.service.mjs +1 -0
- package/dist/registry/actor-commands.config.mjs +127 -0
- package/dist/registry/actor-refusal.pure.mjs +34 -0
- package/dist/registry/approval.service.mjs +80 -0
- package/dist/registry/cloud-commands.config.mjs +151 -0
- package/dist/registry/cloud-served.pure.mjs +31 -0
- package/dist/registry/operation-call.service.mjs +262 -0
- package/dist/registry/operation-input.pure.mjs +1 -0
- package/dist/registry/operation-output.pure.mjs +91 -0
- package/dist/registry/served-command.config.mjs +57 -0
- package/dist/runner-exited.errors.mjs +10 -0
- package/dist/terminal/cli-exit.errors.mjs +38 -0
- package/dist/terminal/cli-failure.pure.mjs +31 -0
- package/dist/terminal/confirm.adapter.mjs +27 -0
- package/package.json +61 -3
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { P as currentTimeMillis, S as prependUrl, f as bearerToken, r as fromWeb } from "./HttpClientResponse-D5ZKICVV.mjs";
|
|
2
|
+
import { A as ensuring, At as provideContext, B as flatMap, Dt as orElseSucceed, L as filterOrElse, Lt as retry, M as fail, T as context, Tt as option, U as fn, Vn as effect, W as fnUntraced, Xt as succeed, _a as pick, aa as Service, cn as tryPromise, d as asVoid, dt as map, en as sync, et as gen, ft as mapBoth, ht as mapError, ia as Reference, in as timeout, jt as provideService, o as andThen, p as callback, rt as interrupt, ua as getOrUndefined, xt as onError } from "./Effect-CKFpqbmw.mjs";
|
|
3
|
+
import { B as TaggedStruct, Qn as remove, Zn as merge, ir as toEpochMillis, mt as is, qn as fromInput, tt as decodeEffect, u as DateTimeUtcFromString } from "./Schema-MiKt5K1v.mjs";
|
|
4
|
+
import { S as none, _ as isSome, b as map$1, r as exists, w as some, x as match } from "./Option-DkxSnBhp.mjs";
|
|
5
|
+
import { i as value, n as make } from "./Redacted-RkJrG5Me.mjs";
|
|
6
|
+
import { o as set, r as make$1, s as update, t as get } from "./Ref-BIPOxJNH.mjs";
|
|
7
|
+
import { t as make$2 } from "./Semaphore-DOVJ4ZGS.mjs";
|
|
8
|
+
import { $ as toReadableStreamEffect, C as interruptWhen, d as ensuring$1, j as mkArrayBuffer } from "./Stream-bK9QIETN.mjs";
|
|
9
|
+
import { _ as mapRequestEffect, d as make$4, f as HttpClient, g as mapRequest, h as make$3, m as layerMergedContext, p as filterStatusOk, v as transformResponse } from "./cloud-client.adapter-Cy1BjDbG.mjs";
|
|
10
|
+
import { c as TransportError, i as HttpClientError, r as EncodeError, s as StatusCodeError } from "./HttpClientError-BUeMWx70.mjs";
|
|
11
|
+
import { DeployFailedError } from "./deploy/deploy-failed.errors.mjs";
|
|
12
|
+
import { ApiAttempt } from "./deploy/auth/api-attempt.service.mjs";
|
|
13
|
+
import { exchangeFailure, progressWait, retriedSignIn, signInRetries } from "./deploy/request-failure.pure.mjs";
|
|
14
|
+
import { signInFailure } from "./registry/operation-output.pure.mjs";
|
|
15
|
+
// ../../node_modules/.pnpm/effect@4.1.2_patch_hash=0e61176203bb1256381ce1c5b488d7951983ce38ff698647d3f4617383abbf07/node_modules/effect/dist/http/FetchHttpClient.js
|
|
16
|
+
/**
|
|
17
|
+
* Fetch-based implementation of the Effect HTTP client service.
|
|
18
|
+
*
|
|
19
|
+
* This module provides an `HttpClient` layer that executes requests through a
|
|
20
|
+
* Web Fetch API implementation. It is the transport to use in browsers, edge
|
|
21
|
+
* runtimes, and Node.js environments where `globalThis.fetch` is available, or
|
|
22
|
+
* anywhere a compatible fetch function can be supplied.
|
|
23
|
+
*
|
|
24
|
+
* Requests with an `Upgrade` header fail with `TransportError` before anything
|
|
25
|
+
* is sent because fetch cannot hand over the connection.
|
|
26
|
+
*
|
|
27
|
+
* @stability unstable
|
|
28
|
+
* @since 4.0.0
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Context reference for the `fetch` implementation used by the fetch-based HTTP client.
|
|
32
|
+
*
|
|
33
|
+
* **Details**
|
|
34
|
+
*
|
|
35
|
+
* Defaults to `globalThis.fetch`.
|
|
36
|
+
*
|
|
37
|
+
* @stability unstable
|
|
38
|
+
* @category services
|
|
39
|
+
* @since 4.0.0
|
|
40
|
+
*/
|
|
41
|
+
const Fetch = /*#__PURE__*/ Reference("effect/http/FetchHttpClient/Fetch", { defaultValue: () => globalThis.fetch });
|
|
42
|
+
/**
|
|
43
|
+
* Context reference controlling stream bodies when the runtime does not support streaming requests.
|
|
44
|
+
*
|
|
45
|
+
* **Details**
|
|
46
|
+
*
|
|
47
|
+
* Defaults to `"error"`, which fails with an `EncodeError` without sending the
|
|
48
|
+
* request. Set to `"buffer"` to collect the entire stream in memory and send it
|
|
49
|
+
* as a single body instead. Runtimes with streaming support always send the stream.
|
|
50
|
+
*
|
|
51
|
+
* **Gotchas**
|
|
52
|
+
*
|
|
53
|
+
* Buffering keeps the entire upload in memory and can exhaust memory for large
|
|
54
|
+
* or unbounded uploads. Support is detected once, on the first stream body sent,
|
|
55
|
+
* using the global `Request` implementation even when a custom `Fetch` is provided.
|
|
56
|
+
*
|
|
57
|
+
* @stability unstable
|
|
58
|
+
* @category services
|
|
59
|
+
* @since 4.0.0
|
|
60
|
+
*/
|
|
61
|
+
const StreamBodyFallback = /*#__PURE__*/ Reference("effect/http/FetchHttpClient/StreamBodyFallback", { defaultValue: () => "error" });
|
|
62
|
+
/**
|
|
63
|
+
* Service that contains default fetch options for the fetch-based HTTP client.
|
|
64
|
+
*
|
|
65
|
+
* **When to use**
|
|
66
|
+
*
|
|
67
|
+
* Use to provide default credentials, cache, redirect, integrity, or other
|
|
68
|
+
* fetch options for outgoing HTTP requests.
|
|
69
|
+
*
|
|
70
|
+
* **Details**
|
|
71
|
+
*
|
|
72
|
+
* Request-specific method, headers, body, and abort signal are supplied by the client when a request is executed.
|
|
73
|
+
*
|
|
74
|
+
* @see {@link HttpClient.followRedirects} for following redirects in the client, which needs `{ redirect: "manual" }` here
|
|
75
|
+
*
|
|
76
|
+
* @stability unstable
|
|
77
|
+
* @category services
|
|
78
|
+
* @since 4.0.0
|
|
79
|
+
*/
|
|
80
|
+
var RequestInit = class extends (/*#__PURE__*/ Service()("effect/http/FetchHttpClient/RequestInit")) {};
|
|
81
|
+
let supportsStreamBody;
|
|
82
|
+
const detectStreamBodySupport = () => {
|
|
83
|
+
try {
|
|
84
|
+
let duplexAccessed = false;
|
|
85
|
+
const body = new ReadableStream();
|
|
86
|
+
const request = new Request("https://example.com", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
body,
|
|
89
|
+
get duplex() {
|
|
90
|
+
duplexAccessed = true;
|
|
91
|
+
return "half";
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return (duplexAccessed || request.body === body) && !request.headers.has("Content-Type");
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Layer that provides an `HttpClient` implementation backed by the configured
|
|
101
|
+
* `Fetch` function.
|
|
102
|
+
*
|
|
103
|
+
* **When to use**
|
|
104
|
+
*
|
|
105
|
+
* Use when an Effect program should execute `HttpClient` requests through the
|
|
106
|
+
* platform `fetch` implementation, especially in browser, edge, or Node.js
|
|
107
|
+
* runtimes with `globalThis.fetch`.
|
|
108
|
+
*
|
|
109
|
+
* **Details**
|
|
110
|
+
*
|
|
111
|
+
* The layer uses the current `Fetch` reference and optional `RequestInit`
|
|
112
|
+
* service for each request. Request-specific method, headers, body, and abort
|
|
113
|
+
* signal are supplied by the client and override matching `RequestInit` fields.
|
|
114
|
+
*
|
|
115
|
+
* **Gotchas**
|
|
116
|
+
*
|
|
117
|
+
* Fetch behavior comes from the runtime's implementation, so CORS, cookies,
|
|
118
|
+
* redirects, abort handling, and streaming support can vary by platform.
|
|
119
|
+
* `fetch` follows redirects itself by default, so `HttpClient.followRedirects`
|
|
120
|
+
* has no effect unless `RequestInit` sets `redirect: "manual"`. Trace
|
|
121
|
+
* propagation is on by default, and a browser preflight fails unless the
|
|
122
|
+
* target allows `b3` and `traceparent`. Stream request bodies are sent as Web
|
|
123
|
+
* streams with `duplex: "half"` when the runtime supports it; see
|
|
124
|
+
* `StreamBodyFallback` for the unsupported case. Any `content-length` header is
|
|
125
|
+
* removed before calling `fetch`. Chrome passes the support check but rejects
|
|
126
|
+
* streaming uploads over HTTP/1.1, which surfaces as a `TransportError`, and
|
|
127
|
+
* browsers only deliver the response after the entire request body has been sent.
|
|
128
|
+
*
|
|
129
|
+
* @see {@link Fetch} for supplying the fetch implementation used by this layer
|
|
130
|
+
* @see {@link RequestInit} for default `RequestInit` options applied before request-specific fields
|
|
131
|
+
* @see {@link StreamBodyFallback} for opting into buffering on unsupported runtimes
|
|
132
|
+
* @see {@link HttpClient.TracerPropagationEnabled} for leaving `b3` and `traceparent` off a browser request
|
|
133
|
+
*
|
|
134
|
+
* @stability unstable
|
|
135
|
+
* @category layers
|
|
136
|
+
* @since 4.0.0
|
|
137
|
+
*/
|
|
138
|
+
const layer = /*#__PURE__*/ layerMergedContext(/*#__PURE__*/ succeed(/* @__PURE__ */ make$3((request, url, signal, fiber) => {
|
|
139
|
+
if (request.headers.upgrade !== void 0) return fail(new HttpClientError({ reason: new TransportError({
|
|
140
|
+
request,
|
|
141
|
+
description: "fetch cannot send an HTTP upgrade request"
|
|
142
|
+
}) }));
|
|
143
|
+
const fetch = fiber.getRef(Fetch);
|
|
144
|
+
const options = getOrUndefined(fiber.context, RequestInit) ?? {};
|
|
145
|
+
let headers = options.headers ? merge(fromInput(options.headers), request.headers) : request.headers;
|
|
146
|
+
if (headers["content-length"]) headers = remove(headers, "content-length");
|
|
147
|
+
let uploadController;
|
|
148
|
+
const stopUpload = sync(() => uploadController?.abort());
|
|
149
|
+
const send = (body) => map(tryPromise({
|
|
150
|
+
try: () => fetch(url, {
|
|
151
|
+
...options,
|
|
152
|
+
method: request.method,
|
|
153
|
+
headers,
|
|
154
|
+
body,
|
|
155
|
+
duplex: typeof ReadableStream !== "undefined" && body instanceof ReadableStream ? "half" : void 0,
|
|
156
|
+
signal: uploadController ? AbortSignal.any([signal, uploadController.signal]) : signal
|
|
157
|
+
}),
|
|
158
|
+
catch: (cause) => new HttpClientError({ reason: new TransportError({
|
|
159
|
+
request,
|
|
160
|
+
cause
|
|
161
|
+
}) })
|
|
162
|
+
}), (response) => {
|
|
163
|
+
const original = fromWeb(request, response);
|
|
164
|
+
if (!uploadController) return original;
|
|
165
|
+
if (response.body === null) uploadController.abort();
|
|
166
|
+
return Object.create(original, {
|
|
167
|
+
...Object.fromEntries([
|
|
168
|
+
"arrayBuffer",
|
|
169
|
+
"text",
|
|
170
|
+
"json",
|
|
171
|
+
"formData",
|
|
172
|
+
"urlParamsBody"
|
|
173
|
+
].map((key) => [key, { get: () => ensuring(original[key], stopUpload) }])),
|
|
174
|
+
stream: { get: () => ensuring$1(original.stream, stopUpload) }
|
|
175
|
+
});
|
|
176
|
+
}).pipe(onError(() => stopUpload));
|
|
177
|
+
switch (request.body._tag) {
|
|
178
|
+
case "Raw":
|
|
179
|
+
case "Uint8Array": return send(request.body.body);
|
|
180
|
+
case "FormData": return send(request.body.formData);
|
|
181
|
+
case "Stream":
|
|
182
|
+
supportsStreamBody ??= detectStreamBodySupport();
|
|
183
|
+
if (supportsStreamBody) {
|
|
184
|
+
uploadController = new AbortController();
|
|
185
|
+
const uploadSignal = AbortSignal.any([signal, uploadController.signal]);
|
|
186
|
+
const stopped = callback((resume) => {
|
|
187
|
+
const onAbort = () => resume(interrupt);
|
|
188
|
+
if (uploadSignal.aborted) {
|
|
189
|
+
onAbort();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
uploadSignal.addEventListener("abort", onAbort, { once: true });
|
|
193
|
+
return sync(() => uploadSignal.removeEventListener("abort", onAbort));
|
|
194
|
+
});
|
|
195
|
+
return flatMap(toReadableStreamEffect(interruptWhen(request.body.stream, stopped)), send);
|
|
196
|
+
}
|
|
197
|
+
if (fiber.getRef(StreamBodyFallback) === "error") return fail(new HttpClientError({ reason: new EncodeError({
|
|
198
|
+
request,
|
|
199
|
+
description: "Streaming request bodies are not supported in this runtime"
|
|
200
|
+
}) }));
|
|
201
|
+
return mkArrayBuffer(request.body.stream).pipe(mapError((cause) => new HttpClientError({ reason: new EncodeError({
|
|
202
|
+
request,
|
|
203
|
+
cause
|
|
204
|
+
}) })), flatMap(send));
|
|
205
|
+
}
|
|
206
|
+
return send(void 0);
|
|
207
|
+
})));
|
|
208
|
+
// src/deploy/cloud-client.adapter.ts
|
|
209
|
+
const sessionOf = fnUntraced(function* sessionOf(issued) {
|
|
210
|
+
const now = yield* currentTimeMillis;
|
|
211
|
+
const current = yield* decodeEffect(DateTimeUtcFromString)(issued.expiresAt).pipe(mapBoth({
|
|
212
|
+
onFailure: () => DeployFailedError.make({ message: "The API returned an invalid token expiry." }),
|
|
213
|
+
onSuccess: (expires) => ({
|
|
214
|
+
expiresAt: toEpochMillis(expires),
|
|
215
|
+
token: make(issued.token)
|
|
216
|
+
})
|
|
217
|
+
}));
|
|
218
|
+
if (current.expiresAt <= now) return yield* DeployFailedError.make({ message: "The API returned an expired token." });
|
|
219
|
+
return current;
|
|
220
|
+
});
|
|
221
|
+
const refreshMarginMillis = 3e4;
|
|
222
|
+
const oneExchange = 1;
|
|
223
|
+
const unauthorizedStatus = 401;
|
|
224
|
+
/** A refused token's expiry, so the next request exchanges the secret again, as a refresh. */
|
|
225
|
+
const refusedAt = 0;
|
|
226
|
+
const firstSuccess = 200;
|
|
227
|
+
const firstRedirect = 300;
|
|
228
|
+
const refusedStatus = (response) => response.text.pipe(orElseSucceed(() => ""), flatMap((description) => fail(new HttpClientError({ reason: new StatusCodeError({
|
|
229
|
+
description,
|
|
230
|
+
request: response.request,
|
|
231
|
+
response
|
|
232
|
+
}) }))));
|
|
233
|
+
const succeeded = (response) => response.status >= firstSuccess && response.status < firstRedirect;
|
|
234
|
+
const statusFirst = (client) => transformResponse(client, (responded) => filterOrElse(responded, succeeded, refusedStatus));
|
|
235
|
+
const transportOf = map(HttpClient, (http) => http.pipe(transformResponse(provideService(RequestInit, { redirect: "error" }))));
|
|
236
|
+
const anonymousClient = fn("anonymousClient")(function* anonymousClient(url) {
|
|
237
|
+
const transport = yield* transportOf;
|
|
238
|
+
return make$4(mapRequest(transport, prependUrl(url.href)));
|
|
239
|
+
});
|
|
240
|
+
/** The API's refusal of the bearer itself, answered before any handler runs, so sending the request again is safe. */
|
|
241
|
+
const isBearerRefusal = is(TaggedStruct("UnauthorizedError", {}));
|
|
242
|
+
const resignedWith = ({ session, token, transport }) => (response) => gen(function* resigned() {
|
|
243
|
+
const body = yield* option(response.json);
|
|
244
|
+
if (response.status !== unauthorizedStatus || !exists(body, isBearerRefusal)) return response;
|
|
245
|
+
yield* update(session, map$1((held) => ({
|
|
246
|
+
expiresAt: refusedAt,
|
|
247
|
+
token: held.token
|
|
248
|
+
})));
|
|
249
|
+
const bearer = yield* token;
|
|
250
|
+
return yield* transport.execute(bearerToken(response.request, bearer));
|
|
251
|
+
});
|
|
252
|
+
/** The token exchange within its wait; a first sign-in (`retrying`) retries on `signInRetries` while the failure may pass. */
|
|
253
|
+
const exchanged = (exchange, retrying) => exchange.pipe(timeout(progressWait), retry({
|
|
254
|
+
schedule: signInRetries,
|
|
255
|
+
while: (failure) => retrying && retriedSignIn(failure)
|
|
256
|
+
}));
|
|
257
|
+
const cloudClient = fn("cloudClient")(function* cloudClient({ apiKey, url }) {
|
|
258
|
+
const transport = yield* transportOf;
|
|
259
|
+
const base = mapRequest(transport, prependUrl(url.href));
|
|
260
|
+
const unsigned = make$4(filterStatusOk(base));
|
|
261
|
+
const session = yield* make$1(none());
|
|
262
|
+
const exchanging = yield* make$2(oneExchange);
|
|
263
|
+
const exchange = unsigned.tokensExchange({ payload: { secret: value(apiKey) } });
|
|
264
|
+
const token = gen(function* bearerToken() {
|
|
265
|
+
const now = yield* currentTimeMillis;
|
|
266
|
+
const cached = yield* get(session);
|
|
267
|
+
const refreshing = isSome(cached);
|
|
268
|
+
if (isSome(cached) && cached.value.expiresAt > now + refreshMarginMillis) return cached.value.token;
|
|
269
|
+
yield* match(cached, {
|
|
270
|
+
onNone: () => ApiAttempt.mark("initial"),
|
|
271
|
+
onSome: () => ApiAttempt.mark("refresh")
|
|
272
|
+
});
|
|
273
|
+
const issued = yield* mapError(exchanged(exchange, !refreshing), exchangeFailure(refreshing));
|
|
274
|
+
const current = yield* sessionOf(issued);
|
|
275
|
+
yield* set(session, some(current)).pipe(andThen(ApiAttempt.mark("request")));
|
|
276
|
+
return current.token;
|
|
277
|
+
}).pipe(exchanging.withPermits(oneExchange));
|
|
278
|
+
const signed = mapRequestEffect(base, (request) => map(token, (bearer) => bearerToken(request, bearer)));
|
|
279
|
+
const authenticated = transformResponse(signed, flatMap(resignedWith({
|
|
280
|
+
session,
|
|
281
|
+
token,
|
|
282
|
+
transport
|
|
283
|
+
})));
|
|
284
|
+
return {
|
|
285
|
+
anonymous: base,
|
|
286
|
+
bearer: token,
|
|
287
|
+
client: make$4(authenticated),
|
|
288
|
+
http: authenticated,
|
|
289
|
+
progress: make$4(authenticated.pipe(statusFirst)),
|
|
290
|
+
request: ApiAttempt.request,
|
|
291
|
+
signIn: asVoid(mapError(token, signInFailure)),
|
|
292
|
+
upload: filterStatusOk(transport)
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
var CloudClient = class extends Service()("@akterjs/cli/deploy/cloud-client.adapter/CloudClient", { make: gen(function* makeCloudClient() {
|
|
296
|
+
const context$1 = yield* context().pipe(map(pick(HttpClient)));
|
|
297
|
+
return {
|
|
298
|
+
anonymous: (url) => provideContext(anonymousClient(url), context$1),
|
|
299
|
+
connect: (options) => provideContext(cloudClient(options), context$1)
|
|
300
|
+
};
|
|
301
|
+
}) }) {
|
|
302
|
+
static layer = effect(this, this.make);
|
|
303
|
+
};
|
|
304
|
+
export { Fetch as n, layer as r, CloudClient as t };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { a as withSubcommands, i as withDescription, t as make } from "../Command-Dzq9KM-h.mjs";
|
|
2
|
+
import { importApp } from "../archive/archive-cli.config.mjs";
|
|
3
|
+
import { bench, deploy, exportApp } from "../app-cli.config.mjs";
|
|
4
|
+
import { login, logout, signup } from "../credentials/login.config.mjs";
|
|
5
|
+
import { t as docs } from "../docs.config-D3zzLzLR.mjs";
|
|
6
|
+
import { localCommands } from "../local/environment-cli.config.mjs";
|
|
7
|
+
import { logCommands } from "../logs/log-follow.config.mjs";
|
|
8
|
+
import { actorCommands } from "../registry/actor-commands.config.mjs";
|
|
9
|
+
import { cloudCommands } from "../registry/cloud-commands.config.mjs";
|
|
10
|
+
// src/command/akter-commands.config.ts
|
|
11
|
+
const akterCommand = (operations) => {
|
|
12
|
+
const joined = {
|
|
13
|
+
...actorCommands(operations),
|
|
14
|
+
...logCommands
|
|
15
|
+
};
|
|
16
|
+
return make("akter").pipe(withDescription("Run Akter apps locally and operate Akter Cloud"), withSubcommands([
|
|
17
|
+
bench,
|
|
18
|
+
...localCommands,
|
|
19
|
+
deploy,
|
|
20
|
+
exportApp,
|
|
21
|
+
importApp,
|
|
22
|
+
docs,
|
|
23
|
+
login,
|
|
24
|
+
logout,
|
|
25
|
+
signup,
|
|
26
|
+
...cloudCommands({
|
|
27
|
+
joined,
|
|
28
|
+
operations
|
|
29
|
+
})
|
|
30
|
+
]));
|
|
31
|
+
};
|
|
32
|
+
export { akterCommand };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as runAkter } from "../akter-program.config-D8yb9xQP.mjs";
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { t as match } from "../Boolean-kILIvmhE.mjs";
|
|
2
|
+
import { a as option, i as String, r as Redacted } from "../Config-B6OND9WR.mjs";
|
|
3
|
+
import { At as provideContext, B as flatMap, Dt as orElseSucceed, Et as orDie, Kn as provide, M as fail, Q as fromOption, T as context, Tt as option$1, Vn as effect, W as fnUntraced, Xt as succeed, _n as when, aa as Service, dt as map, et as gen, f as cached, gn as void_, ht as mapError, tt as ignore } from "../Effect-CKFpqbmw.mjs";
|
|
4
|
+
import { t as FileSystem } from "../FileSystem-4pPcEb_i.mjs";
|
|
5
|
+
import { C as orElse, S as none, _ as isSome, a as firstSomeOf, b as map$1, d as fromUndefinedOr, f as getOrElse, h as isNone, i as filter, o as flatMap$1, w as some, x as match$1 } from "../Option-DkxSnBhp.mjs";
|
|
6
|
+
import { t as Path } from "../Path-Djdv_eF5.mjs";
|
|
7
|
+
import { i as value, n as make$1 } from "../Redacted-RkJrG5Me.mjs";
|
|
8
|
+
import { F as String$1, I as Struct, a as Boolean, an as optionalKey, pt as fromJsonString, st as encodeEffect, tt as decodeEffect, x as Literal } from "../Schema-MiKt5K1v.mjs";
|
|
9
|
+
import { s as isNonEmpty } from "../String-q5dcsVe4.mjs";
|
|
10
|
+
import { CliExitError } from "../terminal/cli-exit.errors.mjs";
|
|
11
|
+
import { Keychain } from "./keychain.adapter.mjs";
|
|
12
|
+
import { cloudUrl, defaultCloudUrl } from "../deploy/cloud-url.pure.mjs";
|
|
13
|
+
// src/credentials/credentials.service.ts
|
|
14
|
+
const LoginFile = Struct({
|
|
15
|
+
keychain: Boolean,
|
|
16
|
+
kind: Literal("apiKey"),
|
|
17
|
+
organization: optionalKey(String$1),
|
|
18
|
+
secret: optionalKey(String$1),
|
|
19
|
+
url: String$1
|
|
20
|
+
});
|
|
21
|
+
const LoginJson = fromJsonString(LoginFile);
|
|
22
|
+
const ownerOnlyFile = 384;
|
|
23
|
+
const ownerOnlyFolder = 448;
|
|
24
|
+
const fileName = "login.json";
|
|
25
|
+
const nonEmpty = (name) => option(String(name)).pipe(map(filter(isNonEmpty)), orElseSucceed(() => none()));
|
|
26
|
+
const apiKeyFromEnvironment = option(Redacted("AKTER_API_KEY")).pipe(map(filter((key) => isNonEmpty(value(key)))), orElseSucceed(() => none()));
|
|
27
|
+
const failure = (code, reason) => (message) => CliExitError.make({
|
|
28
|
+
code,
|
|
29
|
+
message,
|
|
30
|
+
reason
|
|
31
|
+
});
|
|
32
|
+
const signedOut = () => failure(CliExitError.codes.authentication, "NotSignedIn")("Not signed in. Set AKTER_API_KEY to an API key or agent key, or save one with: akter login --with-api-key < key.txt");
|
|
33
|
+
const unreadable = (file) => () => failure(CliExitError.codes.authentication, "LoginUnreadable")(`The saved login in ${file} can't be read. Run akter logout, then sign in again.`);
|
|
34
|
+
const originOf = (url) => cloudUrl(url).pipe(map((parsed) => some(parsed.origin)), orElseSucceed(() => none()));
|
|
35
|
+
const loginFile = fnUntraced(function* loginFile() {
|
|
36
|
+
const path = yield* Path;
|
|
37
|
+
const xdg = yield* nonEmpty("XDG_CONFIG_HOME");
|
|
38
|
+
const home = yield* nonEmpty("HOME");
|
|
39
|
+
const folder = orElse(xdg, () => map$1(home, (homeFolder) => path.join(homeFolder, ".config")));
|
|
40
|
+
return yield* match$1(folder, {
|
|
41
|
+
onNone: () => fail(failure(CliExitError.codes.authentication, "NoConfigFolder")("Neither XDG_CONFIG_HOME nor HOME is set, so there is no folder to keep a login in.")),
|
|
42
|
+
onSome: (found) => succeed(path.join(found, "akter", fileName))
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
const readLogin = fnUntraced(function* readLogin() {
|
|
46
|
+
const fileSystem = yield* FileSystem;
|
|
47
|
+
const located = yield* option$1(loginFile());
|
|
48
|
+
if (!(yield* match$1(located, {
|
|
49
|
+
onNone: () => succeed(false),
|
|
50
|
+
onSome: (file) => fileSystem.exists(file).pipe(orElseSucceed(() => false))
|
|
51
|
+
})) || isNone(located)) return none();
|
|
52
|
+
const file = located.value;
|
|
53
|
+
const text = yield* fileSystem.readFileString(file).pipe(mapError(unreadable(file)));
|
|
54
|
+
const login = yield* decodeEffect(LoginJson)(text).pipe(mapError(unreadable(file)));
|
|
55
|
+
return some(login);
|
|
56
|
+
});
|
|
57
|
+
const savedSecret = fnUntraced(function* savedSecret(saved) {
|
|
58
|
+
const login = yield* saved;
|
|
59
|
+
if (isNone(login)) return none();
|
|
60
|
+
if (!login.value.keychain) {
|
|
61
|
+
const secret = fromUndefinedOr(login.value.secret);
|
|
62
|
+
return map$1(secret, (value) => ({
|
|
63
|
+
secret: make$1(value),
|
|
64
|
+
source: "file"
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
const keychain = yield* Keychain;
|
|
68
|
+
const origin = yield* originOf(login.value.url);
|
|
69
|
+
const found = yield* match$1(origin, {
|
|
70
|
+
onNone: () => succeed(none()),
|
|
71
|
+
onSome: keychain.find
|
|
72
|
+
});
|
|
73
|
+
return map$1(found, (value) => ({
|
|
74
|
+
secret: make$1(value),
|
|
75
|
+
source: "keychain"
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
const elsewhere = (savedOrigin, url) => failure(CliExitError.codes.authentication, "LoginForOtherUrl")(`The saved login is for ${savedOrigin}, not ${url.origin}, so akter won't send its key there. Sign in for that URL with akter login --url ${url.origin}, or set AKTER_API_KEY.`);
|
|
79
|
+
/**
|
|
80
|
+
* A saved login's secret goes only to the origin it was saved for, so a --url or AKTER_CLOUD_URL that names another
|
|
81
|
+
* server, typed by mistake or planted in an agent's prompt, can't receive it.
|
|
82
|
+
*/
|
|
83
|
+
const boundTo = fnUntraced(function* boundTo(saved, url) {
|
|
84
|
+
const login = yield* saved;
|
|
85
|
+
const file = yield* fromOption(login).pipe(mapError(signedOut));
|
|
86
|
+
const savedOrigin = yield* originOf(file.url);
|
|
87
|
+
if (isSome(savedOrigin) && savedOrigin.value !== url.origin) return yield* elsewhere(savedOrigin.value, url);
|
|
88
|
+
return yield* void_;
|
|
89
|
+
});
|
|
90
|
+
const otherThanEnvironment = (environmentOrigin, url) => failure(CliExitError.codes.usage, "ApiKeyForOtherUrl")(`AKTER_API_KEY is for AKTER_CLOUD_URL, ${environmentOrigin}, not ${url.origin}, so akter won't send it there. Drop --url, or set AKTER_CLOUD_URL to ${url.origin}.`);
|
|
91
|
+
/**
|
|
92
|
+
* AKTER_API_KEY goes only to AKTER_CLOUD_URL's origin when that is set, so a --url planted in an agent's prompt can't
|
|
93
|
+
* collect the key a script or agent runs with. With no AKTER_CLOUD_URL, the caller chose both the key and the URL.
|
|
94
|
+
*/
|
|
95
|
+
const environmentBound = fnUntraced(function* environmentBound(url) {
|
|
96
|
+
const configured = yield* nonEmpty("AKTER_CLOUD_URL");
|
|
97
|
+
const origin = yield* flatMap(fromOption(configured), originOf).pipe(orElseSucceed(() => none()));
|
|
98
|
+
if (isSome(origin) && origin.value !== url.origin) return yield* otherThanEnvironment(origin.value, url);
|
|
99
|
+
return yield* void_;
|
|
100
|
+
});
|
|
101
|
+
/** AKTER_API_KEY first, so a script or an agent key wins over a person's saved login, which is bound to its origin. */
|
|
102
|
+
const credentialOf = fnUntraced(function* credentialOf(saved, url) {
|
|
103
|
+
const fromEnvironment = yield* apiKeyFromEnvironment;
|
|
104
|
+
if (isSome(fromEnvironment)) {
|
|
105
|
+
yield* environmentBound(url);
|
|
106
|
+
return {
|
|
107
|
+
secret: fromEnvironment.value,
|
|
108
|
+
source: "AKTER_API_KEY"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const stored = yield* savedSecret(saved);
|
|
112
|
+
const credential = yield* fromOption(stored).pipe(mapError(signedOut));
|
|
113
|
+
yield* boundTo(saved, url);
|
|
114
|
+
return credential;
|
|
115
|
+
});
|
|
116
|
+
const urlOf = fnUntraced(function* urlOf(saved, flag) {
|
|
117
|
+
const login = yield* saved;
|
|
118
|
+
const fromEnvironment = yield* nonEmpty("AKTER_CLOUD_URL");
|
|
119
|
+
const chosen = firstSomeOf([
|
|
120
|
+
flag,
|
|
121
|
+
fromEnvironment,
|
|
122
|
+
map$1(login, (file) => file.url)
|
|
123
|
+
]);
|
|
124
|
+
return yield* cloudUrl(getOrElse(chosen, () => defaultCloudUrl)).pipe(mapError((invalid) => failure(CliExitError.codes.usage, "InvalidUrl")(invalid.message)));
|
|
125
|
+
});
|
|
126
|
+
const organizationOf = fnUntraced(function* organizationOf(saved, flag) {
|
|
127
|
+
const login = yield* saved;
|
|
128
|
+
const fromEnvironment = yield* nonEmpty("AKTER_ORGANIZATION");
|
|
129
|
+
const fromLogin = flatMap$1(login, (file) => fromUndefinedOr(file.organization));
|
|
130
|
+
return firstSomeOf([
|
|
131
|
+
flag,
|
|
132
|
+
fromEnvironment,
|
|
133
|
+
fromLogin
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
const targetOf = fnUntraced(function* targetOf(saved, flags) {
|
|
137
|
+
const url = yield* urlOf(saved, flags.url);
|
|
138
|
+
const organization = yield* organizationOf(saved, flags.organization);
|
|
139
|
+
const { secret } = yield* credentialOf(saved, url);
|
|
140
|
+
const missing = failure(CliExitError.codes.usage, "MissingOrganization")("Pass --organization or set AKTER_ORGANIZATION.");
|
|
141
|
+
return {
|
|
142
|
+
apiKey: secret,
|
|
143
|
+
organization: yield* fromOption(organization).pipe(mapError(() => missing)),
|
|
144
|
+
url: url.href
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
const recordOf = (login, inKeychain) => {
|
|
148
|
+
const record = {
|
|
149
|
+
keychain: inKeychain,
|
|
150
|
+
kind: "apiKey",
|
|
151
|
+
url: login.url.href
|
|
152
|
+
};
|
|
153
|
+
const named = match$1(login.organization, {
|
|
154
|
+
onNone: () => record,
|
|
155
|
+
onSome: (organization) => ({
|
|
156
|
+
...record,
|
|
157
|
+
organization
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
if (inKeychain) return named;
|
|
161
|
+
return {
|
|
162
|
+
...named,
|
|
163
|
+
secret: value(login.secret)
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
const writeOwnerOnly = fnUntraced(function* writeOwnerOnly(file, text) {
|
|
167
|
+
const fileSystem = yield* FileSystem;
|
|
168
|
+
const path = yield* Path;
|
|
169
|
+
const staged = `${file}.tmp`;
|
|
170
|
+
yield* fileSystem.makeDirectory(path.dirname(file), {
|
|
171
|
+
mode: ownerOnlyFolder,
|
|
172
|
+
recursive: true
|
|
173
|
+
});
|
|
174
|
+
yield* fileSystem.chmod(path.dirname(file), ownerOnlyFolder);
|
|
175
|
+
yield* fileSystem.writeFileString(staged, text, { mode: ownerOnlyFile });
|
|
176
|
+
yield* fileSystem.chmod(staged, ownerOnlyFile);
|
|
177
|
+
return yield* fileSystem.rename(staged, file);
|
|
178
|
+
});
|
|
179
|
+
const save = fnUntraced(function* save(login) {
|
|
180
|
+
const file = yield* loginFile();
|
|
181
|
+
const inKeychain = yield* (yield* Keychain).store(login.url.origin, value(login.secret));
|
|
182
|
+
const text = yield* encodeEffect(LoginJson)(recordOf(login, inKeychain)).pipe(orDie);
|
|
183
|
+
yield* writeOwnerOnly(file, `${text}\n`).pipe(mapError((cause) => failure(CliExitError.codes.failed, "LoginNotSaved")(`Could not save the login in ${file}: ${cause.message}`)));
|
|
184
|
+
return {
|
|
185
|
+
file,
|
|
186
|
+
store: match(inKeychain, {
|
|
187
|
+
onFalse: () => "file",
|
|
188
|
+
onTrue: () => "keychain"
|
|
189
|
+
})
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
const clear = fnUntraced(function* clear() {
|
|
193
|
+
const fileSystem = yield* FileSystem;
|
|
194
|
+
const keychain = yield* Keychain;
|
|
195
|
+
const login = yield* readLogin().pipe(orElseSucceed(() => none()));
|
|
196
|
+
const origin = yield* match$1(login, {
|
|
197
|
+
onNone: () => succeed(none()),
|
|
198
|
+
onSome: (record) => originOf(record.url)
|
|
199
|
+
});
|
|
200
|
+
const file = yield* loginFile();
|
|
201
|
+
const exists = yield* fileSystem.exists(file).pipe(orElseSucceed(() => false));
|
|
202
|
+
yield* match$1(origin, {
|
|
203
|
+
onNone: () => void_,
|
|
204
|
+
onSome: keychain.remove
|
|
205
|
+
});
|
|
206
|
+
yield* when(ignore(fileSystem.remove(file)), succeed(exists));
|
|
207
|
+
return exists;
|
|
208
|
+
});
|
|
209
|
+
const make = gen(function* makeCredentials() {
|
|
210
|
+
const context$1 = yield* context();
|
|
211
|
+
const provided = (effect) => provideContext(effect, context$1);
|
|
212
|
+
const saved = yield* readLogin().pipe(provided, cached);
|
|
213
|
+
return {
|
|
214
|
+
clear: provided(clear()),
|
|
215
|
+
/** The secret to sign in at `url` with; a saved login's secret only for the origin it was saved for. */
|
|
216
|
+
credentialFor: (url) => provided(credentialOf(saved, url)),
|
|
217
|
+
organization: (flag) => provided(organizationOf(saved, flag)),
|
|
218
|
+
save: (login) => provided(save(login)),
|
|
219
|
+
target: (flags) => provided(targetOf(saved, flags)),
|
|
220
|
+
url: (flag) => provided(urlOf(saved, flag))
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
var Credentials = class extends Service()("@akterjs/cli/credentials/credentials.service/Credentials", { make }) {
|
|
224
|
+
static layer = effect(this, this.make).pipe(provide(Keychain.layer));
|
|
225
|
+
};
|
|
226
|
+
export { Credentials };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { j as map } from "../Array-B4kuTQeW.mjs";
|
|
2
|
+
import { Dt as orElseSucceed, Gt as scoped, K as forEach, Pt as reduce, Vn as effect, W as fnUntraced, Xt as succeed, a as all, aa as Service, dt as map$1, et as gen, in as timeout, jt as provideService } from "../Effect-CKFpqbmw.mjs";
|
|
3
|
+
import { S as none, _ as isSome, b as map$2, i as filter, w as some } from "../Option-DkxSnBhp.mjs";
|
|
4
|
+
import { M as mkString, T as make, V as runDrain, i as decodeText } from "../Stream-bK9QIETN.mjs";
|
|
5
|
+
import { f as replaceAll, s as isNonEmpty, y as trimEnd } from "../String-q5dcsVe4.mjs";
|
|
6
|
+
import { a as ChildProcessSpawner, n as make$1 } from "../ChildProcess-BCIwxPGc.mjs";
|
|
7
|
+
// src/credentials/keychain.adapter.ts
|
|
8
|
+
const service = "akter-cli";
|
|
9
|
+
const label = "Akter CLI";
|
|
10
|
+
const succeeded = 0;
|
|
11
|
+
const streams = 3;
|
|
12
|
+
const escapeQuote = replaceAll("\"", String.raw`\"`);
|
|
13
|
+
const escapeBackslash = replaceAll("\\", String.raw`\\`);
|
|
14
|
+
const quoted = (value) => `"${escapeQuote(escapeBackslash(value))}"`;
|
|
15
|
+
const encoder = new globalThis.TextEncoder();
|
|
16
|
+
const withInput = (command, args, text) => make$1(command, args, { stdin: make(encoder.encode(text)) });
|
|
17
|
+
const backends = [{
|
|
18
|
+
lookup: (account) => make$1("security", [
|
|
19
|
+
"find-generic-password",
|
|
20
|
+
"-s",
|
|
21
|
+
service,
|
|
22
|
+
"-a",
|
|
23
|
+
account,
|
|
24
|
+
"-w"
|
|
25
|
+
]),
|
|
26
|
+
remove: (account) => make$1("security", [
|
|
27
|
+
"delete-generic-password",
|
|
28
|
+
"-s",
|
|
29
|
+
service,
|
|
30
|
+
"-a",
|
|
31
|
+
account
|
|
32
|
+
]),
|
|
33
|
+
store: (account, secret) => withInput("security", ["-i"], `add-generic-password -U -s ${service} -a ${quoted(account)} -l ${quoted(label)} -w ${quoted(secret)}\n`)
|
|
34
|
+
}, {
|
|
35
|
+
lookup: (account) => make$1("secret-tool", [
|
|
36
|
+
"lookup",
|
|
37
|
+
"service",
|
|
38
|
+
service,
|
|
39
|
+
"account",
|
|
40
|
+
account
|
|
41
|
+
]),
|
|
42
|
+
remove: (account) => make$1("secret-tool", [
|
|
43
|
+
"clear",
|
|
44
|
+
"service",
|
|
45
|
+
service,
|
|
46
|
+
"account",
|
|
47
|
+
account
|
|
48
|
+
]),
|
|
49
|
+
store: (account, secret) => withInput("secret-tool", [
|
|
50
|
+
"store",
|
|
51
|
+
"--label",
|
|
52
|
+
label,
|
|
53
|
+
"service",
|
|
54
|
+
service,
|
|
55
|
+
"account",
|
|
56
|
+
account
|
|
57
|
+
], secret)
|
|
58
|
+
}];
|
|
59
|
+
const run = fnUntraced(function* run(command) {
|
|
60
|
+
const handle = yield* (yield* ChildProcessSpawner).spawn(command);
|
|
61
|
+
const output = mkString(decodeText(handle.stdout));
|
|
62
|
+
const [text, code] = yield* all([
|
|
63
|
+
output,
|
|
64
|
+
handle.exitCode,
|
|
65
|
+
runDrain(handle.stderr)
|
|
66
|
+
], { concurrency: streams });
|
|
67
|
+
return filter(some(text), () => code === succeeded);
|
|
68
|
+
}, scoped);
|
|
69
|
+
const attempt = (command) => run(command).pipe(timeout("10 seconds"), orElseSucceed(() => none()));
|
|
70
|
+
const firstFound = (account) => (found, backend) => {
|
|
71
|
+
if (isSome(found)) return succeed(found);
|
|
72
|
+
return map$1(attempt(backend.lookup(account)), (output) => filter(map$2(output, (text) => trimEnd(text)), (text) => isNonEmpty(text)));
|
|
73
|
+
};
|
|
74
|
+
const firstStored = (account, secret) => (stored, backend) => {
|
|
75
|
+
if (stored) return succeed(true);
|
|
76
|
+
return map$1(attempt(backend.store(account, secret)), isSome);
|
|
77
|
+
};
|
|
78
|
+
const find = (account) => reduce(backends, () => none(), firstFound(account));
|
|
79
|
+
const store = (account, secret) => reduce(backends, () => false, firstStored(account, secret));
|
|
80
|
+
const remove = (account) => forEach(map(backends, (backend) => backend.remove(account)), (command) => attempt(command), { discard: true });
|
|
81
|
+
var Keychain = class extends Service()("@akterjs/cli/credentials/keychain.adapter/Keychain", { make: gen(function* makeKeychain() {
|
|
82
|
+
const spawner = yield* ChildProcessSpawner;
|
|
83
|
+
const provide = provideService(ChildProcessSpawner, spawner);
|
|
84
|
+
return {
|
|
85
|
+
find: (account) => provide(find(account)),
|
|
86
|
+
remove: (account) => provide(remove(account)),
|
|
87
|
+
store: (account, secret) => provide(store(account, secret))
|
|
88
|
+
};
|
|
89
|
+
}) }) {
|
|
90
|
+
static layer = effect(this, this.make);
|
|
91
|
+
};
|
|
92
|
+
export { Keychain };
|