@geekmidas/cli 10.0.0-alpha.36 → 10.0.0-alpha.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{HostingerProvider-GF6NAZAK.mjs → HostingerProvider-Bh6K7QYz.mjs} +2 -2
- package/dist/{HostingerProvider-GF6NAZAK.mjs.map → HostingerProvider-Bh6K7QYz.mjs.map} +1 -1
- package/dist/{ssm-FxeSAt35.cjs → aws-DkHdTV-p.cjs} +7 -6
- package/dist/aws-DkHdTV-p.cjs.map +1 -0
- package/dist/{ssm-BKN1Cg4z.mjs → aws-DojzQnLd.mjs} +7 -6
- package/dist/aws-DojzQnLd.mjs.map +1 -0
- package/dist/{bundler-D9IPNvDt.mjs → bundler--fyybf1N.mjs} +13 -7
- package/dist/bundler--fyybf1N.mjs.map +1 -0
- package/dist/{bundler-CYF5mWi9.cjs → bundler-By6_sXiL.cjs} +12 -6
- package/dist/bundler-By6_sXiL.cjs.map +1 -0
- package/dist/{config-Dss1WwtH.mjs → config-D1S2r0JN.mjs} +93 -70
- package/dist/config-D1S2r0JN.mjs.map +1 -0
- package/dist/{config-DgAHy8nY.cjs → config-j4krJZVq.cjs} +114 -73
- package/dist/config-j4krJZVq.cjs.map +1 -0
- package/dist/config.cjs +6 -3
- package/dist/config.d.cts +40 -19
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +40 -19
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -3
- package/dist/{github-C99BMxf0.mjs → github-BPfmXtqk.mjs} +8 -11
- package/dist/github-BPfmXtqk.mjs.map +1 -0
- package/dist/{github-tZmMZ7td.cjs → github-BvK076ph.cjs} +10 -13
- package/dist/github-BvK076ph.cjs.map +1 -0
- package/dist/{index-DxXMtQNT.d.mts → index-CLH1DgsO.d.cts} +2 -2
- package/dist/{index-CAp22ul5.d.cts.map → index-CLH1DgsO.d.cts.map} +1 -1
- package/dist/{index-CAp22ul5.d.cts → index-CST2jOPH.d.mts} +2 -2
- package/dist/{index-DxXMtQNT.d.mts.map → index-CST2jOPH.d.mts.map} +1 -1
- package/dist/index.cjs +1071 -338
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1059 -326
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-CT8b3nt_.mjs → migrate-C2aYSZTn.mjs} +8 -6
- package/dist/migrate-C2aYSZTn.mjs.map +1 -0
- package/dist/{migrate-BsW4xBEO.cjs → migrate-DKn83skp.cjs} +7 -5
- package/dist/migrate-DKn83skp.cjs.map +1 -0
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{ready-D1BCiaYU.mjs → ready-BTm_sMNU.mjs} +90 -6
- package/dist/ready-BTm_sMNU.mjs.map +1 -0
- package/dist/{ready-CXhoutix.cjs → ready-DxC0Z3No.cjs} +94 -4
- package/dist/ready-DxC0Z3No.cjs.map +1 -0
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/reconcile--I7V4Klj.mjs +34 -0
- package/dist/reconcile--I7V4Klj.mjs.map +1 -0
- package/dist/reconcile-CuCQBa5-.cjs +34 -0
- package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
- package/dist/storage-BzaUxmqe.mjs +124 -0
- package/dist/storage-BzaUxmqe.mjs.map +1 -0
- package/dist/storage-CSGoC3bK.cjs +152 -0
- package/dist/storage-CSGoC3bK.cjs.map +1 -0
- package/dist/store-DQgxT_WR.cjs +211 -0
- package/dist/store-DQgxT_WR.cjs.map +1 -0
- package/dist/store-DeGATCS-.mjs +182 -0
- package/dist/store-DeGATCS-.mjs.map +1 -0
- package/dist/{types-BTiu5kwo.d.mts → types-B0EVAF6U.d.mts} +24 -10
- package/dist/types-B0EVAF6U.d.mts.map +1 -0
- package/dist/{types-Bj_FpeTc.d.cts → types-CHKCUKM0.d.cts} +24 -10
- package/dist/types-CHKCUKM0.d.cts.map +1 -0
- package/dist/vitest.cjs +8 -4
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +5 -4
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.mts +5 -4
- package/dist/vitest.d.mts.map +1 -1
- package/dist/vitest.mjs +8 -4
- package/dist/vitest.mjs.map +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-CsL76tpc.mjs → workspace-DOmIhiau.mjs} +2 -2
- package/dist/{workspace-CsL76tpc.mjs.map → workspace-DOmIhiau.mjs.map} +1 -1
- package/dist/{workspace-EmXwO2qM.mjs → workspace-Lm6dv2nz.mjs} +121 -40
- package/dist/workspace-Lm6dv2nz.mjs.map +1 -0
- package/dist/{workspace-CAJk6UmC.cjs → workspace-RYSYtHP7.cjs} +2 -2
- package/dist/{workspace-CAJk6UmC.cjs.map → workspace-RYSYtHP7.cjs.map} +1 -1
- package/dist/{workspace-C0X8xSXV.cjs → workspace-gfIrSEUp.cjs} +155 -38
- package/dist/workspace-gfIrSEUp.cjs.map +1 -0
- package/package.json +14 -14
- package/dist/bundler-CYF5mWi9.cjs.map +0 -1
- package/dist/bundler-D9IPNvDt.mjs.map +0 -1
- package/dist/config-DgAHy8nY.cjs.map +0 -1
- package/dist/config-Dss1WwtH.mjs.map +0 -1
- package/dist/github-C99BMxf0.mjs.map +0 -1
- package/dist/github-tZmMZ7td.cjs.map +0 -1
- package/dist/migrate-BsW4xBEO.cjs.map +0 -1
- package/dist/migrate-CT8b3nt_.mjs.map +0 -1
- package/dist/ready-CXhoutix.cjs.map +0 -1
- package/dist/ready-D1BCiaYU.mjs.map +0 -1
- package/dist/reconcile-BxZTV2vl.cjs +0 -332
- package/dist/reconcile-BxZTV2vl.cjs.map +0 -1
- package/dist/reconcile-yNq6Shau.mjs +0 -279
- package/dist/reconcile-yNq6Shau.mjs.map +0 -1
- package/dist/ssm-BKN1Cg4z.mjs.map +0 -1
- package/dist/ssm-FxeSAt35.cjs.map +0 -1
- package/dist/storage-BjTLjDDp.mjs +0 -297
- package/dist/storage-BjTLjDDp.mjs.map +0 -1
- package/dist/storage-C2JC31V9.cjs +0 -344
- package/dist/storage-C2JC31V9.cjs.map +0 -1
- package/dist/types-BTiu5kwo.d.mts.map +0 -1
- package/dist/types-Bj_FpeTc.d.cts.map +0 -1
- package/dist/workspace-C0X8xSXV.cjs.map +0 -1
- package/dist/workspace-EmXwO2qM.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
4
4
|
const require_discover = require("./discover-CroTIm0K.cjs");
|
|
5
|
-
const require_config = require("./config-
|
|
6
|
-
const require_workspace = require("./workspace-
|
|
7
|
-
const require_storage = require("./storage-
|
|
8
|
-
const
|
|
5
|
+
const require_config = require("./config-j4krJZVq.cjs");
|
|
6
|
+
const require_workspace = require("./workspace-RYSYtHP7.cjs");
|
|
7
|
+
const require_storage = require("./storage-CSGoC3bK.cjs");
|
|
8
|
+
const require_store = require("./store-DQgxT_WR.cjs");
|
|
9
|
+
const require_workspace$1 = require("./workspace-gfIrSEUp.cjs");
|
|
9
10
|
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-D1nhN98m.cjs");
|
|
10
11
|
const require_openapi = require("./openapi.cjs");
|
|
11
|
-
const require_ready = require("./ready-
|
|
12
|
+
const require_ready = require("./ready-DxC0Z3No.cjs");
|
|
12
13
|
const require_emit = require("./emit-BdoPFjZf.cjs");
|
|
13
|
-
const require_reconcile = require("./reconcile-BxZTV2vl.cjs");
|
|
14
14
|
let commander = require("commander");
|
|
15
15
|
let node_process = require("node:process");
|
|
16
16
|
let node_readline_promises = require("node:readline/promises");
|
|
@@ -35,6 +35,11 @@ let node_util = require("node:util");
|
|
|
35
35
|
let _geekmidas_cache_postgres = require("@geekmidas/cache/postgres");
|
|
36
36
|
let prompts = require("prompts");
|
|
37
37
|
prompts = require_rolldown_runtime.__toESM(prompts, 1);
|
|
38
|
+
let http = require("http");
|
|
39
|
+
let http2 = require("http2");
|
|
40
|
+
let stream = require("stream");
|
|
41
|
+
let crypto = require("crypto");
|
|
42
|
+
crypto = require_rolldown_runtime.__toESM(crypto, 1);
|
|
38
43
|
let _geekmidas_constructs_crons = require("@geekmidas/constructs/crons");
|
|
39
44
|
let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
|
|
40
45
|
let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
|
|
@@ -42,7 +47,7 @@ let _geekmidas_constructs_subscribers = require("@geekmidas/constructs/subscribe
|
|
|
42
47
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
43
48
|
let node_dns_promises = require("node:dns/promises");
|
|
44
49
|
//#region package.json
|
|
45
|
-
var version = "10.0.0-alpha.
|
|
50
|
+
var version = "10.0.0-alpha.38";
|
|
46
51
|
//#endregion
|
|
47
52
|
//#region src/auth/credentials.ts
|
|
48
53
|
/**
|
|
@@ -583,6 +588,485 @@ var UnsupportedPlatform = class extends Error {
|
|
|
583
588
|
}
|
|
584
589
|
};
|
|
585
590
|
//#endregion
|
|
591
|
+
//#region ../../node_modules/.pnpm/@hono+node-server@1.19.5_hono@4.13.8/node_modules/@hono/node-server/dist/index.mjs
|
|
592
|
+
var RequestError = class extends Error {
|
|
593
|
+
constructor(message, options) {
|
|
594
|
+
super(message, options);
|
|
595
|
+
this.name = "RequestError";
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
var toRequestError = (e) => {
|
|
599
|
+
if (e instanceof RequestError) return e;
|
|
600
|
+
return new RequestError(e.message, { cause: e });
|
|
601
|
+
};
|
|
602
|
+
var GlobalRequest = global.Request;
|
|
603
|
+
var Request = class extends GlobalRequest {
|
|
604
|
+
constructor(input, options) {
|
|
605
|
+
if (typeof input === "object" && getRequestCache in input) input = input[getRequestCache]();
|
|
606
|
+
if (typeof options?.body?.getReader !== "undefined") options.duplex ??= "half";
|
|
607
|
+
super(input, options);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
var newHeadersFromIncoming = (incoming) => {
|
|
611
|
+
const headerRecord = [];
|
|
612
|
+
const rawHeaders = incoming.rawHeaders;
|
|
613
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
614
|
+
const { [i]: key, [i + 1]: value } = rawHeaders;
|
|
615
|
+
if (key.charCodeAt(0) !== 58) headerRecord.push([key, value]);
|
|
616
|
+
}
|
|
617
|
+
return new Headers(headerRecord);
|
|
618
|
+
};
|
|
619
|
+
var wrapBodyStream = Symbol("wrapBodyStream");
|
|
620
|
+
var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
621
|
+
const init = {
|
|
622
|
+
method,
|
|
623
|
+
headers,
|
|
624
|
+
signal: abortController.signal
|
|
625
|
+
};
|
|
626
|
+
if (method === "TRACE") {
|
|
627
|
+
init.method = "GET";
|
|
628
|
+
const req = new Request(url, init);
|
|
629
|
+
Object.defineProperty(req, "method", { get() {
|
|
630
|
+
return "TRACE";
|
|
631
|
+
} });
|
|
632
|
+
return req;
|
|
633
|
+
}
|
|
634
|
+
if (!(method === "GET" || method === "HEAD")) {
|
|
635
|
+
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) init.body = new ReadableStream({ start(controller) {
|
|
636
|
+
controller.enqueue(incoming.rawBody);
|
|
637
|
+
controller.close();
|
|
638
|
+
} });
|
|
639
|
+
else if (incoming[wrapBodyStream]) {
|
|
640
|
+
let reader;
|
|
641
|
+
init.body = new ReadableStream({ async pull(controller) {
|
|
642
|
+
try {
|
|
643
|
+
reader ||= stream.Readable.toWeb(incoming).getReader();
|
|
644
|
+
const { done, value } = await reader.read();
|
|
645
|
+
if (done) controller.close();
|
|
646
|
+
else controller.enqueue(value);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
controller.error(error);
|
|
649
|
+
}
|
|
650
|
+
} });
|
|
651
|
+
} else init.body = stream.Readable.toWeb(incoming);
|
|
652
|
+
}
|
|
653
|
+
return new Request(url, init);
|
|
654
|
+
};
|
|
655
|
+
var getRequestCache = Symbol("getRequestCache");
|
|
656
|
+
var requestCache = Symbol("requestCache");
|
|
657
|
+
var incomingKey = Symbol("incomingKey");
|
|
658
|
+
var urlKey = Symbol("urlKey");
|
|
659
|
+
var headersKey = Symbol("headersKey");
|
|
660
|
+
var abortControllerKey = Symbol("abortControllerKey");
|
|
661
|
+
var requestPrototype = {
|
|
662
|
+
get method() {
|
|
663
|
+
return this[incomingKey].method || "GET";
|
|
664
|
+
},
|
|
665
|
+
get url() {
|
|
666
|
+
return this[urlKey];
|
|
667
|
+
},
|
|
668
|
+
get headers() {
|
|
669
|
+
return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
|
|
670
|
+
},
|
|
671
|
+
[Symbol("getAbortController")]() {
|
|
672
|
+
this[getRequestCache]();
|
|
673
|
+
return this[abortControllerKey];
|
|
674
|
+
},
|
|
675
|
+
[getRequestCache]() {
|
|
676
|
+
this[abortControllerKey] ||= new AbortController();
|
|
677
|
+
return this[requestCache] ||= newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], this[abortControllerKey]);
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
[
|
|
681
|
+
"body",
|
|
682
|
+
"bodyUsed",
|
|
683
|
+
"cache",
|
|
684
|
+
"credentials",
|
|
685
|
+
"destination",
|
|
686
|
+
"integrity",
|
|
687
|
+
"mode",
|
|
688
|
+
"redirect",
|
|
689
|
+
"referrer",
|
|
690
|
+
"referrerPolicy",
|
|
691
|
+
"signal",
|
|
692
|
+
"keepalive"
|
|
693
|
+
].forEach((k) => {
|
|
694
|
+
Object.defineProperty(requestPrototype, k, { get() {
|
|
695
|
+
return this[getRequestCache]()[k];
|
|
696
|
+
} });
|
|
697
|
+
});
|
|
698
|
+
[
|
|
699
|
+
"arrayBuffer",
|
|
700
|
+
"blob",
|
|
701
|
+
"clone",
|
|
702
|
+
"formData",
|
|
703
|
+
"json",
|
|
704
|
+
"text"
|
|
705
|
+
].forEach((k) => {
|
|
706
|
+
Object.defineProperty(requestPrototype, k, { value: function() {
|
|
707
|
+
return this[getRequestCache]()[k]();
|
|
708
|
+
} });
|
|
709
|
+
});
|
|
710
|
+
Object.setPrototypeOf(requestPrototype, Request.prototype);
|
|
711
|
+
var newRequest = (incoming, defaultHostname) => {
|
|
712
|
+
const req = Object.create(requestPrototype);
|
|
713
|
+
req[incomingKey] = incoming;
|
|
714
|
+
const incomingUrl = incoming.url || "";
|
|
715
|
+
if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
|
|
716
|
+
if (incoming instanceof http2.Http2ServerRequest) throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
|
|
717
|
+
try {
|
|
718
|
+
req[urlKey] = new URL(incomingUrl).href;
|
|
719
|
+
} catch (e) {
|
|
720
|
+
throw new RequestError("Invalid absolute URL", { cause: e });
|
|
721
|
+
}
|
|
722
|
+
return req;
|
|
723
|
+
}
|
|
724
|
+
const host = (incoming instanceof http2.Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
|
|
725
|
+
if (!host) throw new RequestError("Missing host header");
|
|
726
|
+
let scheme;
|
|
727
|
+
if (incoming instanceof http2.Http2ServerRequest) {
|
|
728
|
+
scheme = incoming.scheme;
|
|
729
|
+
if (!(scheme === "http" || scheme === "https")) throw new RequestError("Unsupported scheme");
|
|
730
|
+
} else scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
|
|
731
|
+
const url = new URL(`${scheme}://${host}${incomingUrl}`);
|
|
732
|
+
if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) throw new RequestError("Invalid host header");
|
|
733
|
+
req[urlKey] = url.href;
|
|
734
|
+
return req;
|
|
735
|
+
};
|
|
736
|
+
var responseCache = Symbol("responseCache");
|
|
737
|
+
var getResponseCache = Symbol("getResponseCache");
|
|
738
|
+
var cacheKey = Symbol("cache");
|
|
739
|
+
var GlobalResponse = global.Response;
|
|
740
|
+
var Response2 = class _Response {
|
|
741
|
+
#body;
|
|
742
|
+
#init;
|
|
743
|
+
[getResponseCache]() {
|
|
744
|
+
delete this[cacheKey];
|
|
745
|
+
return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
|
|
746
|
+
}
|
|
747
|
+
constructor(body, init) {
|
|
748
|
+
let headers;
|
|
749
|
+
this.#body = body;
|
|
750
|
+
if (init instanceof _Response) {
|
|
751
|
+
const cachedGlobalResponse = init[responseCache];
|
|
752
|
+
if (cachedGlobalResponse) {
|
|
753
|
+
this.#init = cachedGlobalResponse;
|
|
754
|
+
this[getResponseCache]();
|
|
755
|
+
return;
|
|
756
|
+
} else {
|
|
757
|
+
this.#init = init.#init;
|
|
758
|
+
headers = new Headers(init.#init.headers);
|
|
759
|
+
}
|
|
760
|
+
} else this.#init = init;
|
|
761
|
+
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
|
|
762
|
+
headers ||= init?.headers || { "content-type": "text/plain; charset=UTF-8" };
|
|
763
|
+
this[cacheKey] = [
|
|
764
|
+
init?.status || 200,
|
|
765
|
+
body,
|
|
766
|
+
headers
|
|
767
|
+
];
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
get headers() {
|
|
771
|
+
const cache = this[cacheKey];
|
|
772
|
+
if (cache) {
|
|
773
|
+
if (!(cache[2] instanceof Headers)) cache[2] = new Headers(cache[2]);
|
|
774
|
+
return cache[2];
|
|
775
|
+
}
|
|
776
|
+
return this[getResponseCache]().headers;
|
|
777
|
+
}
|
|
778
|
+
get status() {
|
|
779
|
+
return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
|
|
780
|
+
}
|
|
781
|
+
get ok() {
|
|
782
|
+
const status = this.status;
|
|
783
|
+
return status >= 200 && status < 300;
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
[
|
|
787
|
+
"body",
|
|
788
|
+
"bodyUsed",
|
|
789
|
+
"redirected",
|
|
790
|
+
"statusText",
|
|
791
|
+
"trailers",
|
|
792
|
+
"type",
|
|
793
|
+
"url"
|
|
794
|
+
].forEach((k) => {
|
|
795
|
+
Object.defineProperty(Response2.prototype, k, { get() {
|
|
796
|
+
return this[getResponseCache]()[k];
|
|
797
|
+
} });
|
|
798
|
+
});
|
|
799
|
+
[
|
|
800
|
+
"arrayBuffer",
|
|
801
|
+
"blob",
|
|
802
|
+
"clone",
|
|
803
|
+
"formData",
|
|
804
|
+
"json",
|
|
805
|
+
"text"
|
|
806
|
+
].forEach((k) => {
|
|
807
|
+
Object.defineProperty(Response2.prototype, k, { value: function() {
|
|
808
|
+
return this[getResponseCache]()[k]();
|
|
809
|
+
} });
|
|
810
|
+
});
|
|
811
|
+
Object.setPrototypeOf(Response2, GlobalResponse);
|
|
812
|
+
Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
|
|
813
|
+
async function readWithoutBlocking(readPromise) {
|
|
814
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
|
|
815
|
+
}
|
|
816
|
+
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
817
|
+
const cancel = (error) => {
|
|
818
|
+
reader.cancel(error).catch(() => {});
|
|
819
|
+
};
|
|
820
|
+
writable.on("close", cancel);
|
|
821
|
+
writable.on("error", cancel);
|
|
822
|
+
(currentReadPromise ?? reader.read()).then(flow, handleStreamError);
|
|
823
|
+
return reader.closed.finally(() => {
|
|
824
|
+
writable.off("close", cancel);
|
|
825
|
+
writable.off("error", cancel);
|
|
826
|
+
});
|
|
827
|
+
function handleStreamError(error) {
|
|
828
|
+
if (error) writable.destroy(error);
|
|
829
|
+
}
|
|
830
|
+
function onDrain() {
|
|
831
|
+
reader.read().then(flow, handleStreamError);
|
|
832
|
+
}
|
|
833
|
+
function flow({ done, value }) {
|
|
834
|
+
try {
|
|
835
|
+
if (done) writable.end();
|
|
836
|
+
else if (!writable.write(value)) writable.once("drain", onDrain);
|
|
837
|
+
else return reader.read().then(flow, handleStreamError);
|
|
838
|
+
} catch (e) {
|
|
839
|
+
handleStreamError(e);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function writeFromReadableStream(stream$1, writable) {
|
|
844
|
+
if (stream$1.locked) throw new TypeError("ReadableStream is locked.");
|
|
845
|
+
else if (writable.destroyed) return;
|
|
846
|
+
return writeFromReadableStreamDefaultReader(stream$1.getReader(), writable);
|
|
847
|
+
}
|
|
848
|
+
var buildOutgoingHttpHeaders = (headers) => {
|
|
849
|
+
const res = {};
|
|
850
|
+
if (!(headers instanceof Headers)) headers = new Headers(headers ?? void 0);
|
|
851
|
+
const cookies = [];
|
|
852
|
+
for (const [k, v] of headers) if (k === "set-cookie") cookies.push(v);
|
|
853
|
+
else res[k] = v;
|
|
854
|
+
if (cookies.length > 0) res["set-cookie"] = cookies;
|
|
855
|
+
res["content-type"] ??= "text/plain; charset=UTF-8";
|
|
856
|
+
return res;
|
|
857
|
+
};
|
|
858
|
+
var X_ALREADY_SENT = "x-hono-already-sent";
|
|
859
|
+
var webFetch = global.fetch;
|
|
860
|
+
if (typeof global.crypto === "undefined") global.crypto = crypto.default;
|
|
861
|
+
global.fetch = (info, init) => {
|
|
862
|
+
init = {
|
|
863
|
+
compress: false,
|
|
864
|
+
...init
|
|
865
|
+
};
|
|
866
|
+
return webFetch(info, init);
|
|
867
|
+
};
|
|
868
|
+
var outgoingEnded = Symbol("outgoingEnded");
|
|
869
|
+
var handleRequestError = () => new Response(null, { status: 400 });
|
|
870
|
+
var handleFetchError = (e) => new Response(null, { status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500 });
|
|
871
|
+
var handleResponseError = (e, outgoing) => {
|
|
872
|
+
const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
|
|
873
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") console.info("The user aborted a request.");
|
|
874
|
+
else {
|
|
875
|
+
console.error(e);
|
|
876
|
+
if (!outgoing.headersSent) outgoing.writeHead(500, { "Content-Type": "text/plain" });
|
|
877
|
+
outgoing.end(`Error: ${err.message}`);
|
|
878
|
+
outgoing.destroy(err);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
var flushHeaders = (outgoing) => {
|
|
882
|
+
if ("flushHeaders" in outgoing && outgoing.writable) outgoing.flushHeaders();
|
|
883
|
+
};
|
|
884
|
+
var responseViaCache = async (res, outgoing) => {
|
|
885
|
+
let [status, body, header] = res[cacheKey];
|
|
886
|
+
if (header instanceof Headers) header = buildOutgoingHttpHeaders(header);
|
|
887
|
+
if (typeof body === "string") header["Content-Length"] = Buffer.byteLength(body);
|
|
888
|
+
else if (body instanceof Uint8Array) header["Content-Length"] = body.byteLength;
|
|
889
|
+
else if (body instanceof Blob) header["Content-Length"] = body.size;
|
|
890
|
+
outgoing.writeHead(status, header);
|
|
891
|
+
if (typeof body === "string" || body instanceof Uint8Array) outgoing.end(body);
|
|
892
|
+
else if (body instanceof Blob) outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
893
|
+
else {
|
|
894
|
+
flushHeaders(outgoing);
|
|
895
|
+
await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
|
|
896
|
+
}
|
|
897
|
+
outgoing[outgoingEnded]?.();
|
|
898
|
+
};
|
|
899
|
+
var isPromise = (res) => typeof res.then === "function";
|
|
900
|
+
var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
901
|
+
if (isPromise(res)) {
|
|
902
|
+
if (options.errorHandler) try {
|
|
903
|
+
res = await res;
|
|
904
|
+
} catch (err) {
|
|
905
|
+
const errRes = await options.errorHandler(err);
|
|
906
|
+
if (!errRes) return;
|
|
907
|
+
res = errRes;
|
|
908
|
+
}
|
|
909
|
+
else res = await res.catch(handleFetchError);
|
|
910
|
+
}
|
|
911
|
+
if (cacheKey in res) return responseViaCache(res, outgoing);
|
|
912
|
+
const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
|
|
913
|
+
if (res.body) {
|
|
914
|
+
const reader = res.body.getReader();
|
|
915
|
+
const values = [];
|
|
916
|
+
let done = false;
|
|
917
|
+
let currentReadPromise = void 0;
|
|
918
|
+
if (resHeaderRecord["transfer-encoding"] !== "chunked") {
|
|
919
|
+
let maxReadCount = 2;
|
|
920
|
+
for (let i = 0; i < maxReadCount; i++) {
|
|
921
|
+
currentReadPromise ||= reader.read();
|
|
922
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
923
|
+
console.error(e);
|
|
924
|
+
done = true;
|
|
925
|
+
});
|
|
926
|
+
if (!chunk) {
|
|
927
|
+
if (i === 1) {
|
|
928
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
929
|
+
maxReadCount = 3;
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
currentReadPromise = void 0;
|
|
935
|
+
if (chunk.value) values.push(chunk.value);
|
|
936
|
+
if (chunk.done) {
|
|
937
|
+
done = true;
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (done && !("content-length" in resHeaderRecord)) resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
|
|
942
|
+
}
|
|
943
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
944
|
+
values.forEach((value) => {
|
|
945
|
+
outgoing.write(value);
|
|
946
|
+
});
|
|
947
|
+
if (done) outgoing.end();
|
|
948
|
+
else {
|
|
949
|
+
if (values.length === 0) flushHeaders(outgoing);
|
|
950
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
|
|
951
|
+
}
|
|
952
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
|
|
953
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
954
|
+
outgoing.end();
|
|
955
|
+
}
|
|
956
|
+
outgoing[outgoingEnded]?.();
|
|
957
|
+
};
|
|
958
|
+
var getRequestListener = (fetchCallback, options = {}) => {
|
|
959
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
960
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request) {
|
|
961
|
+
Object.defineProperty(global, "Request", { value: Request });
|
|
962
|
+
Object.defineProperty(global, "Response", { value: Response2 });
|
|
963
|
+
}
|
|
964
|
+
return async (incoming, outgoing) => {
|
|
965
|
+
let res, req;
|
|
966
|
+
try {
|
|
967
|
+
req = newRequest(incoming, options.hostname);
|
|
968
|
+
let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
|
|
969
|
+
if (!incomingEnded) {
|
|
970
|
+
incoming[wrapBodyStream] = true;
|
|
971
|
+
incoming.on("end", () => {
|
|
972
|
+
incomingEnded = true;
|
|
973
|
+
});
|
|
974
|
+
if (incoming instanceof http2.Http2ServerRequest) outgoing[outgoingEnded] = () => {
|
|
975
|
+
if (!incomingEnded) setTimeout(() => {
|
|
976
|
+
if (!incomingEnded) setTimeout(() => {
|
|
977
|
+
incoming.destroy();
|
|
978
|
+
outgoing.destroy();
|
|
979
|
+
});
|
|
980
|
+
});
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
outgoing.on("close", () => {
|
|
984
|
+
if (req[abortControllerKey]) {
|
|
985
|
+
if (incoming.errored) req[abortControllerKey].abort(incoming.errored.toString());
|
|
986
|
+
else if (!outgoing.writableFinished) req[abortControllerKey].abort("Client connection prematurely closed.");
|
|
987
|
+
}
|
|
988
|
+
if (!incomingEnded) setTimeout(() => {
|
|
989
|
+
if (!incomingEnded) setTimeout(() => {
|
|
990
|
+
incoming.destroy();
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
});
|
|
994
|
+
res = fetchCallback(req, {
|
|
995
|
+
incoming,
|
|
996
|
+
outgoing
|
|
997
|
+
});
|
|
998
|
+
if (cacheKey in res) return responseViaCache(res, outgoing);
|
|
999
|
+
} catch (e) {
|
|
1000
|
+
if (!res) {
|
|
1001
|
+
if (options.errorHandler) {
|
|
1002
|
+
res = await options.errorHandler(req ? e : toRequestError(e));
|
|
1003
|
+
if (!res) return;
|
|
1004
|
+
} else if (!req) res = handleRequestError();
|
|
1005
|
+
else res = handleFetchError(e);
|
|
1006
|
+
} else return handleResponseError(e, outgoing);
|
|
1007
|
+
}
|
|
1008
|
+
try {
|
|
1009
|
+
return await responseViaResponseObject(res, outgoing, options);
|
|
1010
|
+
} catch (e) {
|
|
1011
|
+
return handleResponseError(e, outgoing);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
};
|
|
1015
|
+
var createAdaptorServer = (options) => {
|
|
1016
|
+
const fetchCallback = options.fetch;
|
|
1017
|
+
const requestListener = getRequestListener(fetchCallback, {
|
|
1018
|
+
hostname: options.hostname,
|
|
1019
|
+
overrideGlobalObjects: options.overrideGlobalObjects,
|
|
1020
|
+
autoCleanupIncoming: options.autoCleanupIncoming
|
|
1021
|
+
});
|
|
1022
|
+
return (options.createServer || http.createServer)(options.serverOptions || {}, requestListener);
|
|
1023
|
+
};
|
|
1024
|
+
var serve = (options, listeningListener) => {
|
|
1025
|
+
const server = createAdaptorServer(options);
|
|
1026
|
+
server.listen(options?.port ?? 3e3, options.hostname, () => {
|
|
1027
|
+
const serverInfo = server.address();
|
|
1028
|
+
listeningListener && listeningListener(serverInfo);
|
|
1029
|
+
});
|
|
1030
|
+
return server;
|
|
1031
|
+
};
|
|
1032
|
+
//#endregion
|
|
1033
|
+
//#region src/dev/fakes.ts
|
|
1034
|
+
/**
|
|
1035
|
+
* External APIs' app fakes, served for `gkm dev`.
|
|
1036
|
+
*
|
|
1037
|
+
* An image fake runs with the other containers. An app fake is a fetch handler
|
|
1038
|
+
* in `test/fakes/<id>.ts`, and nothing runs it unless this does: each one
|
|
1039
|
+
* listens on the port its key was allocated, which is the port the stage's
|
|
1040
|
+
* `<ID>_URL` already names.
|
|
1041
|
+
*/
|
|
1042
|
+
/**
|
|
1043
|
+
* Serve every app fake on its allocated port. Image fakes are skipped — their
|
|
1044
|
+
* containers are already up.
|
|
1045
|
+
*/
|
|
1046
|
+
async function serveFakes(fakes, ports) {
|
|
1047
|
+
const served = [];
|
|
1048
|
+
for (const [id, local] of Object.entries(fakes)) {
|
|
1049
|
+
if (local.image) continue;
|
|
1050
|
+
const port = ports[require_workspace$1.fakeKey(id)];
|
|
1051
|
+
if (port === void 0) continue;
|
|
1052
|
+
const { handler } = (await import((0, node_url.pathToFileURL)(local.file).href)).default;
|
|
1053
|
+
const server = serve({
|
|
1054
|
+
fetch: (request) => handler.fetch(request),
|
|
1055
|
+
port
|
|
1056
|
+
});
|
|
1057
|
+
served.push({
|
|
1058
|
+
id,
|
|
1059
|
+
port,
|
|
1060
|
+
server
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
return served;
|
|
1064
|
+
}
|
|
1065
|
+
/** Stop every served fake. */
|
|
1066
|
+
function closeFakes(served) {
|
|
1067
|
+
for (const { server } of served) server.close();
|
|
1068
|
+
}
|
|
1069
|
+
//#endregion
|
|
586
1070
|
//#region src/exec/index.ts
|
|
587
1071
|
const logger$11 = console;
|
|
588
1072
|
/** `gkm exec` was given nothing to run. */
|
|
@@ -734,41 +1218,28 @@ async function devCommand(options) {
|
|
|
734
1218
|
if (options.entry) return entryDevCommand(options);
|
|
735
1219
|
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
736
1220
|
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
} catch {
|
|
762
|
-
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
763
|
-
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
764
|
-
config = loadedConfig.raw;
|
|
765
|
-
workspace = loadedConfig.workspace;
|
|
766
|
-
}
|
|
767
|
-
else {
|
|
768
|
-
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
769
|
-
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
770
|
-
config = loadedConfig.raw;
|
|
771
|
-
workspace = loadedConfig.workspace;
|
|
1221
|
+
const appConfig = await require_config.loadAppConfig().catch((error) => {
|
|
1222
|
+
if (error instanceof require_config.NotInAnApp || error instanceof require_config.NotABackendApp) return;
|
|
1223
|
+
throw error;
|
|
1224
|
+
});
|
|
1225
|
+
if (!appConfig) {
|
|
1226
|
+
const { workspace: everything } = await require_config.loadWorkspaceConfig();
|
|
1227
|
+
return workspaceDevCommand(everything, options);
|
|
1228
|
+
}
|
|
1229
|
+
const config = appConfig.gkmConfig;
|
|
1230
|
+
const appRoot = appConfig.appRoot;
|
|
1231
|
+
const secretsRoot = appConfig.workspaceRoot;
|
|
1232
|
+
const workspaceAppName = appConfig.appName;
|
|
1233
|
+
const workspaceAppPort = appConfig.app.port;
|
|
1234
|
+
const workspace = appConfig.workspace;
|
|
1235
|
+
if (appConfig.app.entry) {
|
|
1236
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1237
|
+
return entryDevCommand({
|
|
1238
|
+
...options,
|
|
1239
|
+
entry: appConfig.app.entry,
|
|
1240
|
+
port: workspaceAppPort,
|
|
1241
|
+
portExplicit: true
|
|
1242
|
+
});
|
|
772
1243
|
}
|
|
773
1244
|
if (config.env) {
|
|
774
1245
|
const { loaded, missing } = require_workspace$1.loadEnvFiles(config.env, appRoot);
|
|
@@ -798,9 +1269,12 @@ async function devCommand(options) {
|
|
|
798
1269
|
const runtime = config.runtime ?? "node";
|
|
799
1270
|
let secretsJsonPath;
|
|
800
1271
|
let publicUrl;
|
|
801
|
-
const appSecrets = await require_workspace$1.loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
|
|
1272
|
+
const appSecrets = await require_workspace$1.loadSecretsForApp(workspace ? await require_store.secretsStoreFor(workspace, config.stages.local) : new require_store.FileSecretsStore(secretsRoot), config.stages.local, workspaceAppName);
|
|
802
1273
|
if (workspace) {
|
|
803
|
-
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, {
|
|
1274
|
+
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, {
|
|
1275
|
+
stage: workspace.stages.local,
|
|
1276
|
+
...options.fake ? { fake: true } : {}
|
|
1277
|
+
});
|
|
804
1278
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
805
1279
|
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
806
1280
|
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
@@ -821,7 +1295,7 @@ async function devCommand(options) {
|
|
|
821
1295
|
devServer.publicUrl = publicUrl;
|
|
822
1296
|
await devServer.start();
|
|
823
1297
|
const hooksFile = (config.hooks?.server?.split("#"))?.[0];
|
|
824
|
-
const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) =>
|
|
1298
|
+
const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) => (0, node_path.isAbsolute)(p) ? (0, node_path.relative)(appRoot, p) : p.replace(/^\.\//, ""));
|
|
825
1299
|
const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
|
|
826
1300
|
cwd: appRoot,
|
|
827
1301
|
absolute: false,
|
|
@@ -1045,10 +1519,8 @@ async function validateFrontendApps(workspace) {
|
|
|
1045
1519
|
async function loadDevSecrets(workspace) {
|
|
1046
1520
|
if (!workspace.secrets.enabled) return {};
|
|
1047
1521
|
const stage = workspace.stages.local;
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
if (secrets) return require_storage.toEmbeddableSecrets(secrets);
|
|
1051
|
-
}
|
|
1522
|
+
const secrets = await (await require_store.secretsStoreFor(workspace, stage)).read(stage);
|
|
1523
|
+
if (secrets) return require_storage.toEmbeddableSecrets(secrets);
|
|
1052
1524
|
return {};
|
|
1053
1525
|
}
|
|
1054
1526
|
/**
|
|
@@ -1082,7 +1554,10 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1082
1554
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1083
1555
|
}
|
|
1084
1556
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
1085
|
-
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, {
|
|
1557
|
+
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, {
|
|
1558
|
+
stage: workspace.stages.local,
|
|
1559
|
+
...options.fake ? { fake: true } : {}
|
|
1560
|
+
});
|
|
1086
1561
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
1087
1562
|
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
1088
1563
|
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
@@ -1091,10 +1566,12 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1091
1566
|
...rawSecrets,
|
|
1092
1567
|
...reconciled.env
|
|
1093
1568
|
};
|
|
1569
|
+
const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
|
|
1570
|
+
for (const { id, port } of fakes) logger$10.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
1094
1571
|
if (options.migrate || options.seed) {
|
|
1095
|
-
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-
|
|
1572
|
+
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-DKn83skp.cjs"));
|
|
1096
1573
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1097
|
-
} else await Promise.resolve().then(() => require("./migrate-
|
|
1574
|
+
} else await Promise.resolve().then(() => require("./migrate-DKn83skp.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1098
1575
|
logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1099
1576
|
});
|
|
1100
1577
|
const appUrls = {};
|
|
@@ -1153,7 +1630,8 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1153
1630
|
...process.env,
|
|
1154
1631
|
...secretsEnv,
|
|
1155
1632
|
NODE_ENV: "development",
|
|
1156
|
-
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1633
|
+
...configPath ? { GKM_CONFIG_PATH: configPath } : {},
|
|
1634
|
+
...options.fake ? { [require_workspace$1.FAKE_ENV]: "1" } : {}
|
|
1157
1635
|
};
|
|
1158
1636
|
const scoped = options.app ? [options.app] : options.filter ? [] : Object.keys(workspace.apps);
|
|
1159
1637
|
const held = [];
|
|
@@ -1182,6 +1660,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1182
1660
|
if (isShuttingDown) return;
|
|
1183
1661
|
isShuttingDown = true;
|
|
1184
1662
|
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1663
|
+
closeFakes(fakes);
|
|
1185
1664
|
const pid = turboProcess.pid;
|
|
1186
1665
|
if (pid) try {
|
|
1187
1666
|
process.kill(-pid, "SIGTERM");
|
|
@@ -2814,7 +3293,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2814
3293
|
let masterKey;
|
|
2815
3294
|
if (context.production?.bundle && !skipBundle) {
|
|
2816
3295
|
logger$8.log(`\n📦 Bundling production server...`);
|
|
2817
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
3296
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-By6_sXiL.cjs"));
|
|
2818
3297
|
const allConstructs = [
|
|
2819
3298
|
...endpoints.map((e) => e.construct),
|
|
2820
3299
|
...functions.map((f) => f.construct),
|
|
@@ -3298,17 +3777,43 @@ const PROVISIONERS = {
|
|
|
3298
3777
|
return broker(declaration.id, context);
|
|
3299
3778
|
},
|
|
3300
3779
|
/**
|
|
3301
|
-
* A signing key, generated once and remembered.
|
|
3780
|
+
* A signing key, generated once and remembered in the stage's store.
|
|
3302
3781
|
*
|
|
3303
|
-
*
|
|
3304
|
-
*
|
|
3305
|
-
*
|
|
3782
|
+
* Random, never derived: a key computed from the project and stage names
|
|
3783
|
+
* signs sessions anyone reading the repo could forge. The deploy generates
|
|
3784
|
+
* it before provisioning (`withGeneratedSecrets`) and every later deploy
|
|
3785
|
+
* reads the same one, so live sessions survive a redeploy. Nothing is
|
|
3786
|
+
* created in Dokploy: a secret has no address.
|
|
3306
3787
|
*/
|
|
3307
3788
|
secret: async (declaration, context) => {
|
|
3308
3789
|
if (declaration.kind !== "secret") throw new WrongKind(declaration.kind);
|
|
3309
3790
|
const key = declaration.provides?.[0];
|
|
3310
3791
|
if (!key) return { provides: {} };
|
|
3311
|
-
return { provides: { [key]:
|
|
3792
|
+
return { provides: { [key]: supplied(declaration.id, key, context) } };
|
|
3793
|
+
},
|
|
3794
|
+
/**
|
|
3795
|
+
* A third party's credentials, as the stage was given them.
|
|
3796
|
+
*
|
|
3797
|
+
* Nothing is created and nothing derived: the value was issued by somebody
|
|
3798
|
+
* else. A stage that was never given one fails the deploy here, naming the
|
|
3799
|
+
* command that sets it — not the first request that needs it.
|
|
3800
|
+
*/
|
|
3801
|
+
credential: async (declaration, context) => {
|
|
3802
|
+
if (declaration.kind !== "credential") throw new WrongKind(declaration.kind);
|
|
3803
|
+
const key = (0, _geekmidas_manifest.provideKey)(declaration.id, "credentials");
|
|
3804
|
+
return { provides: { [key]: supplied(declaration.id, key, context) } };
|
|
3805
|
+
},
|
|
3806
|
+
/**
|
|
3807
|
+
* An API somebody else runs: the URL this stage calls it at, and the
|
|
3808
|
+
* credentials it was issued for this stage.
|
|
3809
|
+
*/
|
|
3810
|
+
"external-api": async (declaration, context) => {
|
|
3811
|
+
if (declaration.kind !== "external-api") throw new WrongKind(declaration.kind);
|
|
3812
|
+
const credentials = (0, _geekmidas_manifest.provideKey)(declaration.id, "credentials");
|
|
3813
|
+
return { provides: {
|
|
3814
|
+
[(0, _geekmidas_manifest.provideKey)(declaration.id, "url")]: (0, _geekmidas_manifest.externalApiUrl)(declaration, context.stage),
|
|
3815
|
+
[credentials]: supplied(declaration.id, credentials, context)
|
|
3816
|
+
} };
|
|
3312
3817
|
},
|
|
3313
3818
|
/**
|
|
3314
3819
|
* A surface, at the address whatever created its domain assigned.
|
|
@@ -3495,14 +4000,16 @@ function soleDatabase(context) {
|
|
|
3495
4000
|
return databases[0];
|
|
3496
4001
|
}
|
|
3497
4002
|
/**
|
|
3498
|
-
* A password derived from the project, the stage and the role.
|
|
4003
|
+
* A password derived from the stage's seed, the project, the stage and the role.
|
|
3499
4004
|
*
|
|
3500
|
-
* Derived rather than
|
|
3501
|
-
*
|
|
3502
|
-
*
|
|
4005
|
+
* Derived rather than stored per role so a redeploy hands the running
|
|
4006
|
+
* application the password it already has, and two projects never share one.
|
|
4007
|
+
* The seed is what makes it secret: random, generated once and kept in the
|
|
4008
|
+
* stage's store. Without it the inputs are all in the repo, and so was every
|
|
4009
|
+
* production password.
|
|
3503
4010
|
*/
|
|
3504
4011
|
function derivedPassword(context, role) {
|
|
3505
|
-
return (0, node_crypto.createHash)("sha256").update(`${context.project}:${context.stage}:role:${role}`).digest("base64url").slice(0, 32);
|
|
4012
|
+
return (0, node_crypto.createHash)("sha256").update(`${context.seed}:${context.project}:${context.stage}:role:${role}`).digest("base64url").slice(0, 32);
|
|
3506
4013
|
}
|
|
3507
4014
|
/** A connection string for a role on a cluster. */
|
|
3508
4015
|
function postgresUrl(role, password, host, database) {
|
|
@@ -3513,6 +4020,29 @@ function quoted(name) {
|
|
|
3513
4020
|
return name.split(".").map((part) => `"${part.replace(/"/g, "\"\"")}"`).join(".");
|
|
3514
4021
|
}
|
|
3515
4022
|
/** A provisioner was handed a declaration of the wrong kind. */
|
|
4023
|
+
/**
|
|
4024
|
+
* A value only the stage's own secrets can hold.
|
|
4025
|
+
*
|
|
4026
|
+
* @throws {MissingSuppliedSecret} when the stage was never given it.
|
|
4027
|
+
*/
|
|
4028
|
+
function supplied(id, key, context) {
|
|
4029
|
+
const value = context.supplied?.[key];
|
|
4030
|
+
if (value === void 0) throw new MissingSuppliedSecret(id, key, context.stage);
|
|
4031
|
+
return value;
|
|
4032
|
+
}
|
|
4033
|
+
/** A construct needs a value the stage was never given. */
|
|
4034
|
+
var MissingSuppliedSecret = class extends Error {
|
|
4035
|
+
id;
|
|
4036
|
+
key;
|
|
4037
|
+
stage;
|
|
4038
|
+
constructor(id, key, stage) {
|
|
4039
|
+
super(`'${id}' needs ${key}, and the stage '${stage}' has none. Nothing can derive it; set it in the stage's secrets: gkm secrets:set ${key} '…' --stage ${stage}`);
|
|
4040
|
+
this.id = id;
|
|
4041
|
+
this.key = key;
|
|
4042
|
+
this.stage = stage;
|
|
4043
|
+
this.name = "MissingSuppliedSecret";
|
|
4044
|
+
}
|
|
4045
|
+
};
|
|
3516
4046
|
var WrongKind = class extends Error {
|
|
3517
4047
|
kind;
|
|
3518
4048
|
constructor(kind) {
|
|
@@ -3635,7 +4165,8 @@ async function provisionDeclared(options) {
|
|
|
3635
4165
|
events: require_workspace$1.eventsBackendFor(require_workspace$1.providerOf(workspace)),
|
|
3636
4166
|
storage: require_workspace$1.storageBackendFor(require_workspace$1.providerOf(workspace)),
|
|
3637
4167
|
addresses: surfaceAddresses(workspace, manifest, options.appUrls),
|
|
3638
|
-
|
|
4168
|
+
seed: options.seed,
|
|
4169
|
+
...options.supplied ? { supplied: options.supplied } : {},
|
|
3639
4170
|
deferred: [],
|
|
3640
4171
|
clusters: {}
|
|
3641
4172
|
};
|
|
@@ -4665,14 +5196,9 @@ exec "$@"
|
|
|
4665
5196
|
*/
|
|
4666
5197
|
function resolveDockerConfig(config) {
|
|
4667
5198
|
const docker = config.docker ?? {};
|
|
4668
|
-
let defaultImageName = "api";
|
|
4669
|
-
try {
|
|
4670
|
-
const pkg = require(`${process.cwd()}/package.json`);
|
|
4671
|
-
if (pkg.name) defaultImageName = pkg.name.replace(/^@[^/]+\//, "");
|
|
4672
|
-
} catch {}
|
|
4673
5199
|
return {
|
|
4674
5200
|
registry: docker.registry ?? "",
|
|
4675
|
-
imageName: docker.imageName ??
|
|
5201
|
+
imageName: docker.imageName ?? config.name ?? "api",
|
|
4676
5202
|
baseImage: docker.baseImage ?? "node:22-alpine",
|
|
4677
5203
|
port: docker.port ?? 3e3
|
|
4678
5204
|
};
|
|
@@ -6265,6 +6791,55 @@ function validateEnvVars(requiredVars, context) {
|
|
|
6265
6791
|
};
|
|
6266
6792
|
}
|
|
6267
6793
|
//#endregion
|
|
6794
|
+
//#region src/deploy/generated.ts
|
|
6795
|
+
/**
|
|
6796
|
+
* The values a deployed stage generates once and keeps: what nothing can
|
|
6797
|
+
* provision and nobody else issues.
|
|
6798
|
+
*
|
|
6799
|
+
* - **A seed**, which salts every password a deploy derives — a database's
|
|
6800
|
+
* master and roles, a bucket's root user. Derived from repo facts alone
|
|
6801
|
+
* (project, stage, construct id), those passwords were computable by anyone
|
|
6802
|
+
* who could read the repo.
|
|
6803
|
+
* - **Each `secret` construct's value** — an auth server's signing secret —
|
|
6804
|
+
* random, rather than derived.
|
|
6805
|
+
*
|
|
6806
|
+
* Both are generated on the first deploy that needs them and written to the
|
|
6807
|
+
* stage's store, so every later deploy reads the same values: sessions survive
|
|
6808
|
+
* a redeploy, and a role's password does not move. A value set by hand with
|
|
6809
|
+
* `gkm secrets:set` is kept, never replaced.
|
|
6810
|
+
*/
|
|
6811
|
+
/**
|
|
6812
|
+
* The stage's secrets with its seed and every declared `secret`'s value
|
|
6813
|
+
* present, generating what is missing. Pure: the caller writes the result
|
|
6814
|
+
* back to the stage's store when `generated` is not empty.
|
|
6815
|
+
*/
|
|
6816
|
+
function withGeneratedSecrets(secrets, manifest) {
|
|
6817
|
+
const generated = [];
|
|
6818
|
+
const custom = { ...secrets.custom };
|
|
6819
|
+
for (const declaration of Object.values(manifest)) {
|
|
6820
|
+
if (declaration?.kind !== "secret") continue;
|
|
6821
|
+
const key = declaration.provides?.[0];
|
|
6822
|
+
if (!key || custom[key]) continue;
|
|
6823
|
+
custom[key] = random();
|
|
6824
|
+
generated.push(key);
|
|
6825
|
+
}
|
|
6826
|
+
const seed = secrets.seed ?? random();
|
|
6827
|
+
if (!secrets.seed) generated.push("seed");
|
|
6828
|
+
return {
|
|
6829
|
+
secrets: generated.length ? {
|
|
6830
|
+
...secrets,
|
|
6831
|
+
seed,
|
|
6832
|
+
custom,
|
|
6833
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6834
|
+
} : secrets,
|
|
6835
|
+
generated
|
|
6836
|
+
};
|
|
6837
|
+
}
|
|
6838
|
+
/** 256 bits, URL-safe. */
|
|
6839
|
+
function random() {
|
|
6840
|
+
return (0, node_crypto.randomBytes)(32).toString("base64url");
|
|
6841
|
+
}
|
|
6842
|
+
//#endregion
|
|
6268
6843
|
//#region src/deploy/StateProvider.ts
|
|
6269
6844
|
/**
|
|
6270
6845
|
* Check if value is a StateProvider implementation.
|
|
@@ -7083,10 +7658,13 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7083
7658
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
7084
7659
|
appsToDeployNames = dokployApps;
|
|
7085
7660
|
logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
7086
|
-
const
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7661
|
+
const secretsStore = await require_store.secretsStoreFor(workspace, stage);
|
|
7662
|
+
const stored = await secretsStore.read(stage);
|
|
7663
|
+
if (!stored) logger$4.log(` ⚠️ No secrets found for stage "${stage}"; starting them`);
|
|
7664
|
+
const { secrets: stageSecrets, generated } = withGeneratedSecrets(stored ?? require_storage.initStageSecrets(stage), manifest);
|
|
7665
|
+
if (generated.length > 0) {
|
|
7666
|
+
await secretsStore.write(stage, stageSecrets);
|
|
7667
|
+
logger$4.log(` 🔑 Generated for "${stage}" (${secretsStore.name}): ${generated.join(", ")}`);
|
|
7090
7668
|
}
|
|
7091
7669
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
7092
7670
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
@@ -7214,6 +7792,8 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7214
7792
|
environmentId,
|
|
7215
7793
|
stage,
|
|
7216
7794
|
appUrls,
|
|
7795
|
+
supplied: stageSecrets.custom,
|
|
7796
|
+
seed: stageSecrets.seed,
|
|
7217
7797
|
manifest
|
|
7218
7798
|
});
|
|
7219
7799
|
declaredEnv = declared.env;
|
|
@@ -7974,6 +8554,172 @@ function printStateDetails(state) {
|
|
|
7974
8554
|
}
|
|
7975
8555
|
}
|
|
7976
8556
|
//#endregion
|
|
8557
|
+
//#region src/secrets/generator.ts
|
|
8558
|
+
/**
|
|
8559
|
+
* Generate a secure random password using URL-safe base64 characters.
|
|
8560
|
+
* @param length Password length (default: 32)
|
|
8561
|
+
*/
|
|
8562
|
+
function generateSecurePassword(length = 32) {
|
|
8563
|
+
return (0, node_crypto.randomBytes)(Math.ceil(length * 3 / 4)).toString("base64url").slice(0, length);
|
|
8564
|
+
}
|
|
8565
|
+
/** Default service configurations (localhost for local dev via Docker port mapping) */
|
|
8566
|
+
const SERVICE_DEFAULTS = {
|
|
8567
|
+
postgres: {
|
|
8568
|
+
host: "localhost",
|
|
8569
|
+
port: 5432,
|
|
8570
|
+
username: "app",
|
|
8571
|
+
database: "app"
|
|
8572
|
+
},
|
|
8573
|
+
redis: {
|
|
8574
|
+
host: "localhost",
|
|
8575
|
+
port: 6379,
|
|
8576
|
+
username: "default"
|
|
8577
|
+
},
|
|
8578
|
+
minio: {
|
|
8579
|
+
host: "localhost",
|
|
8580
|
+
port: 9e3,
|
|
8581
|
+
username: "app",
|
|
8582
|
+
bucket: "app"
|
|
8583
|
+
},
|
|
8584
|
+
mailpit: {
|
|
8585
|
+
host: "localhost",
|
|
8586
|
+
port: 1025,
|
|
8587
|
+
username: "app"
|
|
8588
|
+
},
|
|
8589
|
+
localstack: {
|
|
8590
|
+
host: "localhost",
|
|
8591
|
+
port: 4566,
|
|
8592
|
+
username: "localstack",
|
|
8593
|
+
region: "us-east-1"
|
|
8594
|
+
}
|
|
8595
|
+
};
|
|
8596
|
+
/** Default credentials for pgboss (not a Docker service, reuses postgres) */
|
|
8597
|
+
const PGBOSS_DEFAULTS = {
|
|
8598
|
+
host: "localhost",
|
|
8599
|
+
port: 5432,
|
|
8600
|
+
username: "pgboss",
|
|
8601
|
+
database: "app"
|
|
8602
|
+
};
|
|
8603
|
+
/**
|
|
8604
|
+
* Generate credentials for a specific service.
|
|
8605
|
+
*/
|
|
8606
|
+
function generateServiceCredentials(service) {
|
|
8607
|
+
return {
|
|
8608
|
+
...SERVICE_DEFAULTS[service],
|
|
8609
|
+
password: generateSecurePassword()
|
|
8610
|
+
};
|
|
8611
|
+
}
|
|
8612
|
+
/**
|
|
8613
|
+
* Generate credentials for multiple services.
|
|
8614
|
+
*/
|
|
8615
|
+
function generateServicesCredentials(services) {
|
|
8616
|
+
const result = {};
|
|
8617
|
+
for (const service of services) result[service] = generateServiceCredentials(service);
|
|
8618
|
+
return result;
|
|
8619
|
+
}
|
|
8620
|
+
/**
|
|
8621
|
+
* Generate connection URL for PostgreSQL.
|
|
8622
|
+
*/
|
|
8623
|
+
function generatePostgresUrl(creds) {
|
|
8624
|
+
const { username, password, host, port, database } = creds;
|
|
8625
|
+
return `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
8626
|
+
}
|
|
8627
|
+
/**
|
|
8628
|
+
* Generate connection URL for Redis.
|
|
8629
|
+
*/
|
|
8630
|
+
function generateRedisUrl(creds) {
|
|
8631
|
+
const { password, host, port } = creds;
|
|
8632
|
+
return `redis://:${encodeURIComponent(password)}@${host}:${port}`;
|
|
8633
|
+
}
|
|
8634
|
+
/**
|
|
8635
|
+
* Generate endpoint URL for MinIO (S3-compatible).
|
|
8636
|
+
*/
|
|
8637
|
+
function generateMinioEndpoint(creds) {
|
|
8638
|
+
const { host, port } = creds;
|
|
8639
|
+
return `http://${host}:${port}`;
|
|
8640
|
+
}
|
|
8641
|
+
/**
|
|
8642
|
+
* Generate connection URL for pg-boss (uses PostgreSQL protocol).
|
|
8643
|
+
* Format: pgboss://user:pass@host:port/db?schema=pgboss
|
|
8644
|
+
*/
|
|
8645
|
+
function generatePgBossUrl(creds) {
|
|
8646
|
+
const { username, password, host, port, database } = creds;
|
|
8647
|
+
return `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;
|
|
8648
|
+
}
|
|
8649
|
+
/**
|
|
8650
|
+
* Generate connection URLs from service credentials.
|
|
8651
|
+
*/
|
|
8652
|
+
function generateConnectionUrls(services) {
|
|
8653
|
+
const urls = {};
|
|
8654
|
+
if (services.postgres) urls.DATABASE_URL = generatePostgresUrl(services.postgres);
|
|
8655
|
+
if (services.redis) urls.REDIS_URL = generateRedisUrl(services.redis);
|
|
8656
|
+
if (services.minio) urls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);
|
|
8657
|
+
if (services.pgboss) {
|
|
8658
|
+
const pgbossUrl = generatePgBossUrl(services.pgboss);
|
|
8659
|
+
urls.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;
|
|
8660
|
+
urls.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;
|
|
8661
|
+
}
|
|
8662
|
+
if (services.mailpit) {
|
|
8663
|
+
urls.SMTP_HOST = services.mailpit.host;
|
|
8664
|
+
urls.SMTP_PORT = String(services.mailpit.port);
|
|
8665
|
+
}
|
|
8666
|
+
return urls;
|
|
8667
|
+
}
|
|
8668
|
+
/**
|
|
8669
|
+
* Create a new StageSecrets object with generated credentials.
|
|
8670
|
+
* @param stage - The deployment stage (e.g., 'development', 'production')
|
|
8671
|
+
* @param services - List of services to generate credentials for
|
|
8672
|
+
* @param options - Optional configuration
|
|
8673
|
+
* @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')
|
|
8674
|
+
*/
|
|
8675
|
+
function createStageSecrets(stage, services, options) {
|
|
8676
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8677
|
+
const serviceCredentials = generateServicesCredentials(services);
|
|
8678
|
+
if (options?.projectName) {
|
|
8679
|
+
if (serviceCredentials.postgres) serviceCredentials.postgres.database = `${options.projectName.replace(/-/g, "_")}_dev`;
|
|
8680
|
+
if (serviceCredentials.minio) {
|
|
8681
|
+
serviceCredentials.minio.bucket = options.projectName;
|
|
8682
|
+
serviceCredentials.minio.username = options.projectName;
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
if (serviceCredentials.postgres && !serviceCredentials.pgboss) serviceCredentials.pgboss = {
|
|
8686
|
+
...PGBOSS_DEFAULTS,
|
|
8687
|
+
password: generateSecurePassword(),
|
|
8688
|
+
host: serviceCredentials.postgres.host,
|
|
8689
|
+
port: serviceCredentials.postgres.port,
|
|
8690
|
+
database: serviceCredentials.postgres.database
|
|
8691
|
+
};
|
|
8692
|
+
return {
|
|
8693
|
+
stage,
|
|
8694
|
+
createdAt: now,
|
|
8695
|
+
updatedAt: now,
|
|
8696
|
+
services: serviceCredentials,
|
|
8697
|
+
urls: generateConnectionUrls(serviceCredentials),
|
|
8698
|
+
custom: {}
|
|
8699
|
+
};
|
|
8700
|
+
}
|
|
8701
|
+
/**
|
|
8702
|
+
* Rotate password for a specific service.
|
|
8703
|
+
*/
|
|
8704
|
+
function rotateServicePassword(secrets, service) {
|
|
8705
|
+
const currentCreds = secrets.services[service];
|
|
8706
|
+
if (!currentCreds) throw new Error(`Service "${service}" not configured in secrets`);
|
|
8707
|
+
const newCreds = {
|
|
8708
|
+
...currentCreds,
|
|
8709
|
+
password: generateSecurePassword()
|
|
8710
|
+
};
|
|
8711
|
+
const newServices = {
|
|
8712
|
+
...secrets.services,
|
|
8713
|
+
[service]: newCreds
|
|
8714
|
+
};
|
|
8715
|
+
return {
|
|
8716
|
+
...secrets,
|
|
8717
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8718
|
+
services: newServices,
|
|
8719
|
+
urls: generateConnectionUrls(newServices)
|
|
8720
|
+
};
|
|
8721
|
+
}
|
|
8722
|
+
//#endregion
|
|
7977
8723
|
//#region src/migrate/names.ts
|
|
7978
8724
|
/**
|
|
7979
8725
|
* What a migration file is called: a UTC timestamp, then a name.
|
|
@@ -8809,26 +9555,26 @@ function loadPackageJson() {
|
|
|
8809
9555
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8810
9556
|
*/
|
|
8811
9557
|
const GEEKMIDAS_VERSIONS = {
|
|
8812
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8813
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8814
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8815
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8816
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8817
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8818
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8819
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8820
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8821
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8822
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8823
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8824
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8825
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8826
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8827
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8828
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8829
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8830
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8831
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
9558
|
+
"@geekmidas/audit": "~10.0.0-alpha.38",
|
|
9559
|
+
"@geekmidas/auth": "~10.0.0-alpha.38",
|
|
9560
|
+
"@geekmidas/cache": "~10.0.0-alpha.38",
|
|
9561
|
+
"@geekmidas/client": "~10.0.0-alpha.38",
|
|
9562
|
+
"@geekmidas/cloud": "~10.0.0-alpha.38",
|
|
9563
|
+
"@geekmidas/constructs": "~10.0.0-alpha.38",
|
|
9564
|
+
"@geekmidas/db": "~10.0.0-alpha.38",
|
|
9565
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.38",
|
|
9566
|
+
"@geekmidas/envkit": "~10.0.0-alpha.38",
|
|
9567
|
+
"@geekmidas/errors": "~10.0.0-alpha.38",
|
|
9568
|
+
"@geekmidas/events": "~10.0.0-alpha.38",
|
|
9569
|
+
"@geekmidas/logger": "~10.0.0-alpha.38",
|
|
9570
|
+
"@geekmidas/manifest": "~10.0.0-alpha.38",
|
|
9571
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.38",
|
|
9572
|
+
"@geekmidas/schema": "~10.0.0-alpha.38",
|
|
9573
|
+
"@geekmidas/services": "~10.0.0-alpha.38",
|
|
9574
|
+
"@geekmidas/storage": "~10.0.0-alpha.38",
|
|
9575
|
+
"@geekmidas/studio": "~10.0.0-alpha.38",
|
|
9576
|
+
"@geekmidas/telescope": "~10.0.0-alpha.38",
|
|
9577
|
+
"@geekmidas/testkit": "~10.0.0-alpha.38",
|
|
8832
9578
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8833
9579
|
};
|
|
8834
9580
|
//#endregion
|
|
@@ -9400,16 +10146,7 @@ function deploy(options) {
|
|
|
9400
10146
|
role-to-assume: \${{ vars.AWS_ROLE_ARN }}
|
|
9401
10147
|
aws-region: ${options.region}
|
|
9402
10148
|
` : "";
|
|
9403
|
-
const stageSecrets = sst ?
|
|
9404
|
-
# The stage's secrets, from SSM in the account the role belongs to —
|
|
9405
|
-
# pushed there once with \`gkm secrets:push --stage <stage> --profile …\`.
|
|
9406
|
-
# The stage reaches the shell as a variable, never pasted into the script:
|
|
9407
|
-
# on a manual run it is whatever was typed.
|
|
9408
|
-
- name: Stage secrets
|
|
9409
|
-
run: ${pm.exec} gkm secrets:pull --stage "$STAGE"
|
|
9410
|
-
env:
|
|
9411
|
-
STAGE: \${{ matrix.stage }}
|
|
9412
|
-
` : `
|
|
10149
|
+
const stageSecrets = sst ? credentials : `
|
|
9413
10150
|
# The stage's secrets are kept in the encrypted .gkm/secrets/<stage>.json,
|
|
9414
10151
|
# and each environment holds its key as GKM_SECRETS_KEY. .gkm/ is
|
|
9415
10152
|
# gitignored, so a checkout has the key but not the file: set
|
|
@@ -10835,7 +11572,7 @@ function secretsStore(options) {
|
|
|
10835
11572
|
if (options.deployTarget !== "sst" || !options.region) return "";
|
|
10836
11573
|
return `
|
|
10837
11574
|
// Deployed stages keep their secrets in SSM, in the account each stage
|
|
10838
|
-
// deploys to: \`gkm secrets:
|
|
11575
|
+
// deploys to: \`gkm secrets:set <KEY> '…' --stage <stage>\` writes there.
|
|
10839
11576
|
store: { provider: 'ssm', region: '${options.region}' },`;
|
|
10840
11577
|
}
|
|
10841
11578
|
/**
|
|
@@ -14729,7 +15466,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
14729
15466
|
}
|
|
14730
15467
|
console.log("🔐 Initializing encrypted secrets...\n");
|
|
14731
15468
|
const local = templateOptions.stages.local;
|
|
14732
|
-
const devSecrets =
|
|
15469
|
+
const devSecrets = createStageSecrets(local, [], { projectName: name });
|
|
14733
15470
|
const customSecrets = {
|
|
14734
15471
|
PORT: "3000",
|
|
14735
15472
|
LOG_LEVEL: "debug",
|
|
@@ -14743,8 +15480,8 @@ async function initCommand(projectName, options = {}) {
|
|
|
14743
15480
|
customSecrets.BETTER_AUTH_TRUSTED_ORIGINS = "http://localhost:3000,http://localhost:3001";
|
|
14744
15481
|
}
|
|
14745
15482
|
devSecrets.custom = customSecrets;
|
|
14746
|
-
await
|
|
14747
|
-
const keyPath =
|
|
15483
|
+
await new require_store.FileSecretsStore(targetDir).write(local, devSecrets);
|
|
15484
|
+
const keyPath = require_store.getKeyPath(local, name);
|
|
14748
15485
|
console.log(` Secrets: .gkm/secrets/${local}.json (encrypted)`);
|
|
14749
15486
|
console.log(` Key: ${keyPath}\n`);
|
|
14750
15487
|
if (!options.skipInstall) {
|
|
@@ -14896,15 +15633,104 @@ var NotAnAwsRegion = class extends Error {
|
|
|
14896
15633
|
}
|
|
14897
15634
|
};
|
|
14898
15635
|
//#endregion
|
|
15636
|
+
//#region src/setup/fullstack-secrets.ts
|
|
15637
|
+
/**
|
|
15638
|
+
* Generate a secure random password for database users.
|
|
15639
|
+
* Uses a combination of timestamp and random bytes for uniqueness.
|
|
15640
|
+
*/
|
|
15641
|
+
function generateDbPassword() {
|
|
15642
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
|
|
15643
|
+
}
|
|
15644
|
+
/**
|
|
15645
|
+
* Generate database URL for an app.
|
|
15646
|
+
* All apps connect to the same database, but use different users/schemas.
|
|
15647
|
+
*/
|
|
15648
|
+
function generateDbUrl(appName, password, projectName, host = "localhost", port = 5432) {
|
|
15649
|
+
return `postgresql://${appName.replace(/-/g, "_")}:${password}@${host}:${port}/${`${projectName.replace(/-/g, "_")}_dev`}`;
|
|
15650
|
+
}
|
|
15651
|
+
/**
|
|
15652
|
+
* Generate fullstack-aware custom secrets for a workspace.
|
|
15653
|
+
*
|
|
15654
|
+
* Generates:
|
|
15655
|
+
* - Common secrets: PORT, LOG_LEVEL, JWT_SECRET (no `NODE_ENV` — the command
|
|
15656
|
+
* decides that; see `gkm init`)
|
|
15657
|
+
* - Per-app database passwords and URLs for backend apps with db service
|
|
15658
|
+
* - Better-auth secrets for apps using the better-auth framework
|
|
15659
|
+
*/
|
|
15660
|
+
function generateFullstackCustomSecrets(workspace, containers = []) {
|
|
15661
|
+
const hasDb = containers.includes("postgres");
|
|
15662
|
+
const customs = {
|
|
15663
|
+
PORT: "3000",
|
|
15664
|
+
LOG_LEVEL: "debug",
|
|
15665
|
+
JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
15666
|
+
};
|
|
15667
|
+
if (!hasDb) return customs;
|
|
15668
|
+
const frontendPorts = [];
|
|
15669
|
+
for (const [appName, appConfig] of Object.entries(workspace.apps)) {
|
|
15670
|
+
if (appConfig.type === "web") {
|
|
15671
|
+
frontendPorts.push(appConfig.port);
|
|
15672
|
+
const upperName = appName.toUpperCase();
|
|
15673
|
+
customs[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;
|
|
15674
|
+
continue;
|
|
15675
|
+
}
|
|
15676
|
+
if (appConfig.type === "mobile") continue;
|
|
15677
|
+
const password = generateDbPassword();
|
|
15678
|
+
const upperName = appName.toUpperCase();
|
|
15679
|
+
customs[`${upperName}_DATABASE_URL`] = generateDbUrl(appName, password, workspace.name);
|
|
15680
|
+
customs[`${upperName}_DB_PASSWORD`] = password;
|
|
15681
|
+
if (appConfig.framework === "better-auth") {
|
|
15682
|
+
customs.AUTH_PORT = String(appConfig.port);
|
|
15683
|
+
customs.AUTH_URL = `http://localhost:${appConfig.port}`;
|
|
15684
|
+
customs.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;
|
|
15685
|
+
customs.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;
|
|
15686
|
+
}
|
|
15687
|
+
}
|
|
15688
|
+
if (customs.BETTER_AUTH_SECRET) customs.BETTER_AUTH_TRUSTED_ORIGINS = Object.values(workspace.apps).map((a) => a.port).map((p) => `http://localhost:${p}`).join(",");
|
|
15689
|
+
return customs;
|
|
15690
|
+
}
|
|
15691
|
+
/**
|
|
15692
|
+
* Extract *_DB_PASSWORD keys from secrets and write docker/.env.
|
|
15693
|
+
*
|
|
15694
|
+
* The docker/.env file contains database passwords that the PostgreSQL
|
|
15695
|
+
* init script reads to create per-app database users.
|
|
15696
|
+
*/
|
|
15697
|
+
async function writeDockerEnvFromSecrets(secrets, workspaceRoot) {
|
|
15698
|
+
const dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) => key.endsWith("_DB_PASSWORD"));
|
|
15699
|
+
if (secrets.services.pgboss && !dbPasswordEntries.some(([key]) => key === "PGBOSS_DB_PASSWORD")) dbPasswordEntries.push(["PGBOSS_DB_PASSWORD", secrets.services.pgboss.password]);
|
|
15700
|
+
if (dbPasswordEntries.length === 0) return;
|
|
15701
|
+
const envContent = `# Auto-generated docker environment file
|
|
15702
|
+
# Contains database passwords for docker-compose postgres init
|
|
15703
|
+
# This file is gitignored - do not commit to version control
|
|
15704
|
+
${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
15705
|
+
`;
|
|
15706
|
+
const envPath = (0, node_path.join)(workspaceRoot, "docker", ".env");
|
|
15707
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(envPath), { recursive: true });
|
|
15708
|
+
await (0, node_fs_promises.writeFile)(envPath, envContent);
|
|
15709
|
+
}
|
|
15710
|
+
//#endregion
|
|
14899
15711
|
//#region src/secrets/index.ts
|
|
14900
15712
|
const logger$2 = console;
|
|
14901
15713
|
/**
|
|
15714
|
+
* The store the stage named on the command line keeps its secrets in — SSM in
|
|
15715
|
+
* its account, for a deployed stage kept there. Outside any workspace nothing
|
|
15716
|
+
* names a store, so it is the file.
|
|
15717
|
+
*/
|
|
15718
|
+
async function storeFor(stage) {
|
|
15719
|
+
try {
|
|
15720
|
+
return await require_store.secretsStoreFor(await require_config.loadWorkspaceSettings(), stage);
|
|
15721
|
+
} catch (error) {
|
|
15722
|
+
if (error instanceof require_config.ConfigNotFound) return new require_store.FileSecretsStore(process.cwd());
|
|
15723
|
+
throw error;
|
|
15724
|
+
}
|
|
15725
|
+
}
|
|
15726
|
+
/**
|
|
14902
15727
|
* Initialize secrets for a stage.
|
|
14903
15728
|
* Generates secure random passwords for configured services.
|
|
14904
15729
|
*/
|
|
14905
15730
|
async function secretsInitCommand(options) {
|
|
14906
15731
|
const { stage, force } = options;
|
|
14907
|
-
|
|
15732
|
+
const store = await storeFor(stage);
|
|
15733
|
+
if (!force && await store.read(stage)) {
|
|
14908
15734
|
logger$2.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
14909
15735
|
process.exit(1);
|
|
14910
15736
|
}
|
|
@@ -14914,15 +15740,15 @@ async function secretsInitCommand(options) {
|
|
|
14914
15740
|
const loaded = await require_config.loadWorkspaceConfig();
|
|
14915
15741
|
projectName = loaded.workspace.name;
|
|
14916
15742
|
if (Object.keys(loaded.workspace.apps).length > 1) {
|
|
14917
|
-
workspaceSecrets =
|
|
15743
|
+
workspaceSecrets = generateFullstackCustomSecrets(loaded.workspace);
|
|
14918
15744
|
logger$2.log(" Detected workspace mode — generating per-app secrets");
|
|
14919
15745
|
}
|
|
14920
15746
|
} catch {}
|
|
14921
|
-
const secrets =
|
|
15747
|
+
const secrets = createStageSecrets(stage, [], { projectName });
|
|
14922
15748
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
14923
|
-
await
|
|
15749
|
+
await store.write(stage, secrets);
|
|
14924
15750
|
logger$2.log(`\n✓ Secrets initialized for stage "${stage}"`);
|
|
14925
|
-
logger$2.log(`
|
|
15751
|
+
logger$2.log(` Store: ${store.name}`);
|
|
14926
15752
|
if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14927
15753
|
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14928
15754
|
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
@@ -14957,20 +15783,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
14957
15783
|
process.exit(1);
|
|
14958
15784
|
}
|
|
14959
15785
|
}
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
14964
|
-
logger$2.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
15786
|
+
const store = await storeFor(stage);
|
|
15787
|
+
const secrets = await store.read(stage);
|
|
15788
|
+
if (!secrets) {
|
|
15789
|
+
logger$2.error(`Secrets not found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14965
15790
|
process.exit(1);
|
|
14966
15791
|
}
|
|
15792
|
+
await store.write(stage, require_storage.withCustomSecret(secrets, key, secretValue));
|
|
15793
|
+
logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}" (${store.name})`);
|
|
14967
15794
|
}
|
|
14968
15795
|
/**
|
|
14969
15796
|
* Show secrets for a stage.
|
|
14970
15797
|
*/
|
|
14971
15798
|
async function secretsShowCommand(options) {
|
|
14972
15799
|
const { stage, reveal } = options;
|
|
14973
|
-
const secrets = await
|
|
15800
|
+
const secrets = await (await storeFor(stage)).read(stage);
|
|
14974
15801
|
if (!secrets) {
|
|
14975
15802
|
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14976
15803
|
process.exit(1);
|
|
@@ -15003,7 +15830,8 @@ async function secretsShowCommand(options) {
|
|
|
15003
15830
|
*/
|
|
15004
15831
|
async function secretsRotateCommand(options) {
|
|
15005
15832
|
const { stage, service } = options;
|
|
15006
|
-
const
|
|
15833
|
+
const store = await storeFor(stage);
|
|
15834
|
+
const secrets = await store.read(stage);
|
|
15007
15835
|
if (!secrets) {
|
|
15008
15836
|
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
15009
15837
|
process.exit(1);
|
|
@@ -15013,14 +15841,14 @@ async function secretsRotateCommand(options) {
|
|
|
15013
15841
|
logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
15014
15842
|
process.exit(1);
|
|
15015
15843
|
}
|
|
15016
|
-
const updated =
|
|
15017
|
-
await
|
|
15844
|
+
const updated = rotateServicePassword(secrets, service);
|
|
15845
|
+
await store.write(stage, updated);
|
|
15018
15846
|
logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
15019
15847
|
} else {
|
|
15020
15848
|
let updated = secrets;
|
|
15021
15849
|
const services = Object.keys(secrets.services);
|
|
15022
|
-
for (const svc of services) updated =
|
|
15023
|
-
await
|
|
15850
|
+
for (const svc of services) updated = rotateServicePassword(updated, svc);
|
|
15851
|
+
await store.write(stage, updated);
|
|
15024
15852
|
logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
15025
15853
|
}
|
|
15026
15854
|
logger$2.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
@@ -15044,7 +15872,8 @@ async function secretsImportCommand(file, options) {
|
|
|
15044
15872
|
logger$2.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
15045
15873
|
process.exit(1);
|
|
15046
15874
|
}
|
|
15047
|
-
const
|
|
15875
|
+
const store = await storeFor(stage);
|
|
15876
|
+
const secrets = await store.read(stage);
|
|
15048
15877
|
if (!secrets) {
|
|
15049
15878
|
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
15050
15879
|
process.exit(1);
|
|
@@ -15058,7 +15887,7 @@ async function secretsImportCommand(file, options) {
|
|
|
15058
15887
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15059
15888
|
custom: updatedCustom
|
|
15060
15889
|
};
|
|
15061
|
-
await
|
|
15890
|
+
await store.write(stage, updated);
|
|
15062
15891
|
const importedCount = Object.keys(importedSecrets).length;
|
|
15063
15892
|
const totalCount = Object.keys(updatedCustom).length;
|
|
15064
15893
|
logger$2.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
|
|
@@ -15079,121 +15908,6 @@ function maskUrl(url) {
|
|
|
15079
15908
|
}
|
|
15080
15909
|
}
|
|
15081
15910
|
//#endregion
|
|
15082
|
-
//#region src/secrets/store.ts
|
|
15083
|
-
/** The encrypted file under `.gkm/secrets/`, and its key under `~/.gkm/`. */
|
|
15084
|
-
var FileSecretsStore = class {
|
|
15085
|
-
root;
|
|
15086
|
-
constructor(root) {
|
|
15087
|
-
this.root = root;
|
|
15088
|
-
}
|
|
15089
|
-
pull(stage) {
|
|
15090
|
-
return require_storage.readStageSecrets(stage, this.root);
|
|
15091
|
-
}
|
|
15092
|
-
push(_stage, secrets) {
|
|
15093
|
-
return require_storage.writeStageSecrets(secrets, this.root);
|
|
15094
|
-
}
|
|
15095
|
-
};
|
|
15096
|
-
/**
|
|
15097
|
-
* The store a stage's secrets live in.
|
|
15098
|
-
*
|
|
15099
|
-
* The local stage is always the file: its secrets belong to the machine
|
|
15100
|
-
* running `gkm dev`. A deployed stage uses `secrets.store`.
|
|
15101
|
-
*/
|
|
15102
|
-
async function secretsStoreFor(workspace, stage, options = {}) {
|
|
15103
|
-
const configured = workspace.secrets.store ?? "file";
|
|
15104
|
-
if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
|
|
15105
|
-
if (configured.provider !== "ssm") return configured.provider;
|
|
15106
|
-
const { SsmSecretsStore } = await Promise.resolve().then(() => require("./ssm-FxeSAt35.cjs"));
|
|
15107
|
-
return new SsmSecretsStore({
|
|
15108
|
-
project: workspace.name,
|
|
15109
|
-
region: configured.region,
|
|
15110
|
-
...options.profile ? { profile: options.profile } : {}
|
|
15111
|
-
});
|
|
15112
|
-
}
|
|
15113
|
-
/** Whether a stage's secrets live somewhere other than this machine. */
|
|
15114
|
-
function isRemoteStore(workspace, stage) {
|
|
15115
|
-
const configured = workspace.secrets.store ?? "file";
|
|
15116
|
-
return stage !== workspace.stages.local && configured !== "file";
|
|
15117
|
-
}
|
|
15118
|
-
//#endregion
|
|
15119
|
-
//#region src/secrets/transfer.ts
|
|
15120
|
-
/**
|
|
15121
|
-
* Moving a deployed stage's secrets between this machine and its store.
|
|
15122
|
-
*
|
|
15123
|
-
* `push` sends the encrypted local copy to the stage's store; `pull` brings it
|
|
15124
|
-
* back and writes the local copy a deploy reads — generating a key for it if
|
|
15125
|
-
* the machine has none, which is what a fresh CI runner needs. Both reconcile
|
|
15126
|
-
* the keys the workspace now derives, so a store never lags a new construct.
|
|
15127
|
-
*/
|
|
15128
|
-
var transfer_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
15129
|
-
NoLocalSecrets: () => NoLocalSecrets,
|
|
15130
|
-
NoRemoteSecretsStore: () => NoRemoteSecretsStore,
|
|
15131
|
-
NoStoredSecrets: () => NoStoredSecrets,
|
|
15132
|
-
pullStageSecrets: () => pullStageSecrets,
|
|
15133
|
-
pushStageSecrets: () => pushStageSecrets
|
|
15134
|
-
});
|
|
15135
|
-
/** A push or pull for a stage with nowhere to push or pull from. */
|
|
15136
|
-
var NoRemoteSecretsStore = class extends Error {
|
|
15137
|
-
stage;
|
|
15138
|
-
constructor(stage) {
|
|
15139
|
-
super(`Stage "${stage}" keeps its secrets in the local file, so there is nothing to push or pull. Set secrets.store in gkm.config.ts (e.g. { provider: 'ssm', region: 'eu-west-1' }) for deployed stages.`);
|
|
15140
|
-
this.stage = stage;
|
|
15141
|
-
this.name = "NoRemoteSecretsStore";
|
|
15142
|
-
}
|
|
15143
|
-
};
|
|
15144
|
-
/** A push for a stage whose secrets this machine does not have. */
|
|
15145
|
-
var NoLocalSecrets = class extends Error {
|
|
15146
|
-
stage;
|
|
15147
|
-
constructor(stage) {
|
|
15148
|
-
super(`No secrets for stage "${stage}" on this machine. Run gkm secrets:init --stage ${stage} first.`);
|
|
15149
|
-
this.stage = stage;
|
|
15150
|
-
this.name = "NoLocalSecrets";
|
|
15151
|
-
}
|
|
15152
|
-
};
|
|
15153
|
-
/** A pull for a stage the store holds nothing for. */
|
|
15154
|
-
var NoStoredSecrets = class extends Error {
|
|
15155
|
-
stage;
|
|
15156
|
-
constructor(stage) {
|
|
15157
|
-
super(`The store holds no secrets for stage "${stage}". Push them first: gkm secrets:push --stage ${stage}.`);
|
|
15158
|
-
this.stage = stage;
|
|
15159
|
-
this.name = "NoStoredSecrets";
|
|
15160
|
-
}
|
|
15161
|
-
};
|
|
15162
|
-
async function reconcile(secrets, workspace, stage) {
|
|
15163
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-C0X8xSXV.cjs")).then((n) => n.workspace_exports);
|
|
15164
|
-
const result = require_reconcile.reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15165
|
-
return result ? {
|
|
15166
|
-
secrets: result.secrets,
|
|
15167
|
-
addedKeys: result.addedKeys
|
|
15168
|
-
} : {
|
|
15169
|
-
secrets,
|
|
15170
|
-
addedKeys: []
|
|
15171
|
-
};
|
|
15172
|
-
}
|
|
15173
|
-
function remoteOnly(workspace, stage) {
|
|
15174
|
-
require_workspace.assertDeployedStage(workspace.stages, stage);
|
|
15175
|
-
if (!isRemoteStore(workspace, stage)) throw new NoRemoteSecretsStore(stage);
|
|
15176
|
-
}
|
|
15177
|
-
/** Send this machine's secrets for a deployed stage to its store. */
|
|
15178
|
-
async function pushStageSecrets(workspace, stage, options = {}) {
|
|
15179
|
-
remoteOnly(workspace, stage);
|
|
15180
|
-
const local = await require_storage.readStageSecrets(stage, workspace.root);
|
|
15181
|
-
if (!local) throw new NoLocalSecrets(stage);
|
|
15182
|
-
const result = await reconcile(local, workspace, stage);
|
|
15183
|
-
if (result.addedKeys.length) await require_storage.writeStageSecrets(result.secrets, workspace.root);
|
|
15184
|
-
await (await secretsStoreFor(workspace, stage, options)).push(stage, result.secrets);
|
|
15185
|
-
return result;
|
|
15186
|
-
}
|
|
15187
|
-
/** Bring a deployed stage's secrets from its store into the local copy. */
|
|
15188
|
-
async function pullStageSecrets(workspace, stage, options = {}) {
|
|
15189
|
-
remoteOnly(workspace, stage);
|
|
15190
|
-
const stored = await (await secretsStoreFor(workspace, stage, options)).pull(stage);
|
|
15191
|
-
if (!stored) throw new NoStoredSecrets(stage);
|
|
15192
|
-
const result = await reconcile(stored, workspace, stage);
|
|
15193
|
-
await require_storage.writeStageSecrets(result.secrets, workspace.root);
|
|
15194
|
-
return result;
|
|
15195
|
-
}
|
|
15196
|
-
//#endregion
|
|
15197
15911
|
//#region src/setup/index.ts
|
|
15198
15912
|
var setup_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
15199
15913
|
createFreshWorkspaceSecrets: () => createFreshWorkspaceSecrets,
|
|
@@ -15233,7 +15947,7 @@ async function setupCommand(options = {}) {
|
|
|
15233
15947
|
}
|
|
15234
15948
|
const containers = await require_workspace$1.derivedContainers(workspace, stage);
|
|
15235
15949
|
if (isMultiApp && containers.includes("postgres")) {
|
|
15236
|
-
await
|
|
15950
|
+
await writeDockerEnvFromSecrets(secrets, workspace.root);
|
|
15237
15951
|
logger$1.log("📄 Generated docker/.env with database passwords");
|
|
15238
15952
|
}
|
|
15239
15953
|
if (!options.skipDocker) {
|
|
@@ -15275,30 +15989,18 @@ async function reconcileLocal(workspace, stage, options) {
|
|
|
15275
15989
|
async function resolveSecrets(stage, workspace, options) {
|
|
15276
15990
|
if (options.force) {
|
|
15277
15991
|
logger$1.log("🔐 Generating fresh secrets (--force)...");
|
|
15278
|
-
return generateFreshSecrets(stage, workspace
|
|
15279
|
-
}
|
|
15280
|
-
if (require_storage.secretsExist(stage, workspace.root)) {
|
|
15281
|
-
logger$1.log("🔐 Using existing local secrets");
|
|
15282
|
-
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
15283
|
-
if (secrets) {
|
|
15284
|
-
const reconciled = reconcileSecrets(secrets, workspace, await require_workspace$1.derivedContainers(workspace, stage));
|
|
15285
|
-
if (reconciled) await require_storage.writeStageSecrets(reconciled, workspace.root);
|
|
15286
|
-
return reconciled ?? secrets;
|
|
15287
|
-
}
|
|
15992
|
+
return generateFreshSecrets(stage, workspace);
|
|
15288
15993
|
}
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
if (error instanceof NoStoredSecrets) logger$1.log(" The store holds none yet");
|
|
15297
|
-
else logger$1.warn(`⚠️ Could not pull from the store: ${error.message}`);
|
|
15298
|
-
}
|
|
15994
|
+
const store = await require_store.secretsStoreFor(workspace, stage);
|
|
15995
|
+
const secrets = await store.read(stage);
|
|
15996
|
+
if (secrets) {
|
|
15997
|
+
logger$1.log("🔐 Using existing secrets");
|
|
15998
|
+
const reconciled = reconcileSecrets(secrets, workspace, await require_workspace$1.derivedContainers(workspace, stage));
|
|
15999
|
+
if (reconciled) await store.write(stage, reconciled);
|
|
16000
|
+
return reconciled ?? secrets;
|
|
15299
16001
|
}
|
|
15300
16002
|
logger$1.log("🔐 Generating fresh development secrets...");
|
|
15301
|
-
return generateFreshSecrets(stage, workspace
|
|
16003
|
+
return generateFreshSecrets(stage, workspace);
|
|
15302
16004
|
}
|
|
15303
16005
|
/**
|
|
15304
16006
|
* Reconcile existing secrets with expected workspace-derived keys.
|
|
@@ -15328,7 +16030,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15328
16030
|
let changed = false;
|
|
15329
16031
|
let result = { ...secrets };
|
|
15330
16032
|
for (const name of credentialedServices(containers)) if (!result.services[name]) {
|
|
15331
|
-
const creds =
|
|
16033
|
+
const creds = generateServiceCredentials(name);
|
|
15332
16034
|
if (name === "minio") {
|
|
15333
16035
|
creds.bucket = workspace.name;
|
|
15334
16036
|
creds.username = workspace.name;
|
|
@@ -15340,7 +16042,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15340
16042
|
[name]: creds
|
|
15341
16043
|
}
|
|
15342
16044
|
};
|
|
15343
|
-
result.urls =
|
|
16045
|
+
result.urls = generateConnectionUrls(result.services);
|
|
15344
16046
|
logger$1.log(` 🔄 Adding missing service credentials: ${name}`);
|
|
15345
16047
|
changed = true;
|
|
15346
16048
|
}
|
|
@@ -15353,17 +16055,17 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15353
16055
|
host: result.services.postgres.host,
|
|
15354
16056
|
port: result.services.postgres.port,
|
|
15355
16057
|
username: "pgboss",
|
|
15356
|
-
password:
|
|
16058
|
+
password: generateSecurePassword(),
|
|
15357
16059
|
database: result.services.postgres.database ?? "app"
|
|
15358
16060
|
}
|
|
15359
16061
|
}
|
|
15360
16062
|
};
|
|
15361
|
-
result.urls =
|
|
16063
|
+
result.urls = generateConnectionUrls(result.services);
|
|
15362
16064
|
logger$1.log(" 🔄 Adding missing service credentials: pgboss");
|
|
15363
16065
|
changed = true;
|
|
15364
16066
|
}
|
|
15365
16067
|
if (Object.keys(workspace.apps).length > 1) {
|
|
15366
|
-
const expected =
|
|
16068
|
+
const expected = generateFullstackCustomSecrets(workspace, containers);
|
|
15367
16069
|
const missing = {};
|
|
15368
16070
|
for (const [key, value] of Object.entries(expected)) if (!(key in result.custom)) missing[key] = value;
|
|
15369
16071
|
if (Object.keys(missing).length > 0) {
|
|
@@ -15392,9 +16094,8 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15392
16094
|
* @internal Exported for reuse by `gkm test` auto-setup.
|
|
15393
16095
|
*/
|
|
15394
16096
|
function createFreshWorkspaceSecrets(stage, workspace, containers = []) {
|
|
15395
|
-
const
|
|
15396
|
-
|
|
15397
|
-
if (Object.keys(workspace.apps).length > 1) secrets.custom = require_reconcile.generateFullstackCustomSecrets(workspace, containers);
|
|
16097
|
+
const secrets = createStageSecrets(stage, credentialedServices(containers), { projectName: workspace.name });
|
|
16098
|
+
if (Object.keys(workspace.apps).length > 1) secrets.custom = generateFullstackCustomSecrets(workspace, containers);
|
|
15398
16099
|
else secrets.custom = {
|
|
15399
16100
|
PORT: "3000",
|
|
15400
16101
|
LOG_LEVEL: "debug",
|
|
@@ -15408,38 +16109,26 @@ function createFreshWorkspaceSecrets(stage, workspace, containers = []) {
|
|
|
15408
16109
|
*
|
|
15409
16110
|
* Powers `gkm test --auto-setup` / `GKM_AUTO_SETUP`: CI can run without a
|
|
15410
16111
|
* committed secrets file or a shared encryption key — the stage is regenerated
|
|
15411
|
-
* from the committed `gkm.config.ts`, and
|
|
16112
|
+
* from the committed `gkm.config.ts`, and the file store mints a local key.
|
|
15412
16113
|
*
|
|
15413
16114
|
* @returns true if fresh secrets were generated, false if existing ones were kept
|
|
15414
16115
|
*/
|
|
15415
16116
|
async function ensureStageSecrets(stage, cwd = process.cwd()) {
|
|
15416
16117
|
const { workspace } = await require_config.loadWorkspaceConfig(cwd);
|
|
15417
|
-
|
|
16118
|
+
const store = await require_store.secretsStoreFor(workspace, stage);
|
|
16119
|
+
if (await store.read(stage)) return false;
|
|
15418
16120
|
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_workspace$1.derivedContainers(workspace, stage));
|
|
15419
|
-
await
|
|
16121
|
+
await store.write(stage, secrets);
|
|
15420
16122
|
return true;
|
|
15421
16123
|
}
|
|
15422
16124
|
/**
|
|
15423
16125
|
* Generate fresh secrets for the workspace.
|
|
15424
16126
|
*/
|
|
15425
|
-
async function generateFreshSecrets(stage, workspace
|
|
16127
|
+
async function generateFreshSecrets(stage, workspace) {
|
|
15426
16128
|
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_workspace$1.derivedContainers(workspace, stage));
|
|
15427
|
-
await
|
|
15428
|
-
|
|
15429
|
-
|
|
15430
|
-
const { shouldPush } = await (0, prompts.default)({
|
|
15431
|
-
type: "confirm",
|
|
15432
|
-
name: "shouldPush",
|
|
15433
|
-
message: `Push the "${stage}" secrets to its store?`,
|
|
15434
|
-
initial: true
|
|
15435
|
-
});
|
|
15436
|
-
if (shouldPush) try {
|
|
15437
|
-
await pushStageSecrets(workspace, stage);
|
|
15438
|
-
logger$1.log("☁️ Secrets pushed to the store");
|
|
15439
|
-
} catch (error) {
|
|
15440
|
-
logger$1.warn(`⚠️ Could not push to the store: ${error.message}`);
|
|
15441
|
-
}
|
|
15442
|
-
}
|
|
16129
|
+
const store = await require_store.secretsStoreFor(workspace, stage);
|
|
16130
|
+
await store.write(stage, secrets);
|
|
16131
|
+
logger$1.log(` Secrets written to the "${stage}" store (${store.name})`);
|
|
15443
16132
|
return secrets;
|
|
15444
16133
|
}
|
|
15445
16134
|
/**
|
|
@@ -15459,6 +16148,56 @@ function printSummary(workspace, stage) {
|
|
|
15459
16148
|
logger$1.log("");
|
|
15460
16149
|
}
|
|
15461
16150
|
//#endregion
|
|
16151
|
+
//#region src/test/teardown.ts
|
|
16152
|
+
/**
|
|
16153
|
+
* Dropping the test stage's databases when a suite ends.
|
|
16154
|
+
*
|
|
16155
|
+
* Reconcile never drops — the line it draws is at data. The test stage's data
|
|
16156
|
+
* is the exception: every test runs in a transaction rolled back after it, so
|
|
16157
|
+
* nothing in these databases is anyone's, and a database kept between runs is
|
|
16158
|
+
* one whose migrations already ran — an edited migration is never applied to
|
|
16159
|
+
* it again. So each run starts from a database it created, and drops it when
|
|
16160
|
+
* the suite ends: the Vitest global setup's teardown.
|
|
16161
|
+
*/
|
|
16162
|
+
/**
|
|
16163
|
+
* Drop the databases the last setup recorded, and forget the test stage's
|
|
16164
|
+
* reconcile, so the next run provisions them from nothing.
|
|
16165
|
+
*
|
|
16166
|
+
* A no-op when nothing is recorded — no run yet, or one already torn down —
|
|
16167
|
+
* so running it twice, or before a setup, is harmless.
|
|
16168
|
+
*/
|
|
16169
|
+
async function dropTestDatabases(cwd, root, sql = require_workspace$1.pgClient) {
|
|
16170
|
+
const readyPath = (0, node_path.join)(cwd, require_ready.TEST_READY_FILE);
|
|
16171
|
+
const ready = await readReady(readyPath);
|
|
16172
|
+
const recorded = ready?.databases;
|
|
16173
|
+
if (!ready || !recorded) return [];
|
|
16174
|
+
const client = sql(recorded.port);
|
|
16175
|
+
for (const name of recorded.names) {
|
|
16176
|
+
if (!name.endsWith(`_test`)) throw new NotATestDatabase(name);
|
|
16177
|
+
await client.query(void 0, `DROP DATABASE IF EXISTS ${require_workspace$1.quoteIdentifier(name)} WITH (FORCE)`);
|
|
16178
|
+
}
|
|
16179
|
+
await require_workspace$1.forgetState(root, require_workspace.TEST_STAGE);
|
|
16180
|
+
const { databases: _dropped, ...rest } = ready;
|
|
16181
|
+
await (0, node_fs_promises.writeFile)(readyPath, JSON.stringify(rest, null, 2));
|
|
16182
|
+
return recorded.names;
|
|
16183
|
+
}
|
|
16184
|
+
async function readReady(path) {
|
|
16185
|
+
try {
|
|
16186
|
+
return JSON.parse(await (0, node_fs_promises.readFile)(path, "utf-8"));
|
|
16187
|
+
} catch {
|
|
16188
|
+
return;
|
|
16189
|
+
}
|
|
16190
|
+
}
|
|
16191
|
+
/** A name recorded for the test stage without the test stage's suffix. */
|
|
16192
|
+
var NotATestDatabase = class extends Error {
|
|
16193
|
+
database;
|
|
16194
|
+
constructor(database) {
|
|
16195
|
+
super(`Refusing to drop '${database}': only the test stage's databases (named '…_${require_workspace.TEST_STAGE}') are dropped after a suite. Delete ${require_ready.TEST_READY_FILE} if it was edited by hand.`);
|
|
16196
|
+
this.database = database;
|
|
16197
|
+
this.name = "NotATestDatabase";
|
|
16198
|
+
}
|
|
16199
|
+
};
|
|
16200
|
+
//#endregion
|
|
15462
16201
|
//#region src/test/index.ts
|
|
15463
16202
|
/**
|
|
15464
16203
|
* Run tests with secrets, dependency URLs, and .env files loaded.
|
|
@@ -15467,6 +16206,10 @@ function printSummary(workspace, stage) {
|
|
|
15467
16206
|
async function testCommand(options = {}) {
|
|
15468
16207
|
const cwd = process.cwd();
|
|
15469
16208
|
const stage = options.stage ?? await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace.stages.local).catch(() => void 0);
|
|
16209
|
+
if (options.teardown) {
|
|
16210
|
+
await teardown(cwd);
|
|
16211
|
+
return;
|
|
16212
|
+
}
|
|
15470
16213
|
if (!stage) throw new NoStageToTest();
|
|
15471
16214
|
console.log(`\n🧪 Running tests with ${stage} environment...\n`);
|
|
15472
16215
|
if (options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP)) {
|
|
@@ -15475,6 +16218,7 @@ async function testCommand(options = {}) {
|
|
|
15475
16218
|
}
|
|
15476
16219
|
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
15477
16220
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
16221
|
+
if (!options.prepare) await teardown(cwd);
|
|
15478
16222
|
const result = await require_workspace$1.prepareEntryCredentials({
|
|
15479
16223
|
stage,
|
|
15480
16224
|
startDocker: !options.prepare,
|
|
@@ -15515,7 +16259,8 @@ async function testCommand(options = {}) {
|
|
|
15515
16259
|
const readyPath = (0, node_path.join)(cwd, require_ready.TEST_READY_FILE);
|
|
15516
16260
|
await (0, node_fs_promises.writeFile)(readyPath, JSON.stringify({
|
|
15517
16261
|
env: result.secretsJsonPath,
|
|
15518
|
-
...manifestPath ? { manifest: manifestPath } : {}
|
|
16262
|
+
...manifestPath ? { manifest: manifestPath } : {},
|
|
16263
|
+
...testDatabases(result.reconciled)
|
|
15519
16264
|
}, null, 2));
|
|
15520
16265
|
if (options.setup) {
|
|
15521
16266
|
console.log(` 🧪 Test stage ready: ${require_ready.TEST_READY_FILE}`);
|
|
@@ -15554,12 +16299,29 @@ async function testCommand(options = {}) {
|
|
|
15554
16299
|
});
|
|
15555
16300
|
});
|
|
15556
16301
|
}
|
|
16302
|
+
/** Drop what the last setup created — see `dropTestDatabases`. */
|
|
16303
|
+
async function teardown(cwd) {
|
|
16304
|
+
const dropped = await dropTestDatabases(cwd, await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace.root).catch(() => cwd));
|
|
16305
|
+
if (dropped.length > 0) console.log(` 🧹 Dropped ${dropped.join(", ")}`);
|
|
16306
|
+
}
|
|
16307
|
+
/**
|
|
16308
|
+
* The test stage's Postgres databases, to drop when the suite ends — those
|
|
16309
|
+
* the plan provisions, on the port the shared container answers.
|
|
16310
|
+
*/
|
|
16311
|
+
function testDatabases(reconciled) {
|
|
16312
|
+
const port = reconciled?.ports[require_workspace$1.primaryPortKey("postgres")];
|
|
16313
|
+
const names = (reconciled?.plan.resources ?? []).filter((resource) => resource.kind === "database" && resource.provisions && resource.container === "postgres").map((resource) => resource.name);
|
|
16314
|
+
return port !== void 0 && names.length > 0 ? { databases: {
|
|
16315
|
+
port,
|
|
16316
|
+
names
|
|
16317
|
+
} } : {};
|
|
16318
|
+
}
|
|
15557
16319
|
/**
|
|
15558
16320
|
* Migrate the test stage just reconciled, then seed it: a test run never
|
|
15559
16321
|
* starts without the reference data the app needs.
|
|
15560
16322
|
*/
|
|
15561
16323
|
async function migrateTestStage(workspace, env) {
|
|
15562
|
-
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-
|
|
16324
|
+
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-DKn83skp.cjs"));
|
|
15563
16325
|
const { discover } = await Promise.resolve().then(() => require("./discover-CroTIm0K.cjs")).then((n) => n.discover_exports);
|
|
15564
16326
|
const sources = {};
|
|
15565
16327
|
const manifest = await discover({
|
|
@@ -15959,7 +16721,7 @@ program.command("build").description("Build handlers and the manifest from decla
|
|
|
15959
16721
|
process.exit(1);
|
|
15960
16722
|
}
|
|
15961
16723
|
});
|
|
15962
|
-
program.command("dev").description("Reconcile constructs and start the dev server with automatic reload").option("-p, --port <port>", "Port to run the development server on").option("--entry <file>", "Entry file to run (bypasses gkm config)").option("--watch", "Watch for file changes (default: true with --entry)").option("--no-watch", "Disable file watching").option("--enable-openapi", "Enable OpenAPI documentation for development server", true).option("--migrate", "Apply pending migrations before the apps start").option("--seed", "Migrate, then run the seeds, before the apps start").action(async (options) => {
|
|
16724
|
+
program.command("dev").description("Reconcile constructs and start the dev server with automatic reload").option("-p, --port <port>", "Port to run the development server on").option("--entry <file>", "Entry file to run (bypasses gkm config)").option("--watch", "Watch for file changes (default: true with --entry)").option("--no-watch", "Disable file watching").option("--enable-openapi", "Enable OpenAPI documentation for development server", true).option("--migrate", "Apply pending migrations before the apps start").option("--seed", "Migrate, then run the seeds, before the apps start").option("--fake", "Call each external API’s fake (test/fakes/<id>.ts) instead of the provider").action(async (options) => {
|
|
15963
16725
|
try {
|
|
15964
16726
|
const globalOptions = program.opts();
|
|
15965
16727
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -15970,7 +16732,8 @@ program.command("dev").description("Reconcile constructs and start the dev serve
|
|
|
15970
16732
|
entry: options.entry,
|
|
15971
16733
|
watch: options.watch,
|
|
15972
16734
|
migrate: options.migrate,
|
|
15973
|
-
seed: options.seed
|
|
16735
|
+
seed: options.seed,
|
|
16736
|
+
fake: options.fake
|
|
15974
16737
|
});
|
|
15975
16738
|
} catch (error) {
|
|
15976
16739
|
console.error(formatError(error));
|
|
@@ -15987,7 +16750,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
|
|
|
15987
16750
|
process.exit(1);
|
|
15988
16751
|
}
|
|
15989
16752
|
});
|
|
15990
|
-
program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from (default: stages.local)").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").option("--auto-setup", "Generate a fresh stage (secrets + key) from the workspace config when none exists (for CI; also via GKM_AUTO_SETUP)").option("--prepare", "Write the test manifest and #test harness, then stop — for a typecheck that runs before the suite").option("--setup", "Reconcile, migrate and seed the test stage and write the harness, then stop — what @geekmidas/cli/vitest runs").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
|
|
16753
|
+
program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from (default: stages.local)").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").option("--auto-setup", "Generate a fresh stage (secrets + key) from the workspace config when none exists (for CI; also via GKM_AUTO_SETUP)").option("--prepare", "Write the test manifest and #test harness, then stop — for a typecheck that runs before the suite").option("--setup", "Reconcile, migrate and seed the test stage and write the harness, then stop — what @geekmidas/cli/vitest runs").option("--teardown", "Drop the test stage's databases the last setup created, then stop — what @geekmidas/cli/vitest runs when the suite ends").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
|
|
15991
16754
|
try {
|
|
15992
16755
|
const globalOptions = program.opts();
|
|
15993
16756
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -16004,7 +16767,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
16004
16767
|
try {
|
|
16005
16768
|
const globalOptions = program.opts();
|
|
16006
16769
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16007
|
-
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
16770
|
+
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-DKn83skp.cjs"));
|
|
16008
16771
|
await migrateCommand({
|
|
16009
16772
|
...options,
|
|
16010
16773
|
...construct ? { construct } : {}
|
|
@@ -16018,7 +16781,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
16018
16781
|
try {
|
|
16019
16782
|
const globalOptions = program.opts();
|
|
16020
16783
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16021
|
-
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
16784
|
+
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-DKn83skp.cjs"));
|
|
16022
16785
|
await seedCommand({
|
|
16023
16786
|
...options,
|
|
16024
16787
|
...construct ? { construct } : {}
|
|
@@ -16032,7 +16795,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
16032
16795
|
try {
|
|
16033
16796
|
const globalOptions = program.opts();
|
|
16034
16797
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16035
|
-
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
16798
|
+
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-DKn83skp.cjs"));
|
|
16036
16799
|
await migrationCommand({
|
|
16037
16800
|
construct,
|
|
16038
16801
|
...name ? { name } : {}
|
|
@@ -16156,57 +16919,28 @@ program.command("secrets:import").description("Import secrets from a JSON file")
|
|
|
16156
16919
|
process.exit(1);
|
|
16157
16920
|
}
|
|
16158
16921
|
});
|
|
16159
|
-
program.command("secrets:push").description("Send a deployed stage's secrets to its store (secrets.store)").requiredOption("--stage <stage>", "A deployed stage").option("--profile <profile>", "AWS profile for the stage's account").action(async (options) => {
|
|
16160
|
-
try {
|
|
16161
|
-
const globalOptions = program.opts();
|
|
16162
|
-
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16163
|
-
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16164
|
-
const { pushStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16165
|
-
const { workspace } = await loadWorkspaceConfig();
|
|
16166
|
-
const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16167
|
-
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
16168
|
-
console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
|
|
16169
|
-
} catch (error) {
|
|
16170
|
-
console.error(formatError(error));
|
|
16171
|
-
process.exit(1);
|
|
16172
|
-
}
|
|
16173
|
-
});
|
|
16174
|
-
program.command("secrets:pull").description("Bring a deployed stage's secrets from its store into the local copy").requiredOption("--stage <stage>", "A deployed stage").option("--profile <profile>", "AWS profile for the stage's account").action(async (options) => {
|
|
16175
|
-
try {
|
|
16176
|
-
const globalOptions = program.opts();
|
|
16177
|
-
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16178
|
-
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16179
|
-
const { pullStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16180
|
-
const { workspace } = await loadWorkspaceConfig();
|
|
16181
|
-
const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16182
|
-
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
16183
|
-
console.log(`\n✓ Secrets pulled for stage "${options.stage}"`);
|
|
16184
|
-
} catch (error) {
|
|
16185
|
-
console.error(formatError(error));
|
|
16186
|
-
process.exit(1);
|
|
16187
|
-
}
|
|
16188
|
-
});
|
|
16189
16922
|
program.command("secrets:reconcile").description("Backfill missing custom secrets from workspace config").option("--stage <stage>", "Stage name (default: stages.local)").action(async (options) => {
|
|
16190
16923
|
try {
|
|
16191
16924
|
const globalOptions = program.opts();
|
|
16192
16925
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16193
16926
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16194
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
16195
|
-
const {
|
|
16927
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-CuCQBa5-.cjs"));
|
|
16928
|
+
const { secretsStoreFor } = await Promise.resolve().then(() => require("./store-DQgxT_WR.cjs")).then((n) => n.store_exports);
|
|
16196
16929
|
const { workspace } = await loadWorkspaceConfig();
|
|
16197
16930
|
const stage = options.stage ?? workspace.stages.local;
|
|
16198
|
-
const
|
|
16931
|
+
const store = await secretsStoreFor(workspace, stage);
|
|
16932
|
+
const secrets = await store.read(stage);
|
|
16199
16933
|
if (!secrets) {
|
|
16200
16934
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16201
16935
|
process.exit(1);
|
|
16202
16936
|
}
|
|
16203
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-
|
|
16937
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-gfIrSEUp.cjs")).then((n) => n.workspace_exports);
|
|
16204
16938
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16205
16939
|
if (!result) {
|
|
16206
16940
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
16207
16941
|
return;
|
|
16208
16942
|
}
|
|
16209
|
-
await
|
|
16943
|
+
await store.write(stage, result.secrets);
|
|
16210
16944
|
console.log(`\n✓ Reconciled ${result.addedKeys.length} missing secret(s) for stage "${stage}":`);
|
|
16211
16945
|
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
16212
16946
|
} catch (error) {
|
|
@@ -16259,7 +16993,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16259
16993
|
try {
|
|
16260
16994
|
const globalOptions = program.opts();
|
|
16261
16995
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16262
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
16996
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-BvK076ph.cjs"));
|
|
16263
16997
|
await deployGithubCommand(options);
|
|
16264
16998
|
} catch (error) {
|
|
16265
16999
|
console.error(formatError(error));
|
|
@@ -16390,9 +17124,8 @@ Object.defineProperty(exports, "encryption_exports", {
|
|
|
16390
17124
|
return encryption_exports;
|
|
16391
17125
|
}
|
|
16392
17126
|
});
|
|
17127
|
+
exports.generateFullstackCustomSecrets = generateFullstackCustomSecrets;
|
|
16393
17128
|
exports.getHostingerToken = getHostingerToken;
|
|
16394
|
-
exports.isRemoteStore = isRemoteStore;
|
|
16395
|
-
exports.pushStageSecrets = pushStageSecrets;
|
|
16396
17129
|
exports.slug = slug;
|
|
16397
17130
|
exports.stamp = stamp;
|
|
16398
17131
|
|