pageship 0.0.1 → 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.
Files changed (3) hide show
  1. package/README.md +95 -6
  2. package/dist/cli.mjs +1564 -6
  3. package/package.json +6 -8
package/dist/cli.mjs CHANGED
@@ -1,13 +1,1571 @@
1
1
  #!/usr/bin/env node
2
- import { NodeRuntime, NodeServices } from "@effect/platform-node";
3
- import { Effect } from "effect";
4
- import { Command } from "effect/unstable/cli";
2
+ import { execFile } from "node:child_process";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { lstat, mkdir, open, realpath, rename, rm } from "node:fs/promises";
5
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
+ import { createInterface } from "node:readline/promises";
7
+ import { promisify } from "node:util";
8
+ import { constants } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
11
+ import { NodeServices } from "@effect/platform-node";
12
+ import { Cause, Config, Console, Effect, Option, Schema } from "effect";
13
+ import { Argument, CliConfig, CliError, CliOutput, Command, Flag, GlobalFlag } from "effect/unstable/cli";
14
+ //#region ../../packages/api-client/src/generated/core/bodySerializer.gen.ts
15
+ const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
16
+ Object.entries({
17
+ $body_: "body",
18
+ $headers_: "headers",
19
+ $path_: "path",
20
+ $query_: "query"
21
+ });
22
+ //#endregion
23
+ //#region ../../packages/api-client/src/generated/core/serverSentEvents.gen.ts
24
+ function createSseClient({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url, ...options }) {
25
+ let lastEventId;
26
+ const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
27
+ const createStream = async function* () {
28
+ let retryDelay = sseDefaultRetryDelay ?? 3e3;
29
+ let attempt = 0;
30
+ const signal = options.signal ?? new AbortController().signal;
31
+ while (true) {
32
+ if (signal.aborted) break;
33
+ attempt++;
34
+ const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
35
+ if (lastEventId !== void 0) headers.set("Last-Event-ID", lastEventId);
36
+ try {
37
+ const requestInit = {
38
+ redirect: "follow",
39
+ ...options,
40
+ body: options.serializedBody,
41
+ headers,
42
+ signal
43
+ };
44
+ let request = new Request(url, requestInit);
45
+ if (onRequest) request = await onRequest(url, requestInit);
46
+ const response = await (options.fetch ?? globalThis.fetch)(request);
47
+ if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
48
+ if (!response.body) throw new Error("No body in SSE response");
49
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
50
+ let buffer = "";
51
+ const abortHandler = () => {
52
+ try {
53
+ reader.cancel();
54
+ } catch {}
55
+ };
56
+ signal.addEventListener("abort", abortHandler);
57
+ try {
58
+ while (true) {
59
+ const { done, value } = await reader.read();
60
+ if (done) break;
61
+ buffer += value;
62
+ buffer = buffer.replace(/\r\n?/g, "\n");
63
+ const chunks = buffer.split("\n\n");
64
+ buffer = chunks.pop() ?? "";
65
+ for (const chunk of chunks) {
66
+ const lines = chunk.split("\n");
67
+ const dataLines = [];
68
+ let eventName;
69
+ for (const line of lines) if (line.startsWith("data:")) dataLines.push(line.replace(/^data:\s*/, ""));
70
+ else if (line.startsWith("event:")) eventName = line.replace(/^event:\s*/, "");
71
+ else if (line.startsWith("id:")) lastEventId = line.replace(/^id:\s*/, "");
72
+ else if (line.startsWith("retry:")) {
73
+ const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
74
+ if (!Number.isNaN(parsed)) retryDelay = parsed;
75
+ }
76
+ let data;
77
+ let parsedJson = false;
78
+ if (dataLines.length) {
79
+ const rawData = dataLines.join("\n");
80
+ try {
81
+ data = JSON.parse(rawData);
82
+ parsedJson = true;
83
+ } catch {
84
+ data = rawData;
85
+ }
86
+ }
87
+ if (parsedJson) {
88
+ if (responseValidator) await responseValidator(data);
89
+ if (responseTransformer) data = await responseTransformer(data);
90
+ }
91
+ onSseEvent?.({
92
+ data,
93
+ event: eventName,
94
+ id: lastEventId,
95
+ retry: retryDelay
96
+ });
97
+ if (dataLines.length) yield data;
98
+ }
99
+ }
100
+ } finally {
101
+ signal.removeEventListener("abort", abortHandler);
102
+ reader.releaseLock();
103
+ }
104
+ break;
105
+ } catch (error) {
106
+ onSseError?.(error);
107
+ if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) break;
108
+ const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4);
109
+ await sleep(backoff);
110
+ }
111
+ }
112
+ };
113
+ return { stream: createStream() };
114
+ }
115
+ //#endregion
116
+ //#region ../../packages/api-client/src/generated/core/pathSerializer.gen.ts
117
+ const separatorArrayExplode = (style) => {
118
+ switch (style) {
119
+ case "label": return ".";
120
+ case "matrix": return ";";
121
+ case "simple": return ",";
122
+ default: return "&";
123
+ }
124
+ };
125
+ const separatorArrayNoExplode = (style) => {
126
+ switch (style) {
127
+ case "form": return ",";
128
+ case "pipeDelimited": return "|";
129
+ case "spaceDelimited": return "%20";
130
+ default: return ",";
131
+ }
132
+ };
133
+ const separatorObjectExplode = (style) => {
134
+ switch (style) {
135
+ case "label": return ".";
136
+ case "matrix": return ";";
137
+ case "simple": return ",";
138
+ default: return "&";
139
+ }
140
+ };
141
+ const serializeArrayParam = ({ allowReserved, explode, name, style, value }) => {
142
+ if (!explode) {
143
+ const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
144
+ switch (style) {
145
+ case "label": return `.${joinedValues}`;
146
+ case "matrix": return `;${name}=${joinedValues}`;
147
+ case "simple": return joinedValues;
148
+ default: return `${name}=${joinedValues}`;
149
+ }
150
+ }
151
+ const separator = separatorArrayExplode(style);
152
+ const joinedValues = value.map((v) => {
153
+ if (style === "label" || style === "simple") return allowReserved ? v : encodeURIComponent(v);
154
+ return serializePrimitiveParam({
155
+ allowReserved,
156
+ name,
157
+ value: v
158
+ });
159
+ }).join(separator);
160
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
161
+ };
162
+ const serializePrimitiveParam = ({ allowReserved, name, value }) => {
163
+ if (value === void 0 || value === null) return "";
164
+ if (typeof value === "object") throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
165
+ return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
166
+ };
167
+ const serializeObjectParam = ({ allowReserved, explode, name, style, value, valueOnly }) => {
168
+ if (value instanceof Date) return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
169
+ if (style !== "deepObject" && !explode) {
170
+ let values = [];
171
+ Object.entries(value).forEach(([key, v]) => {
172
+ values = [
173
+ ...values,
174
+ key,
175
+ allowReserved ? v : encodeURIComponent(v)
176
+ ];
177
+ });
178
+ const joinedValues = values.join(",");
179
+ switch (style) {
180
+ case "form": return `${name}=${joinedValues}`;
181
+ case "label": return `.${joinedValues}`;
182
+ case "matrix": return `;${name}=${joinedValues}`;
183
+ default: return joinedValues;
184
+ }
185
+ }
186
+ const separator = separatorObjectExplode(style);
187
+ const joinedValues = Object.entries(value).map(([key, v]) => serializePrimitiveParam({
188
+ allowReserved,
189
+ name: style === "deepObject" ? `${name}[${key}]` : key,
190
+ value: v
191
+ })).join(separator);
192
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
193
+ };
194
+ //#endregion
195
+ //#region ../../packages/api-client/src/generated/core/utils.gen.ts
196
+ const PATH_PARAM_RE = /\{[^{}]+\}/g;
197
+ const defaultPathSerializer = ({ path, url: _url }) => {
198
+ let url = _url;
199
+ const matches = _url.match(PATH_PARAM_RE);
200
+ if (matches) for (const match of matches) {
201
+ let explode = false;
202
+ let name = match.substring(1, match.length - 1);
203
+ let style = "simple";
204
+ if (name.endsWith("*")) {
205
+ explode = true;
206
+ name = name.substring(0, name.length - 1);
207
+ }
208
+ if (name.startsWith(".")) {
209
+ name = name.substring(1);
210
+ style = "label";
211
+ } else if (name.startsWith(";")) {
212
+ name = name.substring(1);
213
+ style = "matrix";
214
+ }
215
+ const value = path[name];
216
+ if (value === void 0 || value === null) continue;
217
+ if (Array.isArray(value)) {
218
+ url = url.replace(match, serializeArrayParam({
219
+ explode,
220
+ name,
221
+ style,
222
+ value
223
+ }));
224
+ continue;
225
+ }
226
+ if (typeof value === "object") {
227
+ url = url.replace(match, serializeObjectParam({
228
+ explode,
229
+ name,
230
+ style,
231
+ value,
232
+ valueOnly: true
233
+ }));
234
+ continue;
235
+ }
236
+ if (style === "matrix") {
237
+ url = url.replace(match, `;${serializePrimitiveParam({
238
+ name,
239
+ value
240
+ })}`);
241
+ continue;
242
+ }
243
+ const replaceValue = encodeURIComponent(style === "label" ? `.${value}` : value);
244
+ url = url.replace(match, replaceValue);
245
+ }
246
+ return url;
247
+ };
248
+ const getUrl = ({ baseUrl, path, query, querySerializer, url: _url }) => {
249
+ const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
250
+ let url = (baseUrl ?? "") + pathUrl;
251
+ if (path) url = defaultPathSerializer({
252
+ path,
253
+ url
254
+ });
255
+ let search = query ? querySerializer(query) : "";
256
+ if (search.startsWith("?")) search = search.substring(1);
257
+ if (search) url += `?${search}`;
258
+ return url;
259
+ };
260
+ function getValidRequestBody(options) {
261
+ const hasBody = options.body !== void 0;
262
+ if (hasBody && options.bodySerializer) {
263
+ if ("serializedBody" in options) return options.serializedBody !== void 0 && options.serializedBody !== "" ? options.serializedBody : null;
264
+ return options.body !== "" ? options.body : null;
265
+ }
266
+ if (hasBody) return options.body;
267
+ }
268
+ //#endregion
269
+ //#region ../../packages/api-client/src/generated/core/auth.gen.ts
270
+ const getAuthToken = async (auth, callback) => {
271
+ const token = typeof callback === "function" ? await callback(auth) : callback;
272
+ if (!token) return;
273
+ if (auth.scheme === "bearer") return `Bearer ${token}`;
274
+ if (auth.scheme === "basic") return `Basic ${btoa(token)}`;
275
+ return token;
276
+ };
277
+ //#endregion
278
+ //#region ../../packages/api-client/src/generated/client/utils.gen.ts
279
+ const createQuerySerializer = ({ parameters = {}, ...args } = {}) => {
280
+ const querySerializer = (queryParams) => {
281
+ const search = [];
282
+ if (queryParams && typeof queryParams === "object") for (const name in queryParams) {
283
+ const value = queryParams[name];
284
+ if (value === void 0 || value === null) continue;
285
+ const options = parameters[name] || args;
286
+ if (Array.isArray(value)) {
287
+ const serializedArray = serializeArrayParam({
288
+ allowReserved: options.allowReserved,
289
+ explode: true,
290
+ name,
291
+ style: "form",
292
+ value,
293
+ ...options.array
294
+ });
295
+ if (serializedArray) search.push(serializedArray);
296
+ } else if (typeof value === "object") {
297
+ const serializedObject = serializeObjectParam({
298
+ allowReserved: options.allowReserved,
299
+ explode: true,
300
+ name,
301
+ style: "deepObject",
302
+ value,
303
+ ...options.object
304
+ });
305
+ if (serializedObject) search.push(serializedObject);
306
+ } else {
307
+ const serializedPrimitive = serializePrimitiveParam({
308
+ allowReserved: options.allowReserved,
309
+ name,
310
+ value
311
+ });
312
+ if (serializedPrimitive) search.push(serializedPrimitive);
313
+ }
314
+ }
315
+ return search.join("&");
316
+ };
317
+ return querySerializer;
318
+ };
319
+ /**
320
+ * Infers parseAs value from provided Content-Type header.
321
+ */
322
+ const getParseAs = (contentType) => {
323
+ if (!contentType) return "stream";
324
+ const cleanContent = contentType.split(";")[0]?.trim();
325
+ if (!cleanContent) return;
326
+ if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) return "json";
327
+ if (cleanContent === "multipart/form-data") return "formData";
328
+ if ([
329
+ "application/",
330
+ "audio/",
331
+ "image/",
332
+ "video/"
333
+ ].some((type) => cleanContent.startsWith(type))) return "blob";
334
+ if (cleanContent.startsWith("text/")) return "text";
335
+ };
336
+ const checkForExistence = (options, name) => {
337
+ if (!name) return false;
338
+ if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) return true;
339
+ return false;
340
+ };
341
+ async function setAuthParams(options) {
342
+ for (const auth of options.security ?? []) {
343
+ if (checkForExistence(options, auth.name)) continue;
344
+ const token = await getAuthToken(auth, options.auth);
345
+ if (!token) continue;
346
+ const name = auth.name ?? "Authorization";
347
+ switch (auth.in) {
348
+ case "query":
349
+ if (!options.query) options.query = {};
350
+ options.query[name] = token;
351
+ break;
352
+ case "cookie":
353
+ options.headers.append("Cookie", `${name}=${token}`);
354
+ break;
355
+ default: options.headers.set(name, token);
356
+ }
357
+ }
358
+ }
359
+ const buildUrl = (options) => getUrl({
360
+ baseUrl: options.baseUrl,
361
+ path: options.path,
362
+ query: options.query,
363
+ querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
364
+ url: options.url
365
+ });
366
+ const mergeConfigs = (a, b) => {
367
+ const config = {
368
+ ...a,
369
+ ...b
370
+ };
371
+ if (config.baseUrl?.endsWith("/")) config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
372
+ config.headers = mergeHeaders(a.headers, b.headers);
373
+ return config;
374
+ };
375
+ const headersEntries = (headers) => {
376
+ const entries = [];
377
+ headers.forEach((value, key) => {
378
+ entries.push([key, value]);
379
+ });
380
+ return entries;
381
+ };
382
+ const mergeHeaders = (...headers) => {
383
+ const mergedHeaders = new Headers();
384
+ for (const header of headers) {
385
+ if (!header) continue;
386
+ const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
387
+ for (const [key, value] of iterator) if (value === null) mergedHeaders.delete(key);
388
+ else if (Array.isArray(value)) for (const v of value) mergedHeaders.append(key, v);
389
+ else if (value !== void 0) mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : value);
390
+ }
391
+ return mergedHeaders;
392
+ };
393
+ var Interceptors = class {
394
+ fns = [];
395
+ clear() {
396
+ this.fns = [];
397
+ }
398
+ eject(id) {
399
+ const index = this.getInterceptorIndex(id);
400
+ if (this.fns[index]) this.fns[index] = null;
401
+ }
402
+ exists(id) {
403
+ const index = this.getInterceptorIndex(id);
404
+ return Boolean(this.fns[index]);
405
+ }
406
+ getInterceptorIndex(id) {
407
+ if (typeof id === "number") return this.fns[id] ? id : -1;
408
+ return this.fns.indexOf(id);
409
+ }
410
+ update(id, fn) {
411
+ const index = this.getInterceptorIndex(id);
412
+ if (this.fns[index]) {
413
+ this.fns[index] = fn;
414
+ return id;
415
+ }
416
+ return false;
417
+ }
418
+ use(fn) {
419
+ this.fns.push(fn);
420
+ return this.fns.length - 1;
421
+ }
422
+ };
423
+ const createInterceptors = () => ({
424
+ error: new Interceptors(),
425
+ request: new Interceptors(),
426
+ response: new Interceptors()
427
+ });
428
+ const defaultQuerySerializer = createQuerySerializer({
429
+ allowReserved: false,
430
+ array: {
431
+ explode: true,
432
+ style: "form"
433
+ },
434
+ object: {
435
+ explode: true,
436
+ style: "deepObject"
437
+ }
438
+ });
439
+ const defaultHeaders = { "Content-Type": "application/json" };
440
+ const createConfig = (override = {}) => ({
441
+ ...jsonBodySerializer,
442
+ headers: defaultHeaders,
443
+ parseAs: "auto",
444
+ querySerializer: defaultQuerySerializer,
445
+ ...override
446
+ });
447
+ //#endregion
448
+ //#region ../../packages/api-client/src/generated/client/client.gen.ts
449
+ const createClient = (config = {}) => {
450
+ let _config = mergeConfigs(createConfig(), config);
451
+ const getConfig = () => ({ ..._config });
452
+ const setConfig = (config) => {
453
+ _config = mergeConfigs(_config, config);
454
+ return getConfig();
455
+ };
456
+ const interceptors = createInterceptors();
457
+ const beforeRequest = async (options) => {
458
+ const opts = {
459
+ ..._config,
460
+ ...options,
461
+ fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
462
+ headers: mergeHeaders(_config.headers, options.headers),
463
+ serializedBody: void 0
464
+ };
465
+ if (opts.security) await setAuthParams(opts);
466
+ if (opts.requestValidator) await opts.requestValidator(opts);
467
+ if (opts.body !== void 0 && opts.bodySerializer) opts.serializedBody = opts.bodySerializer(opts.body);
468
+ if (opts.body === void 0 || opts.serializedBody === "") opts.headers.delete("Content-Type");
469
+ const resolvedOpts = opts;
470
+ return {
471
+ opts: resolvedOpts,
472
+ url: buildUrl(resolvedOpts)
473
+ };
474
+ };
475
+ const request = async (options) => {
476
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
477
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
478
+ let request;
479
+ let response;
480
+ try {
481
+ const { opts, url } = await beforeRequest(options);
482
+ const requestInit = {
483
+ redirect: "follow",
484
+ ...opts,
485
+ body: getValidRequestBody(opts)
486
+ };
487
+ request = new Request(url, requestInit);
488
+ for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
489
+ const _fetch = opts.fetch;
490
+ response = await _fetch(request);
491
+ for (const fn of interceptors.response.fns) if (fn) response = await fn(response, request, opts);
492
+ const result = {
493
+ request,
494
+ response
495
+ };
496
+ if (response.ok) {
497
+ const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
498
+ if (response.status === 204 || response.headers.get("Content-Length") === "0") {
499
+ let emptyData;
500
+ switch (parseAs) {
501
+ case "arrayBuffer":
502
+ case "blob":
503
+ case "text":
504
+ emptyData = await response[parseAs]();
505
+ break;
506
+ case "formData":
507
+ emptyData = new FormData();
508
+ break;
509
+ case "stream":
510
+ emptyData = response.body;
511
+ break;
512
+ default: emptyData = {};
513
+ }
514
+ return opts.responseStyle === "data" ? emptyData : {
515
+ data: emptyData,
516
+ ...result
517
+ };
518
+ }
519
+ let data;
520
+ switch (parseAs) {
521
+ case "arrayBuffer":
522
+ case "blob":
523
+ case "formData":
524
+ case "text":
525
+ data = await response[parseAs]();
526
+ break;
527
+ case "json": {
528
+ const text = await response.text();
529
+ data = text ? JSON.parse(text) : {};
530
+ break;
531
+ }
532
+ case "stream": return opts.responseStyle === "data" ? response.body : {
533
+ data: response.body,
534
+ ...result
535
+ };
536
+ }
537
+ if (parseAs === "json") {
538
+ if (opts.responseValidator) await opts.responseValidator(data);
539
+ if (opts.responseTransformer) data = await opts.responseTransformer(data);
540
+ }
541
+ return opts.responseStyle === "data" ? data : {
542
+ data,
543
+ ...result
544
+ };
545
+ }
546
+ const textError = await response.text();
547
+ let jsonError;
548
+ try {
549
+ jsonError = JSON.parse(textError);
550
+ } catch {}
551
+ throw jsonError ?? textError;
552
+ } catch (error) {
553
+ let finalError = error;
554
+ for (const fn of interceptors.error.fns) if (fn) finalError = await fn(finalError, response, request, options);
555
+ finalError = finalError || {};
556
+ if (throwOnError) throw finalError;
557
+ return responseStyle === "data" ? void 0 : {
558
+ error: finalError,
559
+ request,
560
+ response
561
+ };
562
+ }
563
+ };
564
+ const makeMethodFn = (method) => (options) => request({
565
+ ...options,
566
+ method
567
+ });
568
+ const makeSseFn = (method) => async (options) => {
569
+ const { opts, url } = await beforeRequest(options);
570
+ return createSseClient({
571
+ ...opts,
572
+ body: opts.body,
573
+ method,
574
+ onRequest: async (url, init) => {
575
+ let request = new Request(url, init);
576
+ for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
577
+ return request;
578
+ },
579
+ serializedBody: getValidRequestBody(opts),
580
+ url
581
+ });
582
+ };
583
+ const _buildUrl = (options) => buildUrl({
584
+ ..._config,
585
+ ...options
586
+ });
587
+ return {
588
+ buildUrl: _buildUrl,
589
+ connect: makeMethodFn("CONNECT"),
590
+ delete: makeMethodFn("DELETE"),
591
+ get: makeMethodFn("GET"),
592
+ getConfig,
593
+ head: makeMethodFn("HEAD"),
594
+ interceptors,
595
+ options: makeMethodFn("OPTIONS"),
596
+ patch: makeMethodFn("PATCH"),
597
+ post: makeMethodFn("POST"),
598
+ put: makeMethodFn("PUT"),
599
+ request,
600
+ setConfig,
601
+ sse: {
602
+ connect: makeSseFn("CONNECT"),
603
+ delete: makeSseFn("DELETE"),
604
+ get: makeSseFn("GET"),
605
+ head: makeSseFn("HEAD"),
606
+ options: makeSseFn("OPTIONS"),
607
+ patch: makeSseFn("PATCH"),
608
+ post: makeSseFn("POST"),
609
+ put: makeSseFn("PUT"),
610
+ trace: makeSseFn("TRACE")
611
+ },
612
+ trace: makeMethodFn("TRACE")
613
+ };
614
+ };
615
+ //#endregion
616
+ //#region ../../packages/api-client/src/generated/client.gen.ts
617
+ const client = createClient(createConfig());
618
+ //#endregion
619
+ //#region ../../packages/api-client/src/generated/sdk.gen.ts
620
+ const systemCompatibility = (options) => (options?.client ?? client).get({
621
+ url: "/api/compatibility",
622
+ ...options
623
+ });
624
+ const personalWorkspace = (options) => (options?.client ?? client).get({
625
+ url: "/api/v1/workspace",
626
+ ...options
627
+ });
628
+ const addressAllocate = (options) => (options.client ?? client).post({
629
+ url: "/api/v1/workspace/address",
630
+ ...options,
631
+ headers: {
632
+ "Content-Type": "application/json",
633
+ ...options.headers
634
+ }
635
+ });
636
+ const documentsCreate = (options) => (options.client ?? client).post({
637
+ bodySerializer: null,
638
+ url: "/api/v1/documents",
639
+ ...options,
640
+ headers: {
641
+ "Content-Type": "text/html",
642
+ ...options.headers
643
+ }
644
+ });
645
+ const documentsLimits = (options) => (options?.client ?? client).get({
646
+ url: "/api/v1/documents/limits",
647
+ ...options
648
+ });
649
+ //#endregion
650
+ //#region ../../packages/api-client/src/address.ts
651
+ async function updateWorkspaceAddress(baseUrl, action, options = {}) {
652
+ const timeout = AbortSignal.timeout(1e4);
653
+ const { data, response, error } = await addressAllocate({
654
+ baseUrl,
655
+ body: action,
656
+ credentials: options.token ? "omit" : "include",
657
+ headers: options.token ? { Authorization: `Bearer ${options.token}` } : void 0,
658
+ redirect: "error",
659
+ cache: "no-store",
660
+ throwOnError: false,
661
+ signal: options.signal ? AbortSignal.any([timeout, options.signal]) : timeout
662
+ });
663
+ if (!response?.ok) throw new Error("Could not update your workspace address. Try again.", { cause: error });
664
+ if (data?.status === "signed_out" || data?.status === "uninvited" || data?.status === "reauthentication_required") return { status: data.status };
665
+ const validAddress = (value) => typeof value.subdomain === "string" && /^[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?$/u.test(value.subdomain) && typeof value.hostname === "string" && value.hostname === `${value.subdomain}.pageship.dev`;
666
+ if (data?.status === "allocated" && validAddress(data)) return data;
667
+ if (data?.status === "choice_required" && ["unavailable", "stale_choice"].includes(data.reason) && Array.isArray(data.choices) && data.choices.every((choice) => choice && typeof choice.choiceId === "string" && choice.choiceId.length > 0 && validAddress(choice))) return data;
668
+ throw new Error("Invalid workspace address response.");
669
+ }
670
+ //#endregion
671
+ //#region ../../packages/api-client/src/documents.ts
672
+ function isDocumentId(value) {
673
+ return typeof value === "string" && /^[A-Za-z0-9]{6}(?![\s\S])/u.test(value);
674
+ }
675
+ function isRevisionId(value) {
676
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}(?![\s\S])/iu.test(value);
677
+ }
678
+ function requestOptions(baseUrl, options) {
679
+ const timeout = AbortSignal.timeout(3e4);
680
+ return {
681
+ baseUrl,
682
+ credentials: options.token ? "omit" : "include",
683
+ headers: options.token ? { Authorization: `Bearer ${options.token}` } : {},
684
+ signal: options.signal ? AbortSignal.any([timeout, options.signal]) : timeout,
685
+ redirect: "error",
686
+ cache: "no-store",
687
+ throwOnError: false
688
+ };
689
+ }
690
+ function record(value) {
691
+ return typeof value === "object" && value !== null && !Array.isArray(value);
692
+ }
693
+ function validTitle(value) {
694
+ return typeof value === "string" && value.length >= 1 && value.length <= 120 && value === value.trim() && !/[\x00-\x1f\x7f-\x9f\u2028\u2029]/u.test(value);
695
+ }
696
+ function normalizeDocument(value) {
697
+ if (!record(value) || typeof value.slug !== "string") return;
698
+ const normalized = value.title === void 0 ? {
699
+ ...value,
700
+ title: value.slug
701
+ } : value;
702
+ return validDocument(normalized) ? normalized : void 0;
703
+ }
704
+ function validDocument(value) {
705
+ return record(value) && isDocumentId(value.id) && typeof value.slug === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/u.test(value.slug) && validTitle(value.title) && typeof value.sourcePath === "string" && value.sourcePath.length <= 1024 && /^(?!\/)(?!.*(?:^|\/)\.{1,2}(?:\/|$))(?!.*\/\/)[^\x00-\x1f\x7f\\:]*\.[hH][tT][mM][lL]?(?![\s\S])/u.test(value.sourcePath) && typeof value.sourceContext === "string" && /^(?:personal|git:[a-f0-9]{64})(?![\s\S])/u.test(value.sourceContext) && (value.visibility === "private" || value.visibility === "public") && isRevisionId(value.revisionId) && typeof value.byteLength === "number" && Number.isSafeInteger(value.byteLength) && value.byteLength > 0 && typeof value.createdAt === "number" && Number.isSafeInteger(value.createdAt) && value.createdAt >= 0 && typeof value.url === "string" && new RegExp(`^https://[a-z0-9-]+\\.pageship\\.dev/d/${value.id}-${value.slug}$`, "u").test(value.url);
706
+ }
707
+ var DocumentRequestError = class extends Error {
708
+ status;
709
+ httpStatus;
710
+ constructor(status, message, httpStatus) {
711
+ super(message);
712
+ this.status = status;
713
+ this.httpStatus = httpStatus;
714
+ }
715
+ };
716
+ function failed(response, error) {
717
+ if (response && (response.status === 401 || response.status === 403)) throw new DocumentRequestError(response.status === 401 ? "signed_out" : "uninvited", response.status === 401 ? "Your installation is not authorized. Run login again." : "This account is not invited.", response.status);
718
+ if (response?.status === 413) throw new DocumentRequestError("upload_too_large", "HTML exceeds the server's per-file limit.", 413);
719
+ throw new Error("Could not complete the document request. Retry with the same idempotency key.", { cause: error });
720
+ }
721
+ async function getDocumentLimits(baseUrl, options = {}) {
722
+ const { data: received, response, error } = await documentsLimits(requestOptions(baseUrl, options));
723
+ if (!response?.ok) failed(response, error);
724
+ const data = received;
725
+ if (!record(data) || typeof data.maxUploadBytes !== "number" || !Number.isSafeInteger(data.maxUploadBytes) || data.maxUploadBytes < 1) throw new Error("Invalid document limits response.");
726
+ if (data.publicationActions !== void 0 && data.publicationActions !== null && (!Array.isArray(data.publicationActions) || !data.publicationActions.every((action) => typeof action === "string"))) throw new Error("Invalid publication capabilities response.");
727
+ const publicationActions = Array.isArray(data.publicationActions) ? data.publicationActions.filter((action) => action === "new" || action === "revise") : [];
728
+ return {
729
+ maxUploadBytes: data.maxUploadBytes,
730
+ publicationActions
731
+ };
732
+ }
733
+ async function publishDocument(baseUrl, input, options = {}) {
734
+ if (input.action) {
735
+ if (!(await getDocumentLimits(baseUrl, options)).publicationActions.includes(input.action)) throw new DocumentRequestError("publication_action_unsupported", "This server does not support explicit publication actions. Ask the operator to update the server. No HTML was uploaded.", 400);
736
+ }
737
+ const request = requestOptions(baseUrl, options);
738
+ const { data: received, response, error } = await documentsCreate({
739
+ ...request,
740
+ headers: {
741
+ ...request.headers,
742
+ "idempotency-key": input.idempotencyKey
743
+ },
744
+ body: new Blob([new Uint8Array(input.bytes)], { type: "text/html" }),
745
+ query: {
746
+ sourcePath: input.sourcePath,
747
+ sourceContext: input.sourceContext,
748
+ action: input.action,
749
+ documentId: input.documentId,
750
+ expectedRevisionId: input.expectedRevisionId
751
+ }
752
+ });
753
+ if (!response?.ok) failed(response, error);
754
+ const data = received;
755
+ const published = record(data) ? normalizeDocument(data.document) : void 0;
756
+ if (record(data) && (data.status === "published" || data.status === "revision_conflict") && published) return {
757
+ status: data.status,
758
+ document: published
759
+ };
760
+ if (record(data) && data.status === "source_collision" && Array.isArray(data.candidates)) {
761
+ const candidates = data.candidates.map((document) => normalizeDocument(document));
762
+ if (candidates.every((document) => document !== void 0)) return {
763
+ status: "source_collision",
764
+ candidates
765
+ };
766
+ }
767
+ if (record(data) && (data.status === "idempotency_conflict" || data.status === "address_required" || data.status === "unavailable")) return { status: data.status };
768
+ throw new Error("Invalid publication response. Retry with the same idempotency key.");
769
+ }
770
+ //#endregion
771
+ //#region ../../packages/api-client/src/index.ts
772
+ async function getWorkspace(baseUrl, options = {}) {
773
+ const timeout = AbortSignal.timeout(1e4);
774
+ const { data, response, error } = await personalWorkspace({
775
+ baseUrl,
776
+ credentials: options.token ? "omit" : "include",
777
+ headers: options.token ? { Authorization: `Bearer ${options.token}` } : void 0,
778
+ redirect: "error",
779
+ cache: "no-store",
780
+ signal: options.signal ? AbortSignal.any([timeout, options.signal]) : timeout,
781
+ throwOnError: false
782
+ });
783
+ if (!response?.ok) throw new Error("Could not load your workspace.", { cause: error });
784
+ if (data?.status === "signed_out" || data?.status === "uninvited") return { status: data.status };
785
+ if (data?.status === "admitted" && typeof data.user?.id === "string" && data.user.id.length > 0 && typeof data.user?.name === "string" && typeof data.workspace?.id === "string" && data.workspace.id.length > 0) return data;
786
+ throw new Error("Invalid workspace response.");
787
+ }
788
+ function validateCompatibility(value) {
789
+ if (typeof value !== "object" || value === null) throw new Error("Invalid server compatibility response.");
790
+ const candidate = value;
791
+ const major = (value) => Number.isSafeInteger(value) && Number(value) >= 0;
792
+ if (typeof candidate.serverVersion !== "string" || candidate.serverVersion.length === 0 || !major(candidate.currentApiMajor) || !Array.isArray(candidate.supportedApiMajors) || candidate.supportedApiMajors.length === 0 || !candidate.supportedApiMajors.every(major) || new Set(candidate.supportedApiMajors).size !== candidate.supportedApiMajors.length || !candidate.supportedApiMajors.includes(candidate.currentApiMajor) || candidate.supportedApiMajors.some((version) => version > Number(candidate.currentApiMajor))) throw new Error("Invalid server compatibility response.");
793
+ return candidate;
794
+ }
795
+ function evaluateCompatibility(server, requiredMajor = 1) {
796
+ const supported = server.supportedApiMajors;
797
+ const status = supported.includes(requiredMajor) ? "compatible" : requiredMajor < Math.min(...supported) ? "client_too_old" : requiredMajor > server.currentApiMajor ? "server_too_old" : "unsupported";
798
+ return {
799
+ status,
800
+ requiredApiMajor: requiredMajor,
801
+ server,
802
+ message: {
803
+ compatible: `Compatible with API major ${requiredMajor}.`,
804
+ client_too_old: `This client is too old for this server. Update the client to use a supported API major.`,
805
+ server_too_old: `This server is too old for this client. Ask the server operator to update the server.`,
806
+ unsupported: `This server does not support API major ${requiredMajor}. Ask the operator to restore this contract or use a compatible client.`
807
+ }[status]
808
+ };
809
+ }
810
+ async function checkCompatibility(baseUrl, options = {}) {
811
+ let url;
812
+ try {
813
+ url = new URL(baseUrl);
814
+ } catch {
815
+ throw new Error("Server must be an absolute HTTP(S) URL.");
816
+ }
817
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("Server must be an HTTP(S) URL without credentials, query, or fragment.");
818
+ const timeout = AbortSignal.timeout(1e4);
819
+ const signal = options.signal ? AbortSignal.any([timeout, options.signal]) : timeout;
820
+ const { data, response, error } = await systemCompatibility({
821
+ baseUrl: url.href.replace(/\/$/u, ""),
822
+ signal,
823
+ throwOnError: false,
824
+ credentials: "omit",
825
+ cache: "no-store"
826
+ });
827
+ if (!response) throw error instanceof Error ? error : /* @__PURE__ */ new Error("Could not reach the server compatibility endpoint.");
828
+ if (!response.ok) throw new Error(`Server returned HTTP ${response.status} during compatibility discovery.`);
829
+ return evaluateCompatibility(validateCompatibility(data));
830
+ }
831
+ //#endregion
832
+ //#region src/credential-store.ts
833
+ /** Separate files bind secrets to an exact server origin, outside project directories. */
834
+ async function credentialStore(server, readOnly = false) {
835
+ const windows = process.platform === "win32";
836
+ const directory = process.env.PAGESHIP_CONFIG_DIR ?? join((windows ? process.env.LOCALAPPDATA : process.env.XDG_CONFIG_HOME) ?? join(homedir(), ".config"), "pageship");
837
+ if (!isAbsolute(directory)) throw new Error("PAGESHIP_CONFIG_DIR must be an absolute private directory.");
838
+ await mkdir(directory, {
839
+ recursive: true,
840
+ mode: 448
841
+ });
842
+ const info = await lstat(directory);
843
+ if (!info.isDirectory() || !windows && (info.uid !== process.getuid?.() || (info.mode & 63) !== 0)) throw new Error("Credential directory must be owned by you, not a symlink, and have mode 0700. Choose a private PAGESHIP_CONFIG_DIR.");
844
+ const name = createHash("sha256").update(server).digest("hex");
845
+ const path = join(directory, `${name}.json`);
846
+ const lock = join(directory, `${name}.lock`);
847
+ try {
848
+ if (!readOnly) await mkdir(lock, { mode: 448 });
849
+ } catch {
850
+ throw new Error(`Another authorization command may be running. Retry after it exits. If it was killed, remove ${lock} after checking no Pageship process is running.`);
851
+ }
852
+ return {
853
+ async read() {
854
+ if (windows) try {
855
+ if (!(await lstat(path)).isFile()) throw new Error("Credential file must not be a link.");
856
+ } catch (error) {
857
+ if (error.code !== "ENOENT") throw error;
858
+ }
859
+ let file;
860
+ try {
861
+ file = await open(path, constants.O_RDONLY | (windows ? 0 : constants.O_NOFOLLOW));
862
+ } catch (error) {
863
+ if (error.code === "ENOENT") return {};
864
+ throw new Error("Could not open credentials. Check the credential file is not a symlink.");
865
+ }
866
+ try {
867
+ const stat = await file.stat();
868
+ if (!stat.isFile() || !windows && (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0) || stat.nlink !== 1) throw new Error("Credential file must be owned by you and have mode 0600 with no links.");
869
+ const raw = await file.readFile("utf8");
870
+ let value;
871
+ try {
872
+ value = JSON.parse(raw);
873
+ } catch {
874
+ throw new Error("Could not read credential JSON. Choose a new private PAGESHIP_CONFIG_DIR and log in again.");
875
+ }
876
+ if (!value || typeof value !== "object") throw new Error("Invalid credential file. Start again with a new private PAGESHIP_CONFIG_DIR.");
877
+ const candidate = value;
878
+ if (candidate.token !== void 0 && typeof candidate.token !== "string") throw new Error("Invalid stored credential.");
879
+ const p = candidate.pending;
880
+ if (p && (typeof p.deviceCode !== "string" || typeof p.userCode !== "string" || typeof p.verificationUri !== "string" || typeof p.verificationUriComplete !== "string" || !Number.isFinite(p.expiresAt) || !Number.isFinite(p.nextPollAt) || !Number.isFinite(p.interval) || p.interval < 1)) throw new Error("Invalid stored authorization. Start again with a new private PAGESHIP_CONFIG_DIR.");
881
+ return candidate;
882
+ } finally {
883
+ await file.close();
884
+ }
885
+ },
886
+ async write(value) {
887
+ if (readOnly) throw new Error("Cannot update credentials through a read-only store.");
888
+ const temporary = join(directory, `${name}.${randomUUID()}.tmp`);
889
+ try {
890
+ const file = await open(temporary, "wx", 384);
891
+ try {
892
+ await file.writeFile(JSON.stringify(value));
893
+ await file.sync();
894
+ } finally {
895
+ await file.close();
896
+ }
897
+ await rename(temporary, path);
898
+ } finally {
899
+ await rm(temporary, { force: true });
900
+ }
901
+ },
902
+ close: async () => {
903
+ if (!readOnly) await rm(lock, {
904
+ recursive: true,
905
+ force: true
906
+ });
907
+ }
908
+ };
909
+ }
910
+ //#endregion
911
+ //#region src/authorization.ts
912
+ function serverOrigin(value) {
913
+ const url = new URL(value);
914
+ if (url.username || url.password || url.search || url.hash || url.pathname !== "/" || !(url.protocol === "https:" || url.protocol === "http:" && [
915
+ "localhost",
916
+ "127.0.0.1",
917
+ "[::1]"
918
+ ].includes(url.hostname))) throw new Error("Authorization requires an HTTPS server origin, or HTTP on localhost for local development. Do not include a path, credentials, query or fragment.");
919
+ return url.origin;
920
+ }
921
+ async function authRequest(server, path, body, signal) {
922
+ const response = await fetch(`${server}/api/auth${path}`, {
923
+ method: "POST",
924
+ headers: { "Content-Type": "application/json" },
925
+ body: JSON.stringify(body),
926
+ redirect: "error",
927
+ credentials: "omit",
928
+ cache: "no-store",
929
+ signal: AbortSignal.any([signal, AbortSignal.timeout(1e4)])
930
+ });
931
+ if (response.status >= 500) throw new Error(`Authorization server returned HTTP ${response.status}. Retry later with login --complete if you have a pending code.`);
932
+ let value;
933
+ try {
934
+ value = await response.json();
935
+ } catch {
936
+ throw new Error(`Authorization server returned an unreadable response (HTTP ${response.status}). Check the server and retry.`);
937
+ }
938
+ if (!value || typeof value !== "object") throw new Error("Invalid authorization response. Check the server and retry.");
939
+ const data = value;
940
+ if (!response.ok && typeof data.error !== "string") throw new Error(`Authorization server returned HTTP ${response.status}. Retry later.`);
941
+ return data;
942
+ }
943
+ function pendingResult(pending, server) {
944
+ return {
945
+ status: "authorization_pending",
946
+ message: "Approve this installation in your browser, then run the completion command.",
947
+ verificationUri: pending.verificationUri,
948
+ verificationUriComplete: pending.verificationUriComplete,
949
+ userCode: pending.userCode,
950
+ expiresAt: new Date(pending.expiresAt).toISOString(),
951
+ retryAfterSeconds: Math.max(0, Math.ceil((pending.nextPollAt - Date.now()) / 1e3)),
952
+ next: {
953
+ command: "login",
954
+ args: [
955
+ "--server",
956
+ server,
957
+ "--complete"
958
+ ]
959
+ }
960
+ };
961
+ }
962
+ async function authorize(options) {
963
+ const server = serverOrigin(options.server);
964
+ const compatibility = await checkCompatibility(server, { signal: options.signal });
965
+ if (compatibility.status !== "compatible") return compatibility;
966
+ const store = await credentialStore(server);
967
+ try {
968
+ const credentials = await store.read();
969
+ if (credentials.token && !options.restart && !credentials.pending) {
970
+ const access = await getWorkspace(server, {
971
+ token: credentials.token,
972
+ signal: options.signal
973
+ });
974
+ if (access.status === "admitted") return {
975
+ ...access,
976
+ status: "authorized",
977
+ message: `Authorized as ${access.user.name}.`
978
+ };
979
+ if (access.status === "uninvited") return {
980
+ status: "uninvited",
981
+ message: "This account is no longer invited. Contact the server owner or use login --restart for another account."
982
+ };
983
+ delete credentials.token;
984
+ await store.write(credentials);
985
+ }
986
+ if (options.restart) delete credentials.pending;
987
+ if (!credentials.pending) {
988
+ if (options.complete) return {
989
+ status: "invalid_attempt",
990
+ message: "No pending authorization. Run login without --complete to start a new attempt."
991
+ };
992
+ const data = await authRequest(server, "/device/code", { client_id: "pageship-cli" }, options.signal);
993
+ if (typeof data.device_code !== "string" || typeof data.user_code !== "string" || typeof data.verification_uri !== "string" || typeof data.verification_uri_complete !== "string" || typeof data.expires_in !== "number" || data.expires_in <= 0 || typeof data.interval !== "number" || data.interval < 1) throw new Error("Invalid device authorization response. Check the server and retry.");
994
+ for (const uri of [data.verification_uri, data.verification_uri_complete]) {
995
+ const parsed = new URL(uri);
996
+ if (!["https:", "http:"].includes(parsed.protocol) || parsed.username || parsed.password || parsed.protocol === "http:" && new URL(server).protocol !== "http:") throw new Error("Invalid browser verification URL.");
997
+ }
998
+ credentials.pending = {
999
+ deviceCode: data.device_code,
1000
+ userCode: data.user_code,
1001
+ verificationUri: data.verification_uri,
1002
+ verificationUriComplete: data.verification_uri_complete,
1003
+ expiresAt: Date.now() + data.expires_in * 1e3,
1004
+ interval: data.interval,
1005
+ nextPollAt: 0
1006
+ };
1007
+ await store.write(credentials);
1008
+ }
1009
+ const pending = credentials.pending;
1010
+ if (options.interactive) options.progress(`Open ${pending.verificationUriComplete}\nCheck code ${pending.userCode}. Only approve an installation you started.\nWaiting for approval. Press Ctrl+C to stop; you can resume with login --complete.`);
1011
+ do {
1012
+ if (options.signal.aborted) return {
1013
+ status: "interrupted",
1014
+ message: "Authorization interrupted. Run login --complete to resume, or login --restart for a new code."
1015
+ };
1016
+ if (Date.now() >= pending.expiresAt) {
1017
+ delete credentials.pending;
1018
+ await store.write(credentials);
1019
+ return {
1020
+ status: "expired",
1021
+ message: "The authorization code expired. Run login again for a new code."
1022
+ };
1023
+ }
1024
+ if (!options.interactive && (!options.complete || Date.now() < pending.nextPollAt)) return pendingResult(pending, server);
1025
+ if (Date.now() < pending.nextPollAt) await setTimeout$1(pending.nextPollAt - Date.now(), void 0, { signal: options.signal });
1026
+ pending.nextPollAt = Date.now() + pending.interval * 1e3;
1027
+ await store.write(credentials);
1028
+ const data = await authRequest(server, "/device/token", {
1029
+ client_id: "pageship-cli",
1030
+ device_code: pending.deviceCode,
1031
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
1032
+ }, options.signal);
1033
+ if (typeof data.access_token === "string" && data.access_token.length > 0) {
1034
+ credentials.token = data.access_token;
1035
+ delete credentials.pending;
1036
+ await store.write(credentials);
1037
+ const access = await getWorkspace(server, {
1038
+ token: credentials.token,
1039
+ signal: options.signal
1040
+ });
1041
+ if (access.status !== "admitted") return {
1042
+ status: access.status,
1043
+ message: "Authorization finished, but workspace access is unavailable. Check your admission with the server owner."
1044
+ };
1045
+ return {
1046
+ ...access,
1047
+ status: "authorized",
1048
+ message: `Authorized as ${access.user.name}. Your credential is stored privately for this server.`
1049
+ };
1050
+ }
1051
+ if (data.error === "slow_down") {
1052
+ pending.interval += 5;
1053
+ pending.nextPollAt = Date.now() + pending.interval * 1e3;
1054
+ await store.write(credentials);
1055
+ } else if (data.error !== "authorization_pending") {
1056
+ delete credentials.pending;
1057
+ await store.write(credentials);
1058
+ const status = data.error === "access_denied" ? "denied" : data.error === "expired_token" ? "expired" : "invalid_attempt";
1059
+ return {
1060
+ status,
1061
+ message: `Authorization ${status.replaceAll("_", " ")}. Run login again for a new code.`
1062
+ };
1063
+ }
1064
+ if (!options.interactive) return pendingResult(pending, server);
1065
+ } while (!options.signal.aborted);
1066
+ return {
1067
+ status: "interrupted",
1068
+ message: "Authorization interrupted. Run login --complete to resume."
1069
+ };
1070
+ } catch (error) {
1071
+ if (options.signal.aborted) return {
1072
+ status: "interrupted",
1073
+ message: "Authorization interrupted. Run login --complete to resume or login --restart to start again."
1074
+ };
1075
+ throw error;
1076
+ } finally {
1077
+ await store.close();
1078
+ }
1079
+ }
1080
+ async function identify(serverValue, signal) {
1081
+ const server = serverOrigin(serverValue);
1082
+ const compatibility = await checkCompatibility(server, { signal });
1083
+ if (compatibility.status !== "compatible") return compatibility;
1084
+ const store = await credentialStore(server, true);
1085
+ try {
1086
+ const { token } = await store.read();
1087
+ if (!token) return {
1088
+ status: "signed_out",
1089
+ message: "This installation is not authorized. Run login --server with this server to begin."
1090
+ };
1091
+ const access = await getWorkspace(server, {
1092
+ token,
1093
+ signal
1094
+ });
1095
+ return {
1096
+ ...access,
1097
+ message: access.status === "admitted" ? `Signed in as ${access.user.name}. Personal workspace: ${access.workspace.id}${access.installation ? `\nCLI installation: ${access.installation.id}` : ""}` : access.status === "uninvited" ? "This account is not invited. Contact the server owner." : "Your credential was revoked, expired or is invalid. Run login --server with this server to sign in again."
1098
+ };
1099
+ } finally {
1100
+ await store.close();
1101
+ }
1102
+ }
1103
+ //#endregion
1104
+ //#region src/publish.ts
1105
+ const execute = promisify(execFile);
1106
+ const contained = (root, path) => {
1107
+ const part = relative(root, path);
1108
+ return part !== ".." && !part.startsWith(`..${sep}`) && !isAbsolute(part);
1109
+ };
1110
+ async function sourceMetadata(file) {
1111
+ const path = await realpath(resolve(file));
1112
+ const cwd = await realpath(process.cwd());
1113
+ let root = contained(cwd, path) ? cwd : dirname(path);
1114
+ let sourceContext = "personal";
1115
+ try {
1116
+ const git = async (...args) => (await execute("git", [
1117
+ "-C",
1118
+ dirname(path),
1119
+ ...args
1120
+ ], {
1121
+ timeout: 5e3,
1122
+ maxBuffer: 16384
1123
+ })).stdout.trim();
1124
+ const gitRoot = await realpath(await git("rev-parse", "--show-toplevel"));
1125
+ if (contained(gitRoot, path)) {
1126
+ root = gitRoot;
1127
+ const remote = await git("remote", "get-url", "origin").catch(() => "");
1128
+ const url = remote.includes("://") ? new URL(remote) : /^[^/]+@[^:]+:.+/u.test(remote) ? new URL(`ssh://${remote.replace(":", "/")}`) : void 0;
1129
+ if (url && [
1130
+ "https:",
1131
+ "http:",
1132
+ "ssh:",
1133
+ "git:"
1134
+ ].includes(url.protocol) && url.hostname) {
1135
+ const repository = `${url.hostname.toLowerCase()}${url.port ? `:${url.port}` : ""}/${url.pathname.replace(/^\/+|\/+$/gu, "").replace(/\.git$/u, "")}`;
1136
+ sourceContext = `git:${createHash("sha256").update(repository).digest("hex")}`;
1137
+ }
1138
+ }
1139
+ } catch {}
1140
+ const sourcePath = relative(root, path).split(sep).join("/");
1141
+ if (!/\.html?$/iu.test(sourcePath)) throw new Error("Publish one .html or .htm file.");
1142
+ return {
1143
+ path,
1144
+ sourcePath,
1145
+ sourceContext
1146
+ };
1147
+ }
1148
+ async function fileBytes(path, limit) {
1149
+ const file = await open(path, "r");
1150
+ try {
1151
+ const stat = await file.stat();
1152
+ if (!stat.isFile()) throw new Error("Publish one regular HTML file.");
1153
+ if (stat.size > limit) return;
1154
+ if (stat.size === 0) throw new Error("The HTML file is empty.");
1155
+ const chunks = [];
1156
+ let length = 0;
1157
+ while (length <= limit) {
1158
+ const chunk = Buffer.alloc(Math.min(65536, limit + 1 - length));
1159
+ const { bytesRead } = await file.read(chunk);
1160
+ if (!bytesRead) break;
1161
+ chunks.push(chunk.subarray(0, bytesRead));
1162
+ length += bytesRead;
1163
+ }
1164
+ return length > limit ? void 0 : Buffer.concat(chunks);
1165
+ } finally {
1166
+ await file.close();
1167
+ }
1168
+ }
1169
+ function invalidFile(error) {
1170
+ return {
1171
+ status: "invalid_file",
1172
+ message: `Could not read the HTML file. ${error instanceof Error ? error.message : "Check the file and try again."}`
1173
+ };
1174
+ }
1175
+ function actionArgs(action) {
1176
+ return action.action === "new" ? ["--new"] : action.action === "revise" ? [
1177
+ "--revise",
1178
+ action.documentId,
1179
+ "--expected-revision",
1180
+ action.expectedRevisionId
1181
+ ] : [];
1182
+ }
1183
+ function resolutions(file, server, candidates) {
1184
+ const command = "publish";
1185
+ const args = [
1186
+ file,
1187
+ "--server",
1188
+ server
1189
+ ];
1190
+ return {
1191
+ new: {
1192
+ command,
1193
+ args: [...args, "--new"]
1194
+ },
1195
+ revise: candidates.map((candidate) => ({
1196
+ documentId: candidate.id,
1197
+ url: candidate.url,
1198
+ command,
1199
+ args: [...args, ...actionArgs({
1200
+ action: "revise",
1201
+ documentId: candidate.id,
1202
+ expectedRevisionId: candidate.revisionId
1203
+ })]
1204
+ }))
1205
+ };
1206
+ }
1207
+ async function choosePublication(candidates, signal) {
1208
+ for (;;) {
1209
+ console.log("This source path matches published documents:");
1210
+ candidates.forEach((candidate, index) => console.log(`${index + 1}. ${candidate.sourcePath} (${candidate.id}) ${candidate.url}`));
1211
+ const input = createInterface({
1212
+ input: process.stdin,
1213
+ output: process.stdout
1214
+ });
1215
+ let selection;
1216
+ try {
1217
+ selection = (await input.question("o <number> to open, r <number> to revise, n for a separate document, or q to cancel: ", { signal })).trim();
1218
+ } finally {
1219
+ input.close();
1220
+ }
1221
+ if (selection === "q") return;
1222
+ if (selection === "n") return { action: "new" };
1223
+ const match = /^([or]) ([1-9][0-9]*)$/u.exec(selection);
1224
+ const candidate = match ? candidates[Number(match[2]) - 1] : void 0;
1225
+ if (!candidate) continue;
1226
+ if (match[1] === "r") return {
1227
+ action: "revise",
1228
+ documentId: candidate.id,
1229
+ expectedRevisionId: candidate.revisionId
1230
+ };
1231
+ try {
1232
+ const [program, args] = process.platform === "darwin" ? ["open", [candidate.url]] : process.platform === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", candidate.url]] : ["xdg-open", [candidate.url]];
1233
+ await execute(program, [...args], {
1234
+ timeout: 1e4,
1235
+ signal
1236
+ });
1237
+ console.log(`Opened ${candidate.url}. No document changed.`);
1238
+ } catch {
1239
+ if (signal.aborted) throw new Error("Inspection interrupted.");
1240
+ console.log(`Could not open the browser. Open ${candidate.url} to inspect it. No document changed.`);
1241
+ }
1242
+ }
1243
+ }
1244
+ async function publish(options) {
1245
+ if (options.newDocument && (options.revise || options.expectedRevision) || Boolean(options.revise) !== Boolean(options.expectedRevision)) return {
1246
+ status: "invalid_action",
1247
+ message: "Use --new alone, or --revise <documentId> together with --expected-revision <revisionId>."
1248
+ };
1249
+ let action = options.newDocument ? { action: "new" } : options.revise && options.expectedRevision ? {
1250
+ action: "revise",
1251
+ documentId: options.revise,
1252
+ expectedRevisionId: options.expectedRevision
1253
+ } : {};
1254
+ const server = serverOrigin(options.server);
1255
+ const idempotencyKey = options.idempotencyKey ?? randomUUID();
1256
+ let source;
1257
+ try {
1258
+ source = await sourceMetadata(options.file);
1259
+ } catch (error) {
1260
+ return invalidFile(error);
1261
+ }
1262
+ const compatibility = await checkCompatibility(server, { signal: options.signal });
1263
+ if (compatibility.status !== "compatible") return compatibility;
1264
+ const store = await credentialStore(server, true);
1265
+ try {
1266
+ const { token } = await store.read();
1267
+ if (!token) return {
1268
+ status: "signed_out",
1269
+ message: "This installation is not authorized. Run login first."
1270
+ };
1271
+ const request = {
1272
+ token,
1273
+ signal: options.signal
1274
+ };
1275
+ const { maxUploadBytes } = await getDocumentLimits(server, request);
1276
+ let bytes;
1277
+ try {
1278
+ bytes = await fileBytes(source.path, maxUploadBytes);
1279
+ } catch (error) {
1280
+ return invalidFile(error);
1281
+ }
1282
+ if (!bytes) return {
1283
+ status: "upload_too_large",
1284
+ maxUploadBytes,
1285
+ message: `The HTML file exceeds the ${maxUploadBytes}-byte limit. No HTML was uploaded.`
1286
+ };
1287
+ let address = await updateWorkspaceAddress(server, options.addressChoice ? {
1288
+ action: "select",
1289
+ choiceId: options.addressChoice
1290
+ } : { action: options.refreshAddresses ? "refresh" : "allocate" }, request);
1291
+ while (address.status === "choice_required" && options.interactive) {
1292
+ console.log("Choose your workspace address:");
1293
+ address.choices.forEach((choice, index) => console.log(`${index + 1}. ${choice.hostname}`));
1294
+ const input = createInterface({
1295
+ input: process.stdin,
1296
+ output: process.stdout
1297
+ });
1298
+ let selection;
1299
+ try {
1300
+ selection = (await input.question("Number, r to refresh, or q to cancel: ", { signal: options.signal })).trim();
1301
+ } finally {
1302
+ input.close();
1303
+ }
1304
+ if (selection === "q") return {
1305
+ status: "cancelled",
1306
+ message: "Publication cancelled."
1307
+ };
1308
+ const choice = address.choices[Number(selection) - 1];
1309
+ if (selection !== "r" && !choice) continue;
1310
+ address = await updateWorkspaceAddress(server, selection === "r" ? { action: "refresh" } : {
1311
+ action: "select",
1312
+ choiceId: choice.choiceId
1313
+ }, request);
1314
+ }
1315
+ if (address.status === "choice_required") return {
1316
+ ...address,
1317
+ message: `Choose a generated workspace address and repeat publish with --address-choice <choiceId>, or --refresh-addresses.\n${address.choices.map((choice) => `${choice.hostname}: ${choice.choiceId}`).join("\n")}`,
1318
+ next: {
1319
+ command: "publish",
1320
+ args: [
1321
+ options.file,
1322
+ "--server",
1323
+ server,
1324
+ "--address-choice",
1325
+ "<choiceId>"
1326
+ ]
1327
+ }
1328
+ };
1329
+ if (address.status !== "allocated") return {
1330
+ ...address,
1331
+ message: "Workspace address is unavailable. Sign in again or check admission."
1332
+ };
1333
+ const send = () => publishDocument(server, {
1334
+ bytes,
1335
+ sourcePath: source.sourcePath,
1336
+ sourceContext: source.sourceContext,
1337
+ idempotencyKey,
1338
+ ...action
1339
+ }, request);
1340
+ let result = await send();
1341
+ if (result.status === "source_collision" && options.interactive) {
1342
+ const selected = await choosePublication(result.candidates, options.signal);
1343
+ if (!selected) return {
1344
+ status: "cancelled",
1345
+ message: "Publication cancelled. No document changed."
1346
+ };
1347
+ action = selected;
1348
+ result = await send();
1349
+ }
1350
+ const message = result.status === "published" ? `Published ${result.document.visibility === "private" ? "privately" : "publicly"}: ${result.document.url}` : result.status === "source_collision" ? `Source path already published. No document changed.\n${result.candidates.map((candidate) => candidate.url).join("\n")}\nUse --new for a separate document, or --revise <documentId> --expected-revision <revisionId> from a candidate. Use --json for complete follow-up arguments.` : result.status === "revision_conflict" ? `The selected document changed. No revision was published. Inspect ${result.document.url} and select its current revision before trying again.` : result.status === "idempotency_conflict" ? "That idempotency key belongs to different content, source metadata or publication action. Reuse the original input or choose a new key." : result.status === "unavailable" ? "The selected document is unavailable for this source path. Run publish without action flags to find current candidates." : "Allocate your workspace address before publishing.";
1351
+ return {
1352
+ ...result,
1353
+ idempotencyKey,
1354
+ message,
1355
+ ...result.status === "source_collision" ? { resolutions: resolutions(options.file, server, result.candidates) } : {},
1356
+ ...result.status === "revision_conflict" ? { resolutions: resolutions(options.file, server, [result.document]) } : {}
1357
+ };
1358
+ } catch (error) {
1359
+ if (options.signal.aborted) return {
1360
+ status: "interrupted",
1361
+ idempotencyKey,
1362
+ message: `Publication interrupted. Retry with --idempotency-key ${idempotencyKey} ${actionArgs(action).join(" ")}.`,
1363
+ next: {
1364
+ command: "publish",
1365
+ args: [
1366
+ options.file,
1367
+ "--server",
1368
+ server,
1369
+ "--idempotency-key",
1370
+ idempotencyKey,
1371
+ ...actionArgs(action)
1372
+ ]
1373
+ }
1374
+ };
1375
+ if (error instanceof DocumentRequestError) return {
1376
+ status: error.status,
1377
+ message: error.message,
1378
+ idempotencyKey
1379
+ };
1380
+ return {
1381
+ status: "request_failed",
1382
+ idempotencyKey,
1383
+ message: `Publication did not finish. Retry with --idempotency-key ${idempotencyKey} ${actionArgs(action).join(" ")}. ${error instanceof Error ? error.message : "Check the server and file."}`,
1384
+ next: {
1385
+ command: "publish",
1386
+ args: [
1387
+ options.file,
1388
+ "--server",
1389
+ server,
1390
+ "--idempotency-key",
1391
+ idempotencyKey,
1392
+ ...actionArgs(action)
1393
+ ]
1394
+ }
1395
+ };
1396
+ } finally {
1397
+ await store.close();
1398
+ }
1399
+ }
1400
+ //#endregion
5
1401
  //#region package.json
6
- var version = "0.0.1";
1402
+ var version = "0.1.1";
7
1403
  //#endregion
8
1404
  //#region src/cli.ts
9
- const command = Command.make("pageship").pipe(Command.withDescription("Pageship is a planned HTML publishing service.\n\nEarly development: this CLI currently provides help and version information only. Hosting, authentication, and publishing are not implemented."));
10
1405
  const args = process.argv.slice(2);
11
- Command.runWith(command, { version })(args.length === 0 ? ["--help"] : args).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
1406
+ const separator = args.indexOf("--");
1407
+ const flagArgs = args.slice(0, separator === -1 ? void 0 : separator);
1408
+ const parseBoolean = Schema.decodeUnknownOption(Config.Boolean);
1409
+ const booleanValue = (value) => Option.getOrUndefined(parseBoolean(value));
1410
+ const json = flagArgs.some((arg, index) => arg === "--json" ? booleanValue(flagArgs[index + 1]) !== false : arg.startsWith("--json=") && booleanValue(arg.slice(7)) !== false);
1411
+ const jsonFlag = GlobalFlag.setting("json")({ flag: Flag.boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Emit one JSON result, including errors.")) });
1412
+ let outcome;
1413
+ const plainFormatter = CliOutput.defaultFormatter({ colors: false });
1414
+ const jsonFormatter = {
1415
+ formatCliError: plainFormatter.formatCliError,
1416
+ formatError: plainFormatter.formatError,
1417
+ formatErrors: plainFormatter.formatErrors,
1418
+ formatHelpDoc: (doc) => {
1419
+ outcome = {
1420
+ ok: true,
1421
+ command: "help",
1422
+ usage: doc.usage,
1423
+ description: doc.description,
1424
+ help: plainFormatter.formatHelpDoc(doc)
1425
+ };
1426
+ return "";
1427
+ },
1428
+ formatVersion: (name, version) => {
1429
+ outcome = {
1430
+ ok: true,
1431
+ command: "version",
1432
+ name,
1433
+ version
1434
+ };
1435
+ return "";
1436
+ }
1437
+ };
1438
+ const status = Command.make("status", { server: Flag.string("server").pipe(Flag.withDescription("Pageship server HTTP(S) base URL, for example http://localhost:8787.")) }, ({ server }) => Effect.gen(function* () {
1439
+ const result = yield* Effect.tryPromise({
1440
+ try: () => checkCompatibility(server),
1441
+ catch: (error) => error instanceof Error ? error : new Error(String(error))
1442
+ });
1443
+ outcome = {
1444
+ ok: result.status === "compatible",
1445
+ command: "status",
1446
+ ...result
1447
+ };
1448
+ process.exitCode = result.status === "compatible" ? 0 : 2;
1449
+ if (!json) yield* Console.log(`${result.message}\nServer ${result.server.serverVersion}; current API major ${result.server.currentApiMajor}; supported API majors: ${result.server.supportedApiMajors.join(", ")}.\nThis CLI requires API major ${result.requiredApiMajor}.`);
1450
+ })).pipe(Command.withDescription("Check whether this CLI can use a Pageship server."));
1451
+ const serverFlag = Flag.string("server").pipe(Flag.withDescription("Pageship HTTPS origin, or localhost HTTP for development."));
1452
+ const login = Command.make("login", {
1453
+ server: serverFlag,
1454
+ complete: Flag.boolean("complete").pipe(Flag.withDefault(false), Flag.withDescription("Check a pending authorization once, without waiting.")),
1455
+ restart: Flag.boolean("restart").pipe(Flag.withDefault(false), Flag.withDescription("Start a new authorization code."))
1456
+ }, ({ server, complete, restart }) => authCommand("login", (signal) => authorize({
1457
+ server,
1458
+ complete,
1459
+ restart,
1460
+ interactive: !json && Boolean(process.stdin.isTTY && process.stdout.isTTY) && !complete,
1461
+ progress: (message) => console.log(message),
1462
+ signal
1463
+ }))).pipe(Command.withDescription("Authorize this installation through a browser."));
1464
+ const whoami = Command.make("whoami", { server: serverFlag }, ({ server }) => authCommand("whoami", (signal) => identify(server, signal))).pipe(Command.withDescription("Identify your personal workspace using the stored credential."));
1465
+ const publishCommand = Command.make("publish", {
1466
+ file: Argument.string("file"),
1467
+ server: serverFlag,
1468
+ idempotencyKey: Flag.string("idempotency-key").pipe(Flag.filter((value) => /^[A-Za-z0-9_-]{8,128}$/u.test(value), () => "Use an 8-128 character idempotency key containing letters, digits, underscores or hyphens."), Flag.optional),
1469
+ newDocument: Flag.boolean("new").pipe(Flag.withDefault(false), Flag.withDescription("Explicitly create a separate private document, even when the source matches.")),
1470
+ revise: Flag.string("revise").pipe(Flag.filter((value) => isDocumentId(value), () => "Use the six-character candidate document ID."), Flag.optional),
1471
+ expectedRevision: Flag.string("expected-revision").pipe(Flag.filter((value) => isRevisionId(value), () => "Use the candidate's current revision UUID."), Flag.optional),
1472
+ addressChoice: Flag.string("address-choice").pipe(Flag.optional),
1473
+ refreshAddresses: Flag.boolean("refresh-addresses").pipe(Flag.withDefault(false))
1474
+ }, ({ file, server, idempotencyKey, addressChoice, refreshAddresses, newDocument, revise, expectedRevision }) => authCommand("publish", (signal) => publish({
1475
+ file,
1476
+ server,
1477
+ newDocument,
1478
+ revise: Option.getOrUndefined(revise),
1479
+ expectedRevision: Option.getOrUndefined(expectedRevision),
1480
+ idempotencyKey: Option.getOrUndefined(idempotencyKey),
1481
+ addressChoice: Option.getOrUndefined(addressChoice),
1482
+ refreshAddresses,
1483
+ interactive: !json && Boolean(process.stdin.isTTY && process.stdout.isTTY),
1484
+ signal
1485
+ }))).pipe(Command.withDescription("Publish HTML, resolve source matches, or revise a selected document."));
1486
+ function authCommand(name, operation) {
1487
+ return Effect.tryPromise({
1488
+ try: async () => {
1489
+ const controller = new AbortController();
1490
+ const interrupt = () => controller.abort();
1491
+ process.once("SIGINT", interrupt);
1492
+ process.once("SIGTERM", interrupt);
1493
+ try {
1494
+ const result = await operation(controller.signal).catch((error) => {
1495
+ if (!controller.signal.aborted) throw error;
1496
+ return {
1497
+ status: "interrupted",
1498
+ message: "Operation interrupted. Run the command again to retry; use login --complete to resume a pending authorization."
1499
+ };
1500
+ });
1501
+ const ok = [
1502
+ "authorized",
1503
+ "admitted",
1504
+ "published"
1505
+ ].includes(result.status);
1506
+ outcome = {
1507
+ ok,
1508
+ command: name,
1509
+ ...result
1510
+ };
1511
+ process.exitCode = ok ? 0 : result.status === "interrupted" ? 130 : [
1512
+ "request_failed",
1513
+ "invalid_file",
1514
+ "invalid_action"
1515
+ ].includes(result.status) ? 1 : 2;
1516
+ if (!json) {
1517
+ console.log(result.message);
1518
+ const pending = result;
1519
+ if (typeof pending.verificationUriComplete === "string" && typeof pending.userCode === "string") console.log(`Open ${pending.verificationUriComplete}\nCheck code ${pending.userCode}.\nThen run: pageship login --server ${JSON.stringify(pending.next && pending.next.args[1])} --complete`);
1520
+ }
1521
+ } finally {
1522
+ process.removeListener("SIGINT", interrupt);
1523
+ process.removeListener("SIGTERM", interrupt);
1524
+ }
1525
+ },
1526
+ catch: (error) => error instanceof Error ? error : new Error(String(error))
1527
+ });
1528
+ }
1529
+ const command = Command.make("pageship").pipe(Command.withDescription("Pageship publishes private HTML documents.\n\nEarly development: check server API compatibility with the status command. Authorize this installation with login, then publish one HTML file with publish."), Command.withSubcommands([
1530
+ status,
1531
+ login,
1532
+ whoami,
1533
+ publishCommand
1534
+ ]), Command.withGlobalFlags([jsonFlag]));
1535
+ const effectiveArgs = args.every((arg, index) => arg === "--json" || arg.startsWith("--json=") && booleanValue(arg.slice(7)) !== void 0 || index > 0 && args[index - 1] === "--json" && booleanValue(arg) !== void 0) ? [...args, "--help"] : args;
1536
+ const program = Command.runWith(command, {
1537
+ version,
1538
+ renderErrors: !json
1539
+ })(effectiveArgs).pipe(Effect.catch((error) => Effect.sync(() => {
1540
+ process.exitCode = 1;
1541
+ const usage = CliError.isCliError(error);
1542
+ const message = usage && error._tag === "ShowHelp" ? error.errors.map((entry) => entry.message).join("\n") : error.message;
1543
+ outcome = {
1544
+ ok: false,
1545
+ error: {
1546
+ code: usage ? "usage_error" : "request_failed",
1547
+ message
1548
+ }
1549
+ };
1550
+ if (!json && !usage) console.error(`${flagArgs.includes("status") ? "Could not check server compatibility" : "Pageship request failed"}: ${message}`);
1551
+ if (!json && usage && error._tag !== "ShowHelp" && error._tag !== "UserError") console.error(message);
1552
+ })), Effect.catchCause((cause) => Effect.sync(() => {
1553
+ process.exitCode = 1;
1554
+ const message = Cause.pretty(cause);
1555
+ outcome = {
1556
+ ok: false,
1557
+ error: {
1558
+ code: "internal_error",
1559
+ message
1560
+ }
1561
+ };
1562
+ if (!json) console.error(message);
1563
+ })), Effect.provide(NodeServices.layer));
1564
+ await Effect.runPromise(json ? program.pipe(Effect.provideService(CliOutput.Formatter, jsonFormatter), Effect.provideService(Console.Console, {
1565
+ ...console,
1566
+ log: () => {},
1567
+ error: () => {}
1568
+ }), Effect.provide(CliConfig.layer({ builtIns: [GlobalFlag.Help, GlobalFlag.Version] }))) : program);
1569
+ if (json) console.log(JSON.stringify(outcome));
12
1570
  //#endregion
13
1571
  export {};