@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.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
3
3
|
import { c as isPartitionedRoutes, i as ConstructGenerator, t as discover } from "./discover-CMxesskG.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-D1S2r0JN.mjs";
|
|
5
|
+
import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-DOmIhiau.mjs";
|
|
6
|
+
import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword, t as initStageSecrets } from "./storage-BzaUxmqe.mjs";
|
|
7
|
+
import { a as getKeyPath, i as FileSecretsStore, n as secretsStoreFor } from "./store-DeGATCS-.mjs";
|
|
8
|
+
import { C as findAvailablePort, D as prepareEntryCredentials, E as loadSecretsForApp, S as createEntryWrapper, T as loadEnvFiles, _ as cacheBackendFor, a as reconcileWorkspace, b as storageBackendFor, c as LOCAL_CA_PATH, d as quoteIdentifier, f as pgClient, g as primaryPortKey, h as fakeKey, i as derivedContainers, l as forgetState, m as rootSite, n as backendsOf, p as resourceName, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as eventsBackendFor, w as isPortAvailable, x as createCredentialsPreload, y as providerOf } from "./workspace-Lm6dv2nz.mjs";
|
|
8
9
|
import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-hDINScnz.mjs";
|
|
9
10
|
import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
|
|
10
|
-
import { a as writeTestHarness, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as cacheBackendsIn, s as driversFor, t as TEST_READY_ENV } from "./ready-
|
|
11
|
+
import { a as writeTestHarness, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as cacheBackendsIn, s as driversFor, t as TEST_READY_ENV } from "./ready-BTm_sMNU.mjs";
|
|
11
12
|
import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
|
|
12
|
-
import { a as createStageSecrets, c as generateServiceCredentials, i as writeDockerEnvFromSecrets, l as rotateServicePassword, o as generateConnectionUrls, r as generateFullstackCustomSecrets, s as generateSecurePassword, t as reconcileMissingSecrets } from "./reconcile-yNq6Shau.mjs";
|
|
13
13
|
import { createRequire } from "node:module";
|
|
14
14
|
import { Command } from "commander";
|
|
15
15
|
import { stdin, stdout } from "node:process";
|
|
@@ -19,7 +19,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
|
|
21
21
|
import { execFile, execSync, spawn } from "node:child_process";
|
|
22
|
-
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, kebabCase, migrationFolder, mobileOrigins, provideKey, provisionOrder, schemeBase, scopedName, serviceKey } from "@geekmidas/manifest";
|
|
22
|
+
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, externalApiUrl, kebabCase, migrationFolder, mobileOrigins, provideKey, provisionOrder, schemeBase, scopedName, serviceKey } from "@geekmidas/manifest";
|
|
23
23
|
import fg from "fast-glob";
|
|
24
24
|
import chokidar from "chokidar";
|
|
25
25
|
import { createCipheriv, createHash, randomBytes } from "node:crypto";
|
|
@@ -30,6 +30,10 @@ import { parse as parse$1, parseDocument } from "yaml";
|
|
|
30
30
|
import { promisify } from "node:util";
|
|
31
31
|
import { cacheTableStatements } from "@geekmidas/cache/postgres";
|
|
32
32
|
import prompts from "prompts";
|
|
33
|
+
import { createServer } from "http";
|
|
34
|
+
import { Http2ServerRequest } from "http2";
|
|
35
|
+
import { Readable } from "stream";
|
|
36
|
+
import crypto from "crypto";
|
|
33
37
|
import { Cron } from "@geekmidas/constructs/crons";
|
|
34
38
|
import { Function } from "@geekmidas/constructs/functions";
|
|
35
39
|
import { Queue } from "@geekmidas/constructs/queue";
|
|
@@ -37,7 +41,7 @@ import { Subscriber } from "@geekmidas/constructs/subscribers";
|
|
|
37
41
|
import { Topic } from "@geekmidas/constructs/topic";
|
|
38
42
|
import { lookup } from "node:dns/promises";
|
|
39
43
|
//#region package.json
|
|
40
|
-
var version = "10.0.0-alpha.
|
|
44
|
+
var version = "10.0.0-alpha.38";
|
|
41
45
|
//#endregion
|
|
42
46
|
//#region src/auth/credentials.ts
|
|
43
47
|
/**
|
|
@@ -578,6 +582,485 @@ var UnsupportedPlatform = class extends Error {
|
|
|
578
582
|
}
|
|
579
583
|
};
|
|
580
584
|
//#endregion
|
|
585
|
+
//#region ../../node_modules/.pnpm/@hono+node-server@1.19.5_hono@4.13.8/node_modules/@hono/node-server/dist/index.mjs
|
|
586
|
+
var RequestError = class extends Error {
|
|
587
|
+
constructor(message, options) {
|
|
588
|
+
super(message, options);
|
|
589
|
+
this.name = "RequestError";
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
var toRequestError = (e) => {
|
|
593
|
+
if (e instanceof RequestError) return e;
|
|
594
|
+
return new RequestError(e.message, { cause: e });
|
|
595
|
+
};
|
|
596
|
+
var GlobalRequest = global.Request;
|
|
597
|
+
var Request = class extends GlobalRequest {
|
|
598
|
+
constructor(input, options) {
|
|
599
|
+
if (typeof input === "object" && getRequestCache in input) input = input[getRequestCache]();
|
|
600
|
+
if (typeof options?.body?.getReader !== "undefined") options.duplex ??= "half";
|
|
601
|
+
super(input, options);
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
var newHeadersFromIncoming = (incoming) => {
|
|
605
|
+
const headerRecord = [];
|
|
606
|
+
const rawHeaders = incoming.rawHeaders;
|
|
607
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
608
|
+
const { [i]: key, [i + 1]: value } = rawHeaders;
|
|
609
|
+
if (key.charCodeAt(0) !== 58) headerRecord.push([key, value]);
|
|
610
|
+
}
|
|
611
|
+
return new Headers(headerRecord);
|
|
612
|
+
};
|
|
613
|
+
var wrapBodyStream = Symbol("wrapBodyStream");
|
|
614
|
+
var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
615
|
+
const init = {
|
|
616
|
+
method,
|
|
617
|
+
headers,
|
|
618
|
+
signal: abortController.signal
|
|
619
|
+
};
|
|
620
|
+
if (method === "TRACE") {
|
|
621
|
+
init.method = "GET";
|
|
622
|
+
const req = new Request(url, init);
|
|
623
|
+
Object.defineProperty(req, "method", { get() {
|
|
624
|
+
return "TRACE";
|
|
625
|
+
} });
|
|
626
|
+
return req;
|
|
627
|
+
}
|
|
628
|
+
if (!(method === "GET" || method === "HEAD")) {
|
|
629
|
+
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) init.body = new ReadableStream({ start(controller) {
|
|
630
|
+
controller.enqueue(incoming.rawBody);
|
|
631
|
+
controller.close();
|
|
632
|
+
} });
|
|
633
|
+
else if (incoming[wrapBodyStream]) {
|
|
634
|
+
let reader;
|
|
635
|
+
init.body = new ReadableStream({ async pull(controller) {
|
|
636
|
+
try {
|
|
637
|
+
reader ||= Readable.toWeb(incoming).getReader();
|
|
638
|
+
const { done, value } = await reader.read();
|
|
639
|
+
if (done) controller.close();
|
|
640
|
+
else controller.enqueue(value);
|
|
641
|
+
} catch (error) {
|
|
642
|
+
controller.error(error);
|
|
643
|
+
}
|
|
644
|
+
} });
|
|
645
|
+
} else init.body = Readable.toWeb(incoming);
|
|
646
|
+
}
|
|
647
|
+
return new Request(url, init);
|
|
648
|
+
};
|
|
649
|
+
var getRequestCache = Symbol("getRequestCache");
|
|
650
|
+
var requestCache = Symbol("requestCache");
|
|
651
|
+
var incomingKey = Symbol("incomingKey");
|
|
652
|
+
var urlKey = Symbol("urlKey");
|
|
653
|
+
var headersKey = Symbol("headersKey");
|
|
654
|
+
var abortControllerKey = Symbol("abortControllerKey");
|
|
655
|
+
var requestPrototype = {
|
|
656
|
+
get method() {
|
|
657
|
+
return this[incomingKey].method || "GET";
|
|
658
|
+
},
|
|
659
|
+
get url() {
|
|
660
|
+
return this[urlKey];
|
|
661
|
+
},
|
|
662
|
+
get headers() {
|
|
663
|
+
return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
|
|
664
|
+
},
|
|
665
|
+
[Symbol("getAbortController")]() {
|
|
666
|
+
this[getRequestCache]();
|
|
667
|
+
return this[abortControllerKey];
|
|
668
|
+
},
|
|
669
|
+
[getRequestCache]() {
|
|
670
|
+
this[abortControllerKey] ||= new AbortController();
|
|
671
|
+
return this[requestCache] ||= newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], this[abortControllerKey]);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
[
|
|
675
|
+
"body",
|
|
676
|
+
"bodyUsed",
|
|
677
|
+
"cache",
|
|
678
|
+
"credentials",
|
|
679
|
+
"destination",
|
|
680
|
+
"integrity",
|
|
681
|
+
"mode",
|
|
682
|
+
"redirect",
|
|
683
|
+
"referrer",
|
|
684
|
+
"referrerPolicy",
|
|
685
|
+
"signal",
|
|
686
|
+
"keepalive"
|
|
687
|
+
].forEach((k) => {
|
|
688
|
+
Object.defineProperty(requestPrototype, k, { get() {
|
|
689
|
+
return this[getRequestCache]()[k];
|
|
690
|
+
} });
|
|
691
|
+
});
|
|
692
|
+
[
|
|
693
|
+
"arrayBuffer",
|
|
694
|
+
"blob",
|
|
695
|
+
"clone",
|
|
696
|
+
"formData",
|
|
697
|
+
"json",
|
|
698
|
+
"text"
|
|
699
|
+
].forEach((k) => {
|
|
700
|
+
Object.defineProperty(requestPrototype, k, { value: function() {
|
|
701
|
+
return this[getRequestCache]()[k]();
|
|
702
|
+
} });
|
|
703
|
+
});
|
|
704
|
+
Object.setPrototypeOf(requestPrototype, Request.prototype);
|
|
705
|
+
var newRequest = (incoming, defaultHostname) => {
|
|
706
|
+
const req = Object.create(requestPrototype);
|
|
707
|
+
req[incomingKey] = incoming;
|
|
708
|
+
const incomingUrl = incoming.url || "";
|
|
709
|
+
if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
|
|
710
|
+
if (incoming instanceof Http2ServerRequest) throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
|
|
711
|
+
try {
|
|
712
|
+
req[urlKey] = new URL(incomingUrl).href;
|
|
713
|
+
} catch (e) {
|
|
714
|
+
throw new RequestError("Invalid absolute URL", { cause: e });
|
|
715
|
+
}
|
|
716
|
+
return req;
|
|
717
|
+
}
|
|
718
|
+
const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
|
|
719
|
+
if (!host) throw new RequestError("Missing host header");
|
|
720
|
+
let scheme;
|
|
721
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
722
|
+
scheme = incoming.scheme;
|
|
723
|
+
if (!(scheme === "http" || scheme === "https")) throw new RequestError("Unsupported scheme");
|
|
724
|
+
} else scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
|
|
725
|
+
const url = new URL(`${scheme}://${host}${incomingUrl}`);
|
|
726
|
+
if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) throw new RequestError("Invalid host header");
|
|
727
|
+
req[urlKey] = url.href;
|
|
728
|
+
return req;
|
|
729
|
+
};
|
|
730
|
+
var responseCache = Symbol("responseCache");
|
|
731
|
+
var getResponseCache = Symbol("getResponseCache");
|
|
732
|
+
var cacheKey = Symbol("cache");
|
|
733
|
+
var GlobalResponse = global.Response;
|
|
734
|
+
var Response2 = class _Response {
|
|
735
|
+
#body;
|
|
736
|
+
#init;
|
|
737
|
+
[getResponseCache]() {
|
|
738
|
+
delete this[cacheKey];
|
|
739
|
+
return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
|
|
740
|
+
}
|
|
741
|
+
constructor(body, init) {
|
|
742
|
+
let headers;
|
|
743
|
+
this.#body = body;
|
|
744
|
+
if (init instanceof _Response) {
|
|
745
|
+
const cachedGlobalResponse = init[responseCache];
|
|
746
|
+
if (cachedGlobalResponse) {
|
|
747
|
+
this.#init = cachedGlobalResponse;
|
|
748
|
+
this[getResponseCache]();
|
|
749
|
+
return;
|
|
750
|
+
} else {
|
|
751
|
+
this.#init = init.#init;
|
|
752
|
+
headers = new Headers(init.#init.headers);
|
|
753
|
+
}
|
|
754
|
+
} else this.#init = init;
|
|
755
|
+
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
|
|
756
|
+
headers ||= init?.headers || { "content-type": "text/plain; charset=UTF-8" };
|
|
757
|
+
this[cacheKey] = [
|
|
758
|
+
init?.status || 200,
|
|
759
|
+
body,
|
|
760
|
+
headers
|
|
761
|
+
];
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
get headers() {
|
|
765
|
+
const cache = this[cacheKey];
|
|
766
|
+
if (cache) {
|
|
767
|
+
if (!(cache[2] instanceof Headers)) cache[2] = new Headers(cache[2]);
|
|
768
|
+
return cache[2];
|
|
769
|
+
}
|
|
770
|
+
return this[getResponseCache]().headers;
|
|
771
|
+
}
|
|
772
|
+
get status() {
|
|
773
|
+
return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
|
|
774
|
+
}
|
|
775
|
+
get ok() {
|
|
776
|
+
const status = this.status;
|
|
777
|
+
return status >= 200 && status < 300;
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
[
|
|
781
|
+
"body",
|
|
782
|
+
"bodyUsed",
|
|
783
|
+
"redirected",
|
|
784
|
+
"statusText",
|
|
785
|
+
"trailers",
|
|
786
|
+
"type",
|
|
787
|
+
"url"
|
|
788
|
+
].forEach((k) => {
|
|
789
|
+
Object.defineProperty(Response2.prototype, k, { get() {
|
|
790
|
+
return this[getResponseCache]()[k];
|
|
791
|
+
} });
|
|
792
|
+
});
|
|
793
|
+
[
|
|
794
|
+
"arrayBuffer",
|
|
795
|
+
"blob",
|
|
796
|
+
"clone",
|
|
797
|
+
"formData",
|
|
798
|
+
"json",
|
|
799
|
+
"text"
|
|
800
|
+
].forEach((k) => {
|
|
801
|
+
Object.defineProperty(Response2.prototype, k, { value: function() {
|
|
802
|
+
return this[getResponseCache]()[k]();
|
|
803
|
+
} });
|
|
804
|
+
});
|
|
805
|
+
Object.setPrototypeOf(Response2, GlobalResponse);
|
|
806
|
+
Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
|
|
807
|
+
async function readWithoutBlocking(readPromise) {
|
|
808
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
|
|
809
|
+
}
|
|
810
|
+
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
811
|
+
const cancel = (error) => {
|
|
812
|
+
reader.cancel(error).catch(() => {});
|
|
813
|
+
};
|
|
814
|
+
writable.on("close", cancel);
|
|
815
|
+
writable.on("error", cancel);
|
|
816
|
+
(currentReadPromise ?? reader.read()).then(flow, handleStreamError);
|
|
817
|
+
return reader.closed.finally(() => {
|
|
818
|
+
writable.off("close", cancel);
|
|
819
|
+
writable.off("error", cancel);
|
|
820
|
+
});
|
|
821
|
+
function handleStreamError(error) {
|
|
822
|
+
if (error) writable.destroy(error);
|
|
823
|
+
}
|
|
824
|
+
function onDrain() {
|
|
825
|
+
reader.read().then(flow, handleStreamError);
|
|
826
|
+
}
|
|
827
|
+
function flow({ done, value }) {
|
|
828
|
+
try {
|
|
829
|
+
if (done) writable.end();
|
|
830
|
+
else if (!writable.write(value)) writable.once("drain", onDrain);
|
|
831
|
+
else return reader.read().then(flow, handleStreamError);
|
|
832
|
+
} catch (e) {
|
|
833
|
+
handleStreamError(e);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function writeFromReadableStream(stream, writable) {
|
|
838
|
+
if (stream.locked) throw new TypeError("ReadableStream is locked.");
|
|
839
|
+
else if (writable.destroyed) return;
|
|
840
|
+
return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
|
|
841
|
+
}
|
|
842
|
+
var buildOutgoingHttpHeaders = (headers) => {
|
|
843
|
+
const res = {};
|
|
844
|
+
if (!(headers instanceof Headers)) headers = new Headers(headers ?? void 0);
|
|
845
|
+
const cookies = [];
|
|
846
|
+
for (const [k, v] of headers) if (k === "set-cookie") cookies.push(v);
|
|
847
|
+
else res[k] = v;
|
|
848
|
+
if (cookies.length > 0) res["set-cookie"] = cookies;
|
|
849
|
+
res["content-type"] ??= "text/plain; charset=UTF-8";
|
|
850
|
+
return res;
|
|
851
|
+
};
|
|
852
|
+
var X_ALREADY_SENT = "x-hono-already-sent";
|
|
853
|
+
var webFetch = global.fetch;
|
|
854
|
+
if (typeof global.crypto === "undefined") global.crypto = crypto;
|
|
855
|
+
global.fetch = (info, init) => {
|
|
856
|
+
init = {
|
|
857
|
+
compress: false,
|
|
858
|
+
...init
|
|
859
|
+
};
|
|
860
|
+
return webFetch(info, init);
|
|
861
|
+
};
|
|
862
|
+
var outgoingEnded = Symbol("outgoingEnded");
|
|
863
|
+
var handleRequestError = () => new Response(null, { status: 400 });
|
|
864
|
+
var handleFetchError = (e) => new Response(null, { status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500 });
|
|
865
|
+
var handleResponseError = (e, outgoing) => {
|
|
866
|
+
const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
|
|
867
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") console.info("The user aborted a request.");
|
|
868
|
+
else {
|
|
869
|
+
console.error(e);
|
|
870
|
+
if (!outgoing.headersSent) outgoing.writeHead(500, { "Content-Type": "text/plain" });
|
|
871
|
+
outgoing.end(`Error: ${err.message}`);
|
|
872
|
+
outgoing.destroy(err);
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
var flushHeaders = (outgoing) => {
|
|
876
|
+
if ("flushHeaders" in outgoing && outgoing.writable) outgoing.flushHeaders();
|
|
877
|
+
};
|
|
878
|
+
var responseViaCache = async (res, outgoing) => {
|
|
879
|
+
let [status, body, header] = res[cacheKey];
|
|
880
|
+
if (header instanceof Headers) header = buildOutgoingHttpHeaders(header);
|
|
881
|
+
if (typeof body === "string") header["Content-Length"] = Buffer.byteLength(body);
|
|
882
|
+
else if (body instanceof Uint8Array) header["Content-Length"] = body.byteLength;
|
|
883
|
+
else if (body instanceof Blob) header["Content-Length"] = body.size;
|
|
884
|
+
outgoing.writeHead(status, header);
|
|
885
|
+
if (typeof body === "string" || body instanceof Uint8Array) outgoing.end(body);
|
|
886
|
+
else if (body instanceof Blob) outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
887
|
+
else {
|
|
888
|
+
flushHeaders(outgoing);
|
|
889
|
+
await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
|
|
890
|
+
}
|
|
891
|
+
outgoing[outgoingEnded]?.();
|
|
892
|
+
};
|
|
893
|
+
var isPromise = (res) => typeof res.then === "function";
|
|
894
|
+
var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
895
|
+
if (isPromise(res)) {
|
|
896
|
+
if (options.errorHandler) try {
|
|
897
|
+
res = await res;
|
|
898
|
+
} catch (err) {
|
|
899
|
+
const errRes = await options.errorHandler(err);
|
|
900
|
+
if (!errRes) return;
|
|
901
|
+
res = errRes;
|
|
902
|
+
}
|
|
903
|
+
else res = await res.catch(handleFetchError);
|
|
904
|
+
}
|
|
905
|
+
if (cacheKey in res) return responseViaCache(res, outgoing);
|
|
906
|
+
const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
|
|
907
|
+
if (res.body) {
|
|
908
|
+
const reader = res.body.getReader();
|
|
909
|
+
const values = [];
|
|
910
|
+
let done = false;
|
|
911
|
+
let currentReadPromise = void 0;
|
|
912
|
+
if (resHeaderRecord["transfer-encoding"] !== "chunked") {
|
|
913
|
+
let maxReadCount = 2;
|
|
914
|
+
for (let i = 0; i < maxReadCount; i++) {
|
|
915
|
+
currentReadPromise ||= reader.read();
|
|
916
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
917
|
+
console.error(e);
|
|
918
|
+
done = true;
|
|
919
|
+
});
|
|
920
|
+
if (!chunk) {
|
|
921
|
+
if (i === 1) {
|
|
922
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
923
|
+
maxReadCount = 3;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
currentReadPromise = void 0;
|
|
929
|
+
if (chunk.value) values.push(chunk.value);
|
|
930
|
+
if (chunk.done) {
|
|
931
|
+
done = true;
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (done && !("content-length" in resHeaderRecord)) resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
|
|
936
|
+
}
|
|
937
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
938
|
+
values.forEach((value) => {
|
|
939
|
+
outgoing.write(value);
|
|
940
|
+
});
|
|
941
|
+
if (done) outgoing.end();
|
|
942
|
+
else {
|
|
943
|
+
if (values.length === 0) flushHeaders(outgoing);
|
|
944
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
|
|
945
|
+
}
|
|
946
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
|
|
947
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
948
|
+
outgoing.end();
|
|
949
|
+
}
|
|
950
|
+
outgoing[outgoingEnded]?.();
|
|
951
|
+
};
|
|
952
|
+
var getRequestListener = (fetchCallback, options = {}) => {
|
|
953
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
954
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request) {
|
|
955
|
+
Object.defineProperty(global, "Request", { value: Request });
|
|
956
|
+
Object.defineProperty(global, "Response", { value: Response2 });
|
|
957
|
+
}
|
|
958
|
+
return async (incoming, outgoing) => {
|
|
959
|
+
let res, req;
|
|
960
|
+
try {
|
|
961
|
+
req = newRequest(incoming, options.hostname);
|
|
962
|
+
let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
|
|
963
|
+
if (!incomingEnded) {
|
|
964
|
+
incoming[wrapBodyStream] = true;
|
|
965
|
+
incoming.on("end", () => {
|
|
966
|
+
incomingEnded = true;
|
|
967
|
+
});
|
|
968
|
+
if (incoming instanceof Http2ServerRequest) outgoing[outgoingEnded] = () => {
|
|
969
|
+
if (!incomingEnded) setTimeout(() => {
|
|
970
|
+
if (!incomingEnded) setTimeout(() => {
|
|
971
|
+
incoming.destroy();
|
|
972
|
+
outgoing.destroy();
|
|
973
|
+
});
|
|
974
|
+
});
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
outgoing.on("close", () => {
|
|
978
|
+
if (req[abortControllerKey]) {
|
|
979
|
+
if (incoming.errored) req[abortControllerKey].abort(incoming.errored.toString());
|
|
980
|
+
else if (!outgoing.writableFinished) req[abortControllerKey].abort("Client connection prematurely closed.");
|
|
981
|
+
}
|
|
982
|
+
if (!incomingEnded) setTimeout(() => {
|
|
983
|
+
if (!incomingEnded) setTimeout(() => {
|
|
984
|
+
incoming.destroy();
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
res = fetchCallback(req, {
|
|
989
|
+
incoming,
|
|
990
|
+
outgoing
|
|
991
|
+
});
|
|
992
|
+
if (cacheKey in res) return responseViaCache(res, outgoing);
|
|
993
|
+
} catch (e) {
|
|
994
|
+
if (!res) {
|
|
995
|
+
if (options.errorHandler) {
|
|
996
|
+
res = await options.errorHandler(req ? e : toRequestError(e));
|
|
997
|
+
if (!res) return;
|
|
998
|
+
} else if (!req) res = handleRequestError();
|
|
999
|
+
else res = handleFetchError(e);
|
|
1000
|
+
} else return handleResponseError(e, outgoing);
|
|
1001
|
+
}
|
|
1002
|
+
try {
|
|
1003
|
+
return await responseViaResponseObject(res, outgoing, options);
|
|
1004
|
+
} catch (e) {
|
|
1005
|
+
return handleResponseError(e, outgoing);
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
};
|
|
1009
|
+
var createAdaptorServer = (options) => {
|
|
1010
|
+
const fetchCallback = options.fetch;
|
|
1011
|
+
const requestListener = getRequestListener(fetchCallback, {
|
|
1012
|
+
hostname: options.hostname,
|
|
1013
|
+
overrideGlobalObjects: options.overrideGlobalObjects,
|
|
1014
|
+
autoCleanupIncoming: options.autoCleanupIncoming
|
|
1015
|
+
});
|
|
1016
|
+
return (options.createServer || createServer)(options.serverOptions || {}, requestListener);
|
|
1017
|
+
};
|
|
1018
|
+
var serve = (options, listeningListener) => {
|
|
1019
|
+
const server = createAdaptorServer(options);
|
|
1020
|
+
server.listen(options?.port ?? 3e3, options.hostname, () => {
|
|
1021
|
+
const serverInfo = server.address();
|
|
1022
|
+
listeningListener && listeningListener(serverInfo);
|
|
1023
|
+
});
|
|
1024
|
+
return server;
|
|
1025
|
+
};
|
|
1026
|
+
//#endregion
|
|
1027
|
+
//#region src/dev/fakes.ts
|
|
1028
|
+
/**
|
|
1029
|
+
* External APIs' app fakes, served for `gkm dev`.
|
|
1030
|
+
*
|
|
1031
|
+
* An image fake runs with the other containers. An app fake is a fetch handler
|
|
1032
|
+
* in `test/fakes/<id>.ts`, and nothing runs it unless this does: each one
|
|
1033
|
+
* listens on the port its key was allocated, which is the port the stage's
|
|
1034
|
+
* `<ID>_URL` already names.
|
|
1035
|
+
*/
|
|
1036
|
+
/**
|
|
1037
|
+
* Serve every app fake on its allocated port. Image fakes are skipped — their
|
|
1038
|
+
* containers are already up.
|
|
1039
|
+
*/
|
|
1040
|
+
async function serveFakes(fakes, ports) {
|
|
1041
|
+
const served = [];
|
|
1042
|
+
for (const [id, local] of Object.entries(fakes)) {
|
|
1043
|
+
if (local.image) continue;
|
|
1044
|
+
const port = ports[fakeKey(id)];
|
|
1045
|
+
if (port === void 0) continue;
|
|
1046
|
+
const { handler } = (await import(pathToFileURL(local.file).href)).default;
|
|
1047
|
+
const server = serve({
|
|
1048
|
+
fetch: (request) => handler.fetch(request),
|
|
1049
|
+
port
|
|
1050
|
+
});
|
|
1051
|
+
served.push({
|
|
1052
|
+
id,
|
|
1053
|
+
port,
|
|
1054
|
+
server
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
return served;
|
|
1058
|
+
}
|
|
1059
|
+
/** Stop every served fake. */
|
|
1060
|
+
function closeFakes(served) {
|
|
1061
|
+
for (const { server } of served) server.close();
|
|
1062
|
+
}
|
|
1063
|
+
//#endregion
|
|
581
1064
|
//#region src/exec/index.ts
|
|
582
1065
|
const logger$11 = console;
|
|
583
1066
|
/** `gkm exec` was given nothing to run. */
|
|
@@ -729,41 +1212,28 @@ async function devCommand(options) {
|
|
|
729
1212
|
if (options.entry) return entryDevCommand(options);
|
|
730
1213
|
const defaultEnv = loadEnvFiles(".env");
|
|
731
1214
|
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
} catch {
|
|
757
|
-
const loadedConfig = await loadWorkspaceConfig();
|
|
758
|
-
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
759
|
-
config = loadedConfig.raw;
|
|
760
|
-
workspace = loadedConfig.workspace;
|
|
761
|
-
}
|
|
762
|
-
else {
|
|
763
|
-
const loadedConfig = await loadWorkspaceConfig();
|
|
764
|
-
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
765
|
-
config = loadedConfig.raw;
|
|
766
|
-
workspace = loadedConfig.workspace;
|
|
1215
|
+
const appConfig = await loadAppConfig().catch((error) => {
|
|
1216
|
+
if (error instanceof NotInAnApp || error instanceof NotABackendApp) return;
|
|
1217
|
+
throw error;
|
|
1218
|
+
});
|
|
1219
|
+
if (!appConfig) {
|
|
1220
|
+
const { workspace: everything } = await loadWorkspaceConfig();
|
|
1221
|
+
return workspaceDevCommand(everything, options);
|
|
1222
|
+
}
|
|
1223
|
+
const config = appConfig.gkmConfig;
|
|
1224
|
+
const appRoot = appConfig.appRoot;
|
|
1225
|
+
const secretsRoot = appConfig.workspaceRoot;
|
|
1226
|
+
const workspaceAppName = appConfig.appName;
|
|
1227
|
+
const workspaceAppPort = appConfig.app.port;
|
|
1228
|
+
const workspace = appConfig.workspace;
|
|
1229
|
+
if (appConfig.app.entry) {
|
|
1230
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1231
|
+
return entryDevCommand({
|
|
1232
|
+
...options,
|
|
1233
|
+
entry: appConfig.app.entry,
|
|
1234
|
+
port: workspaceAppPort,
|
|
1235
|
+
portExplicit: true
|
|
1236
|
+
});
|
|
767
1237
|
}
|
|
768
1238
|
if (config.env) {
|
|
769
1239
|
const { loaded, missing } = loadEnvFiles(config.env, appRoot);
|
|
@@ -793,9 +1263,12 @@ async function devCommand(options) {
|
|
|
793
1263
|
const runtime = config.runtime ?? "node";
|
|
794
1264
|
let secretsJsonPath;
|
|
795
1265
|
let publicUrl;
|
|
796
|
-
const appSecrets = await loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
|
|
1266
|
+
const appSecrets = await loadSecretsForApp(workspace ? await secretsStoreFor(workspace, config.stages.local) : new FileSecretsStore(secretsRoot), config.stages.local, workspaceAppName);
|
|
797
1267
|
if (workspace) {
|
|
798
|
-
const reconciled = await reconcileWorkspace(workspace, {
|
|
1268
|
+
const reconciled = await reconcileWorkspace(workspace, {
|
|
1269
|
+
stage: workspace.stages.local,
|
|
1270
|
+
...options.fake ? { fake: true } : {}
|
|
1271
|
+
});
|
|
799
1272
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
800
1273
|
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
801
1274
|
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
@@ -816,7 +1289,7 @@ async function devCommand(options) {
|
|
|
816
1289
|
devServer.publicUrl = publicUrl;
|
|
817
1290
|
await devServer.start();
|
|
818
1291
|
const hooksFile = (config.hooks?.server?.split("#"))?.[0];
|
|
819
|
-
const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) => p
|
|
1292
|
+
const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) => isAbsolute(p) ? relative(appRoot, p) : p.replace(/^\.\//, ""));
|
|
820
1293
|
const resolvedFiles = await fg(normalizedPatterns, {
|
|
821
1294
|
cwd: appRoot,
|
|
822
1295
|
absolute: false,
|
|
@@ -1040,10 +1513,8 @@ async function validateFrontendApps(workspace) {
|
|
|
1040
1513
|
async function loadDevSecrets(workspace) {
|
|
1041
1514
|
if (!workspace.secrets.enabled) return {};
|
|
1042
1515
|
const stage = workspace.stages.local;
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
if (secrets) return toEmbeddableSecrets(secrets);
|
|
1046
|
-
}
|
|
1516
|
+
const secrets = await (await secretsStoreFor(workspace, stage)).read(stage);
|
|
1517
|
+
if (secrets) return toEmbeddableSecrets(secrets);
|
|
1047
1518
|
return {};
|
|
1048
1519
|
}
|
|
1049
1520
|
/**
|
|
@@ -1077,7 +1548,10 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1077
1548
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1078
1549
|
}
|
|
1079
1550
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
1080
|
-
const reconciled = await reconcileWorkspace(workspace, {
|
|
1551
|
+
const reconciled = await reconcileWorkspace(workspace, {
|
|
1552
|
+
stage: workspace.stages.local,
|
|
1553
|
+
...options.fake ? { fake: true } : {}
|
|
1554
|
+
});
|
|
1081
1555
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
1082
1556
|
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
1083
1557
|
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
@@ -1086,10 +1560,12 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1086
1560
|
...rawSecrets,
|
|
1087
1561
|
...reconciled.env
|
|
1088
1562
|
};
|
|
1563
|
+
const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
|
|
1564
|
+
for (const { id, port } of fakes) logger$10.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
1089
1565
|
if (options.migrate || options.seed) {
|
|
1090
|
-
const { prepareDevDatabases } = await import("./migrate-
|
|
1566
|
+
const { prepareDevDatabases } = await import("./migrate-C2aYSZTn.mjs");
|
|
1091
1567
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1092
|
-
} else await import("./migrate-
|
|
1568
|
+
} else await import("./migrate-C2aYSZTn.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1093
1569
|
logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1094
1570
|
});
|
|
1095
1571
|
const appUrls = {};
|
|
@@ -1148,7 +1624,8 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1148
1624
|
...process.env,
|
|
1149
1625
|
...secretsEnv,
|
|
1150
1626
|
NODE_ENV: "development",
|
|
1151
|
-
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1627
|
+
...configPath ? { GKM_CONFIG_PATH: configPath } : {},
|
|
1628
|
+
...options.fake ? { [FAKE_ENV]: "1" } : {}
|
|
1152
1629
|
};
|
|
1153
1630
|
const scoped = options.app ? [options.app] : options.filter ? [] : Object.keys(workspace.apps);
|
|
1154
1631
|
const held = [];
|
|
@@ -1177,6 +1654,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1177
1654
|
if (isShuttingDown) return;
|
|
1178
1655
|
isShuttingDown = true;
|
|
1179
1656
|
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1657
|
+
closeFakes(fakes);
|
|
1180
1658
|
const pid = turboProcess.pid;
|
|
1181
1659
|
if (pid) try {
|
|
1182
1660
|
process.kill(-pid, "SIGTERM");
|
|
@@ -2809,7 +3287,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2809
3287
|
let masterKey;
|
|
2810
3288
|
if (context.production?.bundle && !skipBundle) {
|
|
2811
3289
|
logger$8.log(`\n📦 Bundling production server...`);
|
|
2812
|
-
const { bundleServer } = await import("./bundler
|
|
3290
|
+
const { bundleServer } = await import("./bundler--fyybf1N.mjs");
|
|
2813
3291
|
const allConstructs = [
|
|
2814
3292
|
...endpoints.map((e) => e.construct),
|
|
2815
3293
|
...functions.map((f) => f.construct),
|
|
@@ -3293,17 +3771,43 @@ const PROVISIONERS = {
|
|
|
3293
3771
|
return broker(declaration.id, context);
|
|
3294
3772
|
},
|
|
3295
3773
|
/**
|
|
3296
|
-
* A signing key, generated once and remembered.
|
|
3774
|
+
* A signing key, generated once and remembered in the stage's store.
|
|
3297
3775
|
*
|
|
3298
|
-
*
|
|
3299
|
-
*
|
|
3300
|
-
*
|
|
3776
|
+
* Random, never derived: a key computed from the project and stage names
|
|
3777
|
+
* signs sessions anyone reading the repo could forge. The deploy generates
|
|
3778
|
+
* it before provisioning (`withGeneratedSecrets`) and every later deploy
|
|
3779
|
+
* reads the same one, so live sessions survive a redeploy. Nothing is
|
|
3780
|
+
* created in Dokploy: a secret has no address.
|
|
3301
3781
|
*/
|
|
3302
3782
|
secret: async (declaration, context) => {
|
|
3303
3783
|
if (declaration.kind !== "secret") throw new WrongKind(declaration.kind);
|
|
3304
3784
|
const key = declaration.provides?.[0];
|
|
3305
3785
|
if (!key) return { provides: {} };
|
|
3306
|
-
return { provides: { [key]:
|
|
3786
|
+
return { provides: { [key]: supplied(declaration.id, key, context) } };
|
|
3787
|
+
},
|
|
3788
|
+
/**
|
|
3789
|
+
* A third party's credentials, as the stage was given them.
|
|
3790
|
+
*
|
|
3791
|
+
* Nothing is created and nothing derived: the value was issued by somebody
|
|
3792
|
+
* else. A stage that was never given one fails the deploy here, naming the
|
|
3793
|
+
* command that sets it — not the first request that needs it.
|
|
3794
|
+
*/
|
|
3795
|
+
credential: async (declaration, context) => {
|
|
3796
|
+
if (declaration.kind !== "credential") throw new WrongKind(declaration.kind);
|
|
3797
|
+
const key = provideKey(declaration.id, "credentials");
|
|
3798
|
+
return { provides: { [key]: supplied(declaration.id, key, context) } };
|
|
3799
|
+
},
|
|
3800
|
+
/**
|
|
3801
|
+
* An API somebody else runs: the URL this stage calls it at, and the
|
|
3802
|
+
* credentials it was issued for this stage.
|
|
3803
|
+
*/
|
|
3804
|
+
"external-api": async (declaration, context) => {
|
|
3805
|
+
if (declaration.kind !== "external-api") throw new WrongKind(declaration.kind);
|
|
3806
|
+
const credentials = provideKey(declaration.id, "credentials");
|
|
3807
|
+
return { provides: {
|
|
3808
|
+
[provideKey(declaration.id, "url")]: externalApiUrl(declaration, context.stage),
|
|
3809
|
+
[credentials]: supplied(declaration.id, credentials, context)
|
|
3810
|
+
} };
|
|
3307
3811
|
},
|
|
3308
3812
|
/**
|
|
3309
3813
|
* A surface, at the address whatever created its domain assigned.
|
|
@@ -3490,14 +3994,16 @@ function soleDatabase(context) {
|
|
|
3490
3994
|
return databases[0];
|
|
3491
3995
|
}
|
|
3492
3996
|
/**
|
|
3493
|
-
* A password derived from the project, the stage and the role.
|
|
3997
|
+
* A password derived from the stage's seed, the project, the stage and the role.
|
|
3494
3998
|
*
|
|
3495
|
-
* Derived rather than
|
|
3496
|
-
*
|
|
3497
|
-
*
|
|
3999
|
+
* Derived rather than stored per role so a redeploy hands the running
|
|
4000
|
+
* application the password it already has, and two projects never share one.
|
|
4001
|
+
* The seed is what makes it secret: random, generated once and kept in the
|
|
4002
|
+
* stage's store. Without it the inputs are all in the repo, and so was every
|
|
4003
|
+
* production password.
|
|
3498
4004
|
*/
|
|
3499
4005
|
function derivedPassword(context, role) {
|
|
3500
|
-
return createHash("sha256").update(`${context.project}:${context.stage}:role:${role}`).digest("base64url").slice(0, 32);
|
|
4006
|
+
return createHash("sha256").update(`${context.seed}:${context.project}:${context.stage}:role:${role}`).digest("base64url").slice(0, 32);
|
|
3501
4007
|
}
|
|
3502
4008
|
/** A connection string for a role on a cluster. */
|
|
3503
4009
|
function postgresUrl(role, password, host, database) {
|
|
@@ -3508,6 +4014,29 @@ function quoted(name) {
|
|
|
3508
4014
|
return name.split(".").map((part) => `"${part.replace(/"/g, "\"\"")}"`).join(".");
|
|
3509
4015
|
}
|
|
3510
4016
|
/** A provisioner was handed a declaration of the wrong kind. */
|
|
4017
|
+
/**
|
|
4018
|
+
* A value only the stage's own secrets can hold.
|
|
4019
|
+
*
|
|
4020
|
+
* @throws {MissingSuppliedSecret} when the stage was never given it.
|
|
4021
|
+
*/
|
|
4022
|
+
function supplied(id, key, context) {
|
|
4023
|
+
const value = context.supplied?.[key];
|
|
4024
|
+
if (value === void 0) throw new MissingSuppliedSecret(id, key, context.stage);
|
|
4025
|
+
return value;
|
|
4026
|
+
}
|
|
4027
|
+
/** A construct needs a value the stage was never given. */
|
|
4028
|
+
var MissingSuppliedSecret = class extends Error {
|
|
4029
|
+
id;
|
|
4030
|
+
key;
|
|
4031
|
+
stage;
|
|
4032
|
+
constructor(id, key, stage) {
|
|
4033
|
+
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}`);
|
|
4034
|
+
this.id = id;
|
|
4035
|
+
this.key = key;
|
|
4036
|
+
this.stage = stage;
|
|
4037
|
+
this.name = "MissingSuppliedSecret";
|
|
4038
|
+
}
|
|
4039
|
+
};
|
|
3511
4040
|
var WrongKind = class extends Error {
|
|
3512
4041
|
kind;
|
|
3513
4042
|
constructor(kind) {
|
|
@@ -3630,7 +4159,8 @@ async function provisionDeclared(options) {
|
|
|
3630
4159
|
events: eventsBackendFor(providerOf(workspace)),
|
|
3631
4160
|
storage: storageBackendFor(providerOf(workspace)),
|
|
3632
4161
|
addresses: surfaceAddresses(workspace, manifest, options.appUrls),
|
|
3633
|
-
|
|
4162
|
+
seed: options.seed,
|
|
4163
|
+
...options.supplied ? { supplied: options.supplied } : {},
|
|
3634
4164
|
deferred: [],
|
|
3635
4165
|
clusters: {}
|
|
3636
4166
|
};
|
|
@@ -3826,7 +4356,7 @@ async function createDnsProvider(options) {
|
|
|
3826
4356
|
if (isDnsProvider(config.provider)) return config.provider;
|
|
3827
4357
|
const provider = config.provider;
|
|
3828
4358
|
if (provider === "hostinger") {
|
|
3829
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
4359
|
+
const { HostingerProvider } = await import("./HostingerProvider-Bh6K7QYz.mjs");
|
|
3830
4360
|
return new HostingerProvider();
|
|
3831
4361
|
}
|
|
3832
4362
|
if (provider === "route53") {
|
|
@@ -4654,14 +5184,9 @@ exec "$@"
|
|
|
4654
5184
|
*/
|
|
4655
5185
|
function resolveDockerConfig(config) {
|
|
4656
5186
|
const docker = config.docker ?? {};
|
|
4657
|
-
let defaultImageName = "api";
|
|
4658
|
-
try {
|
|
4659
|
-
const pkg = __require(`${process.cwd()}/package.json`);
|
|
4660
|
-
if (pkg.name) defaultImageName = pkg.name.replace(/^@[^/]+\//, "");
|
|
4661
|
-
} catch {}
|
|
4662
5187
|
return {
|
|
4663
5188
|
registry: docker.registry ?? "",
|
|
4664
|
-
imageName: docker.imageName ??
|
|
5189
|
+
imageName: docker.imageName ?? config.name ?? "api",
|
|
4665
5190
|
baseImage: docker.baseImage ?? "node:22-alpine",
|
|
4666
5191
|
port: docker.port ?? 3e3
|
|
4667
5192
|
};
|
|
@@ -6254,6 +6779,55 @@ function validateEnvVars(requiredVars, context) {
|
|
|
6254
6779
|
};
|
|
6255
6780
|
}
|
|
6256
6781
|
//#endregion
|
|
6782
|
+
//#region src/deploy/generated.ts
|
|
6783
|
+
/**
|
|
6784
|
+
* The values a deployed stage generates once and keeps: what nothing can
|
|
6785
|
+
* provision and nobody else issues.
|
|
6786
|
+
*
|
|
6787
|
+
* - **A seed**, which salts every password a deploy derives — a database's
|
|
6788
|
+
* master and roles, a bucket's root user. Derived from repo facts alone
|
|
6789
|
+
* (project, stage, construct id), those passwords were computable by anyone
|
|
6790
|
+
* who could read the repo.
|
|
6791
|
+
* - **Each `secret` construct's value** — an auth server's signing secret —
|
|
6792
|
+
* random, rather than derived.
|
|
6793
|
+
*
|
|
6794
|
+
* Both are generated on the first deploy that needs them and written to the
|
|
6795
|
+
* stage's store, so every later deploy reads the same values: sessions survive
|
|
6796
|
+
* a redeploy, and a role's password does not move. A value set by hand with
|
|
6797
|
+
* `gkm secrets:set` is kept, never replaced.
|
|
6798
|
+
*/
|
|
6799
|
+
/**
|
|
6800
|
+
* The stage's secrets with its seed and every declared `secret`'s value
|
|
6801
|
+
* present, generating what is missing. Pure: the caller writes the result
|
|
6802
|
+
* back to the stage's store when `generated` is not empty.
|
|
6803
|
+
*/
|
|
6804
|
+
function withGeneratedSecrets(secrets, manifest) {
|
|
6805
|
+
const generated = [];
|
|
6806
|
+
const custom = { ...secrets.custom };
|
|
6807
|
+
for (const declaration of Object.values(manifest)) {
|
|
6808
|
+
if (declaration?.kind !== "secret") continue;
|
|
6809
|
+
const key = declaration.provides?.[0];
|
|
6810
|
+
if (!key || custom[key]) continue;
|
|
6811
|
+
custom[key] = random();
|
|
6812
|
+
generated.push(key);
|
|
6813
|
+
}
|
|
6814
|
+
const seed = secrets.seed ?? random();
|
|
6815
|
+
if (!secrets.seed) generated.push("seed");
|
|
6816
|
+
return {
|
|
6817
|
+
secrets: generated.length ? {
|
|
6818
|
+
...secrets,
|
|
6819
|
+
seed,
|
|
6820
|
+
custom,
|
|
6821
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6822
|
+
} : secrets,
|
|
6823
|
+
generated
|
|
6824
|
+
};
|
|
6825
|
+
}
|
|
6826
|
+
/** 256 bits, URL-safe. */
|
|
6827
|
+
function random() {
|
|
6828
|
+
return randomBytes(32).toString("base64url");
|
|
6829
|
+
}
|
|
6830
|
+
//#endregion
|
|
6257
6831
|
//#region src/deploy/StateProvider.ts
|
|
6258
6832
|
/**
|
|
6259
6833
|
* Check if value is a StateProvider implementation.
|
|
@@ -7072,10 +7646,13 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7072
7646
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
7073
7647
|
appsToDeployNames = dokployApps;
|
|
7074
7648
|
logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
7075
|
-
const
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7649
|
+
const secretsStore = await secretsStoreFor(workspace, stage);
|
|
7650
|
+
const stored = await secretsStore.read(stage);
|
|
7651
|
+
if (!stored) logger$4.log(` ⚠️ No secrets found for stage "${stage}"; starting them`);
|
|
7652
|
+
const { secrets: stageSecrets, generated } = withGeneratedSecrets(stored ?? initStageSecrets(stage), manifest);
|
|
7653
|
+
if (generated.length > 0) {
|
|
7654
|
+
await secretsStore.write(stage, stageSecrets);
|
|
7655
|
+
logger$4.log(` 🔑 Generated for "${stage}" (${secretsStore.name}): ${generated.join(", ")}`);
|
|
7079
7656
|
}
|
|
7080
7657
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
7081
7658
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
@@ -7203,6 +7780,8 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7203
7780
|
environmentId,
|
|
7204
7781
|
stage,
|
|
7205
7782
|
appUrls,
|
|
7783
|
+
supplied: stageSecrets.custom,
|
|
7784
|
+
seed: stageSecrets.seed,
|
|
7206
7785
|
manifest
|
|
7207
7786
|
});
|
|
7208
7787
|
declaredEnv = declared.env;
|
|
@@ -7963,6 +8542,172 @@ function printStateDetails(state) {
|
|
|
7963
8542
|
}
|
|
7964
8543
|
}
|
|
7965
8544
|
//#endregion
|
|
8545
|
+
//#region src/secrets/generator.ts
|
|
8546
|
+
/**
|
|
8547
|
+
* Generate a secure random password using URL-safe base64 characters.
|
|
8548
|
+
* @param length Password length (default: 32)
|
|
8549
|
+
*/
|
|
8550
|
+
function generateSecurePassword(length = 32) {
|
|
8551
|
+
return randomBytes(Math.ceil(length * 3 / 4)).toString("base64url").slice(0, length);
|
|
8552
|
+
}
|
|
8553
|
+
/** Default service configurations (localhost for local dev via Docker port mapping) */
|
|
8554
|
+
const SERVICE_DEFAULTS = {
|
|
8555
|
+
postgres: {
|
|
8556
|
+
host: "localhost",
|
|
8557
|
+
port: 5432,
|
|
8558
|
+
username: "app",
|
|
8559
|
+
database: "app"
|
|
8560
|
+
},
|
|
8561
|
+
redis: {
|
|
8562
|
+
host: "localhost",
|
|
8563
|
+
port: 6379,
|
|
8564
|
+
username: "default"
|
|
8565
|
+
},
|
|
8566
|
+
minio: {
|
|
8567
|
+
host: "localhost",
|
|
8568
|
+
port: 9e3,
|
|
8569
|
+
username: "app",
|
|
8570
|
+
bucket: "app"
|
|
8571
|
+
},
|
|
8572
|
+
mailpit: {
|
|
8573
|
+
host: "localhost",
|
|
8574
|
+
port: 1025,
|
|
8575
|
+
username: "app"
|
|
8576
|
+
},
|
|
8577
|
+
localstack: {
|
|
8578
|
+
host: "localhost",
|
|
8579
|
+
port: 4566,
|
|
8580
|
+
username: "localstack",
|
|
8581
|
+
region: "us-east-1"
|
|
8582
|
+
}
|
|
8583
|
+
};
|
|
8584
|
+
/** Default credentials for pgboss (not a Docker service, reuses postgres) */
|
|
8585
|
+
const PGBOSS_DEFAULTS = {
|
|
8586
|
+
host: "localhost",
|
|
8587
|
+
port: 5432,
|
|
8588
|
+
username: "pgboss",
|
|
8589
|
+
database: "app"
|
|
8590
|
+
};
|
|
8591
|
+
/**
|
|
8592
|
+
* Generate credentials for a specific service.
|
|
8593
|
+
*/
|
|
8594
|
+
function generateServiceCredentials(service) {
|
|
8595
|
+
return {
|
|
8596
|
+
...SERVICE_DEFAULTS[service],
|
|
8597
|
+
password: generateSecurePassword()
|
|
8598
|
+
};
|
|
8599
|
+
}
|
|
8600
|
+
/**
|
|
8601
|
+
* Generate credentials for multiple services.
|
|
8602
|
+
*/
|
|
8603
|
+
function generateServicesCredentials(services) {
|
|
8604
|
+
const result = {};
|
|
8605
|
+
for (const service of services) result[service] = generateServiceCredentials(service);
|
|
8606
|
+
return result;
|
|
8607
|
+
}
|
|
8608
|
+
/**
|
|
8609
|
+
* Generate connection URL for PostgreSQL.
|
|
8610
|
+
*/
|
|
8611
|
+
function generatePostgresUrl(creds) {
|
|
8612
|
+
const { username, password, host, port, database } = creds;
|
|
8613
|
+
return `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
8614
|
+
}
|
|
8615
|
+
/**
|
|
8616
|
+
* Generate connection URL for Redis.
|
|
8617
|
+
*/
|
|
8618
|
+
function generateRedisUrl(creds) {
|
|
8619
|
+
const { password, host, port } = creds;
|
|
8620
|
+
return `redis://:${encodeURIComponent(password)}@${host}:${port}`;
|
|
8621
|
+
}
|
|
8622
|
+
/**
|
|
8623
|
+
* Generate endpoint URL for MinIO (S3-compatible).
|
|
8624
|
+
*/
|
|
8625
|
+
function generateMinioEndpoint(creds) {
|
|
8626
|
+
const { host, port } = creds;
|
|
8627
|
+
return `http://${host}:${port}`;
|
|
8628
|
+
}
|
|
8629
|
+
/**
|
|
8630
|
+
* Generate connection URL for pg-boss (uses PostgreSQL protocol).
|
|
8631
|
+
* Format: pgboss://user:pass@host:port/db?schema=pgboss
|
|
8632
|
+
*/
|
|
8633
|
+
function generatePgBossUrl(creds) {
|
|
8634
|
+
const { username, password, host, port, database } = creds;
|
|
8635
|
+
return `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;
|
|
8636
|
+
}
|
|
8637
|
+
/**
|
|
8638
|
+
* Generate connection URLs from service credentials.
|
|
8639
|
+
*/
|
|
8640
|
+
function generateConnectionUrls(services) {
|
|
8641
|
+
const urls = {};
|
|
8642
|
+
if (services.postgres) urls.DATABASE_URL = generatePostgresUrl(services.postgres);
|
|
8643
|
+
if (services.redis) urls.REDIS_URL = generateRedisUrl(services.redis);
|
|
8644
|
+
if (services.minio) urls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);
|
|
8645
|
+
if (services.pgboss) {
|
|
8646
|
+
const pgbossUrl = generatePgBossUrl(services.pgboss);
|
|
8647
|
+
urls.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;
|
|
8648
|
+
urls.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;
|
|
8649
|
+
}
|
|
8650
|
+
if (services.mailpit) {
|
|
8651
|
+
urls.SMTP_HOST = services.mailpit.host;
|
|
8652
|
+
urls.SMTP_PORT = String(services.mailpit.port);
|
|
8653
|
+
}
|
|
8654
|
+
return urls;
|
|
8655
|
+
}
|
|
8656
|
+
/**
|
|
8657
|
+
* Create a new StageSecrets object with generated credentials.
|
|
8658
|
+
* @param stage - The deployment stage (e.g., 'development', 'production')
|
|
8659
|
+
* @param services - List of services to generate credentials for
|
|
8660
|
+
* @param options - Optional configuration
|
|
8661
|
+
* @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')
|
|
8662
|
+
*/
|
|
8663
|
+
function createStageSecrets(stage, services, options) {
|
|
8664
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8665
|
+
const serviceCredentials = generateServicesCredentials(services);
|
|
8666
|
+
if (options?.projectName) {
|
|
8667
|
+
if (serviceCredentials.postgres) serviceCredentials.postgres.database = `${options.projectName.replace(/-/g, "_")}_dev`;
|
|
8668
|
+
if (serviceCredentials.minio) {
|
|
8669
|
+
serviceCredentials.minio.bucket = options.projectName;
|
|
8670
|
+
serviceCredentials.minio.username = options.projectName;
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
if (serviceCredentials.postgres && !serviceCredentials.pgboss) serviceCredentials.pgboss = {
|
|
8674
|
+
...PGBOSS_DEFAULTS,
|
|
8675
|
+
password: generateSecurePassword(),
|
|
8676
|
+
host: serviceCredentials.postgres.host,
|
|
8677
|
+
port: serviceCredentials.postgres.port,
|
|
8678
|
+
database: serviceCredentials.postgres.database
|
|
8679
|
+
};
|
|
8680
|
+
return {
|
|
8681
|
+
stage,
|
|
8682
|
+
createdAt: now,
|
|
8683
|
+
updatedAt: now,
|
|
8684
|
+
services: serviceCredentials,
|
|
8685
|
+
urls: generateConnectionUrls(serviceCredentials),
|
|
8686
|
+
custom: {}
|
|
8687
|
+
};
|
|
8688
|
+
}
|
|
8689
|
+
/**
|
|
8690
|
+
* Rotate password for a specific service.
|
|
8691
|
+
*/
|
|
8692
|
+
function rotateServicePassword(secrets, service) {
|
|
8693
|
+
const currentCreds = secrets.services[service];
|
|
8694
|
+
if (!currentCreds) throw new Error(`Service "${service}" not configured in secrets`);
|
|
8695
|
+
const newCreds = {
|
|
8696
|
+
...currentCreds,
|
|
8697
|
+
password: generateSecurePassword()
|
|
8698
|
+
};
|
|
8699
|
+
const newServices = {
|
|
8700
|
+
...secrets.services,
|
|
8701
|
+
[service]: newCreds
|
|
8702
|
+
};
|
|
8703
|
+
return {
|
|
8704
|
+
...secrets,
|
|
8705
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8706
|
+
services: newServices,
|
|
8707
|
+
urls: generateConnectionUrls(newServices)
|
|
8708
|
+
};
|
|
8709
|
+
}
|
|
8710
|
+
//#endregion
|
|
7966
8711
|
//#region src/migrate/names.ts
|
|
7967
8712
|
/**
|
|
7968
8713
|
* What a migration file is called: a UTC timestamp, then a name.
|
|
@@ -8798,26 +9543,26 @@ function loadPackageJson() {
|
|
|
8798
9543
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8799
9544
|
*/
|
|
8800
9545
|
const GEEKMIDAS_VERSIONS = {
|
|
8801
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8802
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8803
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8804
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8805
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8806
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8807
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8808
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8809
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8810
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8811
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8812
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8813
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8814
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8815
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8816
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8817
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8818
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8819
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8820
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
9546
|
+
"@geekmidas/audit": "~10.0.0-alpha.38",
|
|
9547
|
+
"@geekmidas/auth": "~10.0.0-alpha.38",
|
|
9548
|
+
"@geekmidas/cache": "~10.0.0-alpha.38",
|
|
9549
|
+
"@geekmidas/client": "~10.0.0-alpha.38",
|
|
9550
|
+
"@geekmidas/cloud": "~10.0.0-alpha.38",
|
|
9551
|
+
"@geekmidas/constructs": "~10.0.0-alpha.38",
|
|
9552
|
+
"@geekmidas/db": "~10.0.0-alpha.38",
|
|
9553
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.38",
|
|
9554
|
+
"@geekmidas/envkit": "~10.0.0-alpha.38",
|
|
9555
|
+
"@geekmidas/errors": "~10.0.0-alpha.38",
|
|
9556
|
+
"@geekmidas/events": "~10.0.0-alpha.38",
|
|
9557
|
+
"@geekmidas/logger": "~10.0.0-alpha.38",
|
|
9558
|
+
"@geekmidas/manifest": "~10.0.0-alpha.38",
|
|
9559
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.38",
|
|
9560
|
+
"@geekmidas/schema": "~10.0.0-alpha.38",
|
|
9561
|
+
"@geekmidas/services": "~10.0.0-alpha.38",
|
|
9562
|
+
"@geekmidas/storage": "~10.0.0-alpha.38",
|
|
9563
|
+
"@geekmidas/studio": "~10.0.0-alpha.38",
|
|
9564
|
+
"@geekmidas/telescope": "~10.0.0-alpha.38",
|
|
9565
|
+
"@geekmidas/testkit": "~10.0.0-alpha.38",
|
|
8821
9566
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8822
9567
|
};
|
|
8823
9568
|
//#endregion
|
|
@@ -9389,16 +10134,7 @@ function deploy(options) {
|
|
|
9389
10134
|
role-to-assume: \${{ vars.AWS_ROLE_ARN }}
|
|
9390
10135
|
aws-region: ${options.region}
|
|
9391
10136
|
` : "";
|
|
9392
|
-
const stageSecrets = sst ?
|
|
9393
|
-
# The stage's secrets, from SSM in the account the role belongs to —
|
|
9394
|
-
# pushed there once with \`gkm secrets:push --stage <stage> --profile …\`.
|
|
9395
|
-
# The stage reaches the shell as a variable, never pasted into the script:
|
|
9396
|
-
# on a manual run it is whatever was typed.
|
|
9397
|
-
- name: Stage secrets
|
|
9398
|
-
run: ${pm.exec} gkm secrets:pull --stage "$STAGE"
|
|
9399
|
-
env:
|
|
9400
|
-
STAGE: \${{ matrix.stage }}
|
|
9401
|
-
` : `
|
|
10137
|
+
const stageSecrets = sst ? credentials : `
|
|
9402
10138
|
# The stage's secrets are kept in the encrypted .gkm/secrets/<stage>.json,
|
|
9403
10139
|
# and each environment holds its key as GKM_SECRETS_KEY. .gkm/ is
|
|
9404
10140
|
# gitignored, so a checkout has the key but not the file: set
|
|
@@ -10824,7 +11560,7 @@ function secretsStore(options) {
|
|
|
10824
11560
|
if (options.deployTarget !== "sst" || !options.region) return "";
|
|
10825
11561
|
return `
|
|
10826
11562
|
// Deployed stages keep their secrets in SSM, in the account each stage
|
|
10827
|
-
// deploys to: \`gkm secrets:
|
|
11563
|
+
// deploys to: \`gkm secrets:set <KEY> '…' --stage <stage>\` writes there.
|
|
10828
11564
|
store: { provider: 'ssm', region: '${options.region}' },`;
|
|
10829
11565
|
}
|
|
10830
11566
|
/**
|
|
@@ -14732,7 +15468,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
14732
15468
|
customSecrets.BETTER_AUTH_TRUSTED_ORIGINS = "http://localhost:3000,http://localhost:3001";
|
|
14733
15469
|
}
|
|
14734
15470
|
devSecrets.custom = customSecrets;
|
|
14735
|
-
await
|
|
15471
|
+
await new FileSecretsStore(targetDir).write(local, devSecrets);
|
|
14736
15472
|
const keyPath = getKeyPath(local, name);
|
|
14737
15473
|
console.log(` Secrets: .gkm/secrets/${local}.json (encrypted)`);
|
|
14738
15474
|
console.log(` Key: ${keyPath}\n`);
|
|
@@ -14885,15 +15621,104 @@ var NotAnAwsRegion = class extends Error {
|
|
|
14885
15621
|
}
|
|
14886
15622
|
};
|
|
14887
15623
|
//#endregion
|
|
15624
|
+
//#region src/setup/fullstack-secrets.ts
|
|
15625
|
+
/**
|
|
15626
|
+
* Generate a secure random password for database users.
|
|
15627
|
+
* Uses a combination of timestamp and random bytes for uniqueness.
|
|
15628
|
+
*/
|
|
15629
|
+
function generateDbPassword() {
|
|
15630
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
|
|
15631
|
+
}
|
|
15632
|
+
/**
|
|
15633
|
+
* Generate database URL for an app.
|
|
15634
|
+
* All apps connect to the same database, but use different users/schemas.
|
|
15635
|
+
*/
|
|
15636
|
+
function generateDbUrl(appName, password, projectName, host = "localhost", port = 5432) {
|
|
15637
|
+
return `postgresql://${appName.replace(/-/g, "_")}:${password}@${host}:${port}/${`${projectName.replace(/-/g, "_")}_dev`}`;
|
|
15638
|
+
}
|
|
15639
|
+
/**
|
|
15640
|
+
* Generate fullstack-aware custom secrets for a workspace.
|
|
15641
|
+
*
|
|
15642
|
+
* Generates:
|
|
15643
|
+
* - Common secrets: PORT, LOG_LEVEL, JWT_SECRET (no `NODE_ENV` — the command
|
|
15644
|
+
* decides that; see `gkm init`)
|
|
15645
|
+
* - Per-app database passwords and URLs for backend apps with db service
|
|
15646
|
+
* - Better-auth secrets for apps using the better-auth framework
|
|
15647
|
+
*/
|
|
15648
|
+
function generateFullstackCustomSecrets(workspace, containers = []) {
|
|
15649
|
+
const hasDb = containers.includes("postgres");
|
|
15650
|
+
const customs = {
|
|
15651
|
+
PORT: "3000",
|
|
15652
|
+
LOG_LEVEL: "debug",
|
|
15653
|
+
JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
15654
|
+
};
|
|
15655
|
+
if (!hasDb) return customs;
|
|
15656
|
+
const frontendPorts = [];
|
|
15657
|
+
for (const [appName, appConfig] of Object.entries(workspace.apps)) {
|
|
15658
|
+
if (appConfig.type === "web") {
|
|
15659
|
+
frontendPorts.push(appConfig.port);
|
|
15660
|
+
const upperName = appName.toUpperCase();
|
|
15661
|
+
customs[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;
|
|
15662
|
+
continue;
|
|
15663
|
+
}
|
|
15664
|
+
if (appConfig.type === "mobile") continue;
|
|
15665
|
+
const password = generateDbPassword();
|
|
15666
|
+
const upperName = appName.toUpperCase();
|
|
15667
|
+
customs[`${upperName}_DATABASE_URL`] = generateDbUrl(appName, password, workspace.name);
|
|
15668
|
+
customs[`${upperName}_DB_PASSWORD`] = password;
|
|
15669
|
+
if (appConfig.framework === "better-auth") {
|
|
15670
|
+
customs.AUTH_PORT = String(appConfig.port);
|
|
15671
|
+
customs.AUTH_URL = `http://localhost:${appConfig.port}`;
|
|
15672
|
+
customs.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;
|
|
15673
|
+
customs.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;
|
|
15674
|
+
}
|
|
15675
|
+
}
|
|
15676
|
+
if (customs.BETTER_AUTH_SECRET) customs.BETTER_AUTH_TRUSTED_ORIGINS = Object.values(workspace.apps).map((a) => a.port).map((p) => `http://localhost:${p}`).join(",");
|
|
15677
|
+
return customs;
|
|
15678
|
+
}
|
|
15679
|
+
/**
|
|
15680
|
+
* Extract *_DB_PASSWORD keys from secrets and write docker/.env.
|
|
15681
|
+
*
|
|
15682
|
+
* The docker/.env file contains database passwords that the PostgreSQL
|
|
15683
|
+
* init script reads to create per-app database users.
|
|
15684
|
+
*/
|
|
15685
|
+
async function writeDockerEnvFromSecrets(secrets, workspaceRoot) {
|
|
15686
|
+
const dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) => key.endsWith("_DB_PASSWORD"));
|
|
15687
|
+
if (secrets.services.pgboss && !dbPasswordEntries.some(([key]) => key === "PGBOSS_DB_PASSWORD")) dbPasswordEntries.push(["PGBOSS_DB_PASSWORD", secrets.services.pgboss.password]);
|
|
15688
|
+
if (dbPasswordEntries.length === 0) return;
|
|
15689
|
+
const envContent = `# Auto-generated docker environment file
|
|
15690
|
+
# Contains database passwords for docker-compose postgres init
|
|
15691
|
+
# This file is gitignored - do not commit to version control
|
|
15692
|
+
${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
15693
|
+
`;
|
|
15694
|
+
const envPath = join(workspaceRoot, "docker", ".env");
|
|
15695
|
+
await mkdir(dirname(envPath), { recursive: true });
|
|
15696
|
+
await writeFile(envPath, envContent);
|
|
15697
|
+
}
|
|
15698
|
+
//#endregion
|
|
14888
15699
|
//#region src/secrets/index.ts
|
|
14889
15700
|
const logger$2 = console;
|
|
14890
15701
|
/**
|
|
15702
|
+
* The store the stage named on the command line keeps its secrets in — SSM in
|
|
15703
|
+
* its account, for a deployed stage kept there. Outside any workspace nothing
|
|
15704
|
+
* names a store, so it is the file.
|
|
15705
|
+
*/
|
|
15706
|
+
async function storeFor(stage) {
|
|
15707
|
+
try {
|
|
15708
|
+
return await secretsStoreFor(await loadWorkspaceSettings(), stage);
|
|
15709
|
+
} catch (error) {
|
|
15710
|
+
if (error instanceof ConfigNotFound) return new FileSecretsStore(process.cwd());
|
|
15711
|
+
throw error;
|
|
15712
|
+
}
|
|
15713
|
+
}
|
|
15714
|
+
/**
|
|
14891
15715
|
* Initialize secrets for a stage.
|
|
14892
15716
|
* Generates secure random passwords for configured services.
|
|
14893
15717
|
*/
|
|
14894
15718
|
async function secretsInitCommand(options) {
|
|
14895
15719
|
const { stage, force } = options;
|
|
14896
|
-
|
|
15720
|
+
const store = await storeFor(stage);
|
|
15721
|
+
if (!force && await store.read(stage)) {
|
|
14897
15722
|
logger$2.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
14898
15723
|
process.exit(1);
|
|
14899
15724
|
}
|
|
@@ -14909,9 +15734,9 @@ async function secretsInitCommand(options) {
|
|
|
14909
15734
|
} catch {}
|
|
14910
15735
|
const secrets = createStageSecrets(stage, [], { projectName });
|
|
14911
15736
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
14912
|
-
await
|
|
15737
|
+
await store.write(stage, secrets);
|
|
14913
15738
|
logger$2.log(`\n✓ Secrets initialized for stage "${stage}"`);
|
|
14914
|
-
logger$2.log(`
|
|
15739
|
+
logger$2.log(` Store: ${store.name}`);
|
|
14915
15740
|
if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14916
15741
|
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14917
15742
|
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
@@ -14946,20 +15771,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
14946
15771
|
process.exit(1);
|
|
14947
15772
|
}
|
|
14948
15773
|
}
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
logger$2.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
15774
|
+
const store = await storeFor(stage);
|
|
15775
|
+
const secrets = await store.read(stage);
|
|
15776
|
+
if (!secrets) {
|
|
15777
|
+
logger$2.error(`Secrets not found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14954
15778
|
process.exit(1);
|
|
14955
15779
|
}
|
|
15780
|
+
await store.write(stage, withCustomSecret(secrets, key, secretValue));
|
|
15781
|
+
logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}" (${store.name})`);
|
|
14956
15782
|
}
|
|
14957
15783
|
/**
|
|
14958
15784
|
* Show secrets for a stage.
|
|
14959
15785
|
*/
|
|
14960
15786
|
async function secretsShowCommand(options) {
|
|
14961
15787
|
const { stage, reveal } = options;
|
|
14962
|
-
const secrets = await
|
|
15788
|
+
const secrets = await (await storeFor(stage)).read(stage);
|
|
14963
15789
|
if (!secrets) {
|
|
14964
15790
|
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14965
15791
|
process.exit(1);
|
|
@@ -14992,7 +15818,8 @@ async function secretsShowCommand(options) {
|
|
|
14992
15818
|
*/
|
|
14993
15819
|
async function secretsRotateCommand(options) {
|
|
14994
15820
|
const { stage, service } = options;
|
|
14995
|
-
const
|
|
15821
|
+
const store = await storeFor(stage);
|
|
15822
|
+
const secrets = await store.read(stage);
|
|
14996
15823
|
if (!secrets) {
|
|
14997
15824
|
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14998
15825
|
process.exit(1);
|
|
@@ -15003,13 +15830,13 @@ async function secretsRotateCommand(options) {
|
|
|
15003
15830
|
process.exit(1);
|
|
15004
15831
|
}
|
|
15005
15832
|
const updated = rotateServicePassword(secrets, service);
|
|
15006
|
-
await
|
|
15833
|
+
await store.write(stage, updated);
|
|
15007
15834
|
logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
15008
15835
|
} else {
|
|
15009
15836
|
let updated = secrets;
|
|
15010
15837
|
const services = Object.keys(secrets.services);
|
|
15011
15838
|
for (const svc of services) updated = rotateServicePassword(updated, svc);
|
|
15012
|
-
await
|
|
15839
|
+
await store.write(stage, updated);
|
|
15013
15840
|
logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
15014
15841
|
}
|
|
15015
15842
|
logger$2.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
@@ -15033,7 +15860,8 @@ async function secretsImportCommand(file, options) {
|
|
|
15033
15860
|
logger$2.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
15034
15861
|
process.exit(1);
|
|
15035
15862
|
}
|
|
15036
|
-
const
|
|
15863
|
+
const store = await storeFor(stage);
|
|
15864
|
+
const secrets = await store.read(stage);
|
|
15037
15865
|
if (!secrets) {
|
|
15038
15866
|
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
15039
15867
|
process.exit(1);
|
|
@@ -15047,7 +15875,7 @@ async function secretsImportCommand(file, options) {
|
|
|
15047
15875
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15048
15876
|
custom: updatedCustom
|
|
15049
15877
|
};
|
|
15050
|
-
await
|
|
15878
|
+
await store.write(stage, updated);
|
|
15051
15879
|
const importedCount = Object.keys(importedSecrets).length;
|
|
15052
15880
|
const totalCount = Object.keys(updatedCustom).length;
|
|
15053
15881
|
logger$2.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
|
|
@@ -15068,121 +15896,6 @@ function maskUrl(url) {
|
|
|
15068
15896
|
}
|
|
15069
15897
|
}
|
|
15070
15898
|
//#endregion
|
|
15071
|
-
//#region src/secrets/store.ts
|
|
15072
|
-
/** The encrypted file under `.gkm/secrets/`, and its key under `~/.gkm/`. */
|
|
15073
|
-
var FileSecretsStore = class {
|
|
15074
|
-
root;
|
|
15075
|
-
constructor(root) {
|
|
15076
|
-
this.root = root;
|
|
15077
|
-
}
|
|
15078
|
-
pull(stage) {
|
|
15079
|
-
return readStageSecrets(stage, this.root);
|
|
15080
|
-
}
|
|
15081
|
-
push(_stage, secrets) {
|
|
15082
|
-
return writeStageSecrets(secrets, this.root);
|
|
15083
|
-
}
|
|
15084
|
-
};
|
|
15085
|
-
/**
|
|
15086
|
-
* The store a stage's secrets live in.
|
|
15087
|
-
*
|
|
15088
|
-
* The local stage is always the file: its secrets belong to the machine
|
|
15089
|
-
* running `gkm dev`. A deployed stage uses `secrets.store`.
|
|
15090
|
-
*/
|
|
15091
|
-
async function secretsStoreFor(workspace, stage, options = {}) {
|
|
15092
|
-
const configured = workspace.secrets.store ?? "file";
|
|
15093
|
-
if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
|
|
15094
|
-
if (configured.provider !== "ssm") return configured.provider;
|
|
15095
|
-
const { SsmSecretsStore } = await import("./ssm-BKN1Cg4z.mjs");
|
|
15096
|
-
return new SsmSecretsStore({
|
|
15097
|
-
project: workspace.name,
|
|
15098
|
-
region: configured.region,
|
|
15099
|
-
...options.profile ? { profile: options.profile } : {}
|
|
15100
|
-
});
|
|
15101
|
-
}
|
|
15102
|
-
/** Whether a stage's secrets live somewhere other than this machine. */
|
|
15103
|
-
function isRemoteStore(workspace, stage) {
|
|
15104
|
-
const configured = workspace.secrets.store ?? "file";
|
|
15105
|
-
return stage !== workspace.stages.local && configured !== "file";
|
|
15106
|
-
}
|
|
15107
|
-
//#endregion
|
|
15108
|
-
//#region src/secrets/transfer.ts
|
|
15109
|
-
/**
|
|
15110
|
-
* Moving a deployed stage's secrets between this machine and its store.
|
|
15111
|
-
*
|
|
15112
|
-
* `push` sends the encrypted local copy to the stage's store; `pull` brings it
|
|
15113
|
-
* back and writes the local copy a deploy reads — generating a key for it if
|
|
15114
|
-
* the machine has none, which is what a fresh CI runner needs. Both reconcile
|
|
15115
|
-
* the keys the workspace now derives, so a store never lags a new construct.
|
|
15116
|
-
*/
|
|
15117
|
-
var transfer_exports = /* @__PURE__ */ __exportAll({
|
|
15118
|
-
NoLocalSecrets: () => NoLocalSecrets,
|
|
15119
|
-
NoRemoteSecretsStore: () => NoRemoteSecretsStore,
|
|
15120
|
-
NoStoredSecrets: () => NoStoredSecrets,
|
|
15121
|
-
pullStageSecrets: () => pullStageSecrets,
|
|
15122
|
-
pushStageSecrets: () => pushStageSecrets
|
|
15123
|
-
});
|
|
15124
|
-
/** A push or pull for a stage with nowhere to push or pull from. */
|
|
15125
|
-
var NoRemoteSecretsStore = class extends Error {
|
|
15126
|
-
stage;
|
|
15127
|
-
constructor(stage) {
|
|
15128
|
-
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.`);
|
|
15129
|
-
this.stage = stage;
|
|
15130
|
-
this.name = "NoRemoteSecretsStore";
|
|
15131
|
-
}
|
|
15132
|
-
};
|
|
15133
|
-
/** A push for a stage whose secrets this machine does not have. */
|
|
15134
|
-
var NoLocalSecrets = class extends Error {
|
|
15135
|
-
stage;
|
|
15136
|
-
constructor(stage) {
|
|
15137
|
-
super(`No secrets for stage "${stage}" on this machine. Run gkm secrets:init --stage ${stage} first.`);
|
|
15138
|
-
this.stage = stage;
|
|
15139
|
-
this.name = "NoLocalSecrets";
|
|
15140
|
-
}
|
|
15141
|
-
};
|
|
15142
|
-
/** A pull for a stage the store holds nothing for. */
|
|
15143
|
-
var NoStoredSecrets = class extends Error {
|
|
15144
|
-
stage;
|
|
15145
|
-
constructor(stage) {
|
|
15146
|
-
super(`The store holds no secrets for stage "${stage}". Push them first: gkm secrets:push --stage ${stage}.`);
|
|
15147
|
-
this.stage = stage;
|
|
15148
|
-
this.name = "NoStoredSecrets";
|
|
15149
|
-
}
|
|
15150
|
-
};
|
|
15151
|
-
async function reconcile(secrets, workspace, stage) {
|
|
15152
|
-
const { derivedContainers } = await import("./workspace-EmXwO2qM.mjs").then((n) => n.a);
|
|
15153
|
-
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15154
|
-
return result ? {
|
|
15155
|
-
secrets: result.secrets,
|
|
15156
|
-
addedKeys: result.addedKeys
|
|
15157
|
-
} : {
|
|
15158
|
-
secrets,
|
|
15159
|
-
addedKeys: []
|
|
15160
|
-
};
|
|
15161
|
-
}
|
|
15162
|
-
function remoteOnly(workspace, stage) {
|
|
15163
|
-
assertDeployedStage(workspace.stages, stage);
|
|
15164
|
-
if (!isRemoteStore(workspace, stage)) throw new NoRemoteSecretsStore(stage);
|
|
15165
|
-
}
|
|
15166
|
-
/** Send this machine's secrets for a deployed stage to its store. */
|
|
15167
|
-
async function pushStageSecrets(workspace, stage, options = {}) {
|
|
15168
|
-
remoteOnly(workspace, stage);
|
|
15169
|
-
const local = await readStageSecrets(stage, workspace.root);
|
|
15170
|
-
if (!local) throw new NoLocalSecrets(stage);
|
|
15171
|
-
const result = await reconcile(local, workspace, stage);
|
|
15172
|
-
if (result.addedKeys.length) await writeStageSecrets(result.secrets, workspace.root);
|
|
15173
|
-
await (await secretsStoreFor(workspace, stage, options)).push(stage, result.secrets);
|
|
15174
|
-
return result;
|
|
15175
|
-
}
|
|
15176
|
-
/** Bring a deployed stage's secrets from its store into the local copy. */
|
|
15177
|
-
async function pullStageSecrets(workspace, stage, options = {}) {
|
|
15178
|
-
remoteOnly(workspace, stage);
|
|
15179
|
-
const stored = await (await secretsStoreFor(workspace, stage, options)).pull(stage);
|
|
15180
|
-
if (!stored) throw new NoStoredSecrets(stage);
|
|
15181
|
-
const result = await reconcile(stored, workspace, stage);
|
|
15182
|
-
await writeStageSecrets(result.secrets, workspace.root);
|
|
15183
|
-
return result;
|
|
15184
|
-
}
|
|
15185
|
-
//#endregion
|
|
15186
15899
|
//#region src/setup/index.ts
|
|
15187
15900
|
var setup_exports = /* @__PURE__ */ __exportAll({
|
|
15188
15901
|
createFreshWorkspaceSecrets: () => createFreshWorkspaceSecrets,
|
|
@@ -15264,30 +15977,18 @@ async function reconcileLocal(workspace, stage, options) {
|
|
|
15264
15977
|
async function resolveSecrets(stage, workspace, options) {
|
|
15265
15978
|
if (options.force) {
|
|
15266
15979
|
logger$1.log("🔐 Generating fresh secrets (--force)...");
|
|
15267
|
-
return generateFreshSecrets(stage, workspace
|
|
15980
|
+
return generateFreshSecrets(stage, workspace);
|
|
15268
15981
|
}
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
}
|
|
15277
|
-
}
|
|
15278
|
-
if (isRemoteStore(workspace, stage)) {
|
|
15279
|
-
logger$1.log("☁️ Checking the secrets store...");
|
|
15280
|
-
try {
|
|
15281
|
-
const { secrets } = await pullStageSecrets(workspace, stage);
|
|
15282
|
-
logger$1.log("✅ Pulled secrets from the store");
|
|
15283
|
-
return secrets;
|
|
15284
|
-
} catch (error) {
|
|
15285
|
-
if (error instanceof NoStoredSecrets) logger$1.log(" The store holds none yet");
|
|
15286
|
-
else logger$1.warn(`⚠️ Could not pull from the store: ${error.message}`);
|
|
15287
|
-
}
|
|
15982
|
+
const store = await secretsStoreFor(workspace, stage);
|
|
15983
|
+
const secrets = await store.read(stage);
|
|
15984
|
+
if (secrets) {
|
|
15985
|
+
logger$1.log("🔐 Using existing secrets");
|
|
15986
|
+
const reconciled = reconcileSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15987
|
+
if (reconciled) await store.write(stage, reconciled);
|
|
15988
|
+
return reconciled ?? secrets;
|
|
15288
15989
|
}
|
|
15289
15990
|
logger$1.log("🔐 Generating fresh development secrets...");
|
|
15290
|
-
return generateFreshSecrets(stage, workspace
|
|
15991
|
+
return generateFreshSecrets(stage, workspace);
|
|
15291
15992
|
}
|
|
15292
15993
|
/**
|
|
15293
15994
|
* Reconcile existing secrets with expected workspace-derived keys.
|
|
@@ -15381,8 +16082,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15381
16082
|
* @internal Exported for reuse by `gkm test` auto-setup.
|
|
15382
16083
|
*/
|
|
15383
16084
|
function createFreshWorkspaceSecrets(stage, workspace, containers = []) {
|
|
15384
|
-
const
|
|
15385
|
-
const secrets = createStageSecrets(stage, serviceNames, { projectName: workspace.name });
|
|
16085
|
+
const secrets = createStageSecrets(stage, credentialedServices(containers), { projectName: workspace.name });
|
|
15386
16086
|
if (Object.keys(workspace.apps).length > 1) secrets.custom = generateFullstackCustomSecrets(workspace, containers);
|
|
15387
16087
|
else secrets.custom = {
|
|
15388
16088
|
PORT: "3000",
|
|
@@ -15397,38 +16097,26 @@ function createFreshWorkspaceSecrets(stage, workspace, containers = []) {
|
|
|
15397
16097
|
*
|
|
15398
16098
|
* Powers `gkm test --auto-setup` / `GKM_AUTO_SETUP`: CI can run without a
|
|
15399
16099
|
* committed secrets file or a shared encryption key — the stage is regenerated
|
|
15400
|
-
* from the committed `gkm.config.ts`, and
|
|
16100
|
+
* from the committed `gkm.config.ts`, and the file store mints a local key.
|
|
15401
16101
|
*
|
|
15402
16102
|
* @returns true if fresh secrets were generated, false if existing ones were kept
|
|
15403
16103
|
*/
|
|
15404
16104
|
async function ensureStageSecrets(stage, cwd = process.cwd()) {
|
|
15405
16105
|
const { workspace } = await loadWorkspaceConfig(cwd);
|
|
15406
|
-
|
|
16106
|
+
const store = await secretsStoreFor(workspace, stage);
|
|
16107
|
+
if (await store.read(stage)) return false;
|
|
15407
16108
|
const secrets = createFreshWorkspaceSecrets(stage, workspace, await derivedContainers(workspace, stage));
|
|
15408
|
-
await
|
|
16109
|
+
await store.write(stage, secrets);
|
|
15409
16110
|
return true;
|
|
15410
16111
|
}
|
|
15411
16112
|
/**
|
|
15412
16113
|
* Generate fresh secrets for the workspace.
|
|
15413
16114
|
*/
|
|
15414
|
-
async function generateFreshSecrets(stage, workspace
|
|
16115
|
+
async function generateFreshSecrets(stage, workspace) {
|
|
15415
16116
|
const secrets = createFreshWorkspaceSecrets(stage, workspace, await derivedContainers(workspace, stage));
|
|
15416
|
-
await
|
|
15417
|
-
|
|
15418
|
-
|
|
15419
|
-
const { shouldPush } = await prompts({
|
|
15420
|
-
type: "confirm",
|
|
15421
|
-
name: "shouldPush",
|
|
15422
|
-
message: `Push the "${stage}" secrets to its store?`,
|
|
15423
|
-
initial: true
|
|
15424
|
-
});
|
|
15425
|
-
if (shouldPush) try {
|
|
15426
|
-
await pushStageSecrets(workspace, stage);
|
|
15427
|
-
logger$1.log("☁️ Secrets pushed to the store");
|
|
15428
|
-
} catch (error) {
|
|
15429
|
-
logger$1.warn(`⚠️ Could not push to the store: ${error.message}`);
|
|
15430
|
-
}
|
|
15431
|
-
}
|
|
16117
|
+
const store = await secretsStoreFor(workspace, stage);
|
|
16118
|
+
await store.write(stage, secrets);
|
|
16119
|
+
logger$1.log(` Secrets written to the "${stage}" store (${store.name})`);
|
|
15432
16120
|
return secrets;
|
|
15433
16121
|
}
|
|
15434
16122
|
/**
|
|
@@ -15448,6 +16136,56 @@ function printSummary(workspace, stage) {
|
|
|
15448
16136
|
logger$1.log("");
|
|
15449
16137
|
}
|
|
15450
16138
|
//#endregion
|
|
16139
|
+
//#region src/test/teardown.ts
|
|
16140
|
+
/**
|
|
16141
|
+
* Dropping the test stage's databases when a suite ends.
|
|
16142
|
+
*
|
|
16143
|
+
* Reconcile never drops — the line it draws is at data. The test stage's data
|
|
16144
|
+
* is the exception: every test runs in a transaction rolled back after it, so
|
|
16145
|
+
* nothing in these databases is anyone's, and a database kept between runs is
|
|
16146
|
+
* one whose migrations already ran — an edited migration is never applied to
|
|
16147
|
+
* it again. So each run starts from a database it created, and drops it when
|
|
16148
|
+
* the suite ends: the Vitest global setup's teardown.
|
|
16149
|
+
*/
|
|
16150
|
+
/**
|
|
16151
|
+
* Drop the databases the last setup recorded, and forget the test stage's
|
|
16152
|
+
* reconcile, so the next run provisions them from nothing.
|
|
16153
|
+
*
|
|
16154
|
+
* A no-op when nothing is recorded — no run yet, or one already torn down —
|
|
16155
|
+
* so running it twice, or before a setup, is harmless.
|
|
16156
|
+
*/
|
|
16157
|
+
async function dropTestDatabases(cwd, root, sql = pgClient) {
|
|
16158
|
+
const readyPath = join(cwd, TEST_READY_FILE);
|
|
16159
|
+
const ready = await readReady(readyPath);
|
|
16160
|
+
const recorded = ready?.databases;
|
|
16161
|
+
if (!ready || !recorded) return [];
|
|
16162
|
+
const client = sql(recorded.port);
|
|
16163
|
+
for (const name of recorded.names) {
|
|
16164
|
+
if (!name.endsWith(`_test`)) throw new NotATestDatabase(name);
|
|
16165
|
+
await client.query(void 0, `DROP DATABASE IF EXISTS ${quoteIdentifier(name)} WITH (FORCE)`);
|
|
16166
|
+
}
|
|
16167
|
+
await forgetState(root, TEST_STAGE);
|
|
16168
|
+
const { databases: _dropped, ...rest } = ready;
|
|
16169
|
+
await writeFile(readyPath, JSON.stringify(rest, null, 2));
|
|
16170
|
+
return recorded.names;
|
|
16171
|
+
}
|
|
16172
|
+
async function readReady(path) {
|
|
16173
|
+
try {
|
|
16174
|
+
return JSON.parse(await readFile(path, "utf-8"));
|
|
16175
|
+
} catch {
|
|
16176
|
+
return;
|
|
16177
|
+
}
|
|
16178
|
+
}
|
|
16179
|
+
/** A name recorded for the test stage without the test stage's suffix. */
|
|
16180
|
+
var NotATestDatabase = class extends Error {
|
|
16181
|
+
database;
|
|
16182
|
+
constructor(database) {
|
|
16183
|
+
super(`Refusing to drop '${database}': only the test stage's databases (named '…_${TEST_STAGE}') are dropped after a suite. Delete ${TEST_READY_FILE} if it was edited by hand.`);
|
|
16184
|
+
this.database = database;
|
|
16185
|
+
this.name = "NotATestDatabase";
|
|
16186
|
+
}
|
|
16187
|
+
};
|
|
16188
|
+
//#endregion
|
|
15451
16189
|
//#region src/test/index.ts
|
|
15452
16190
|
/**
|
|
15453
16191
|
* Run tests with secrets, dependency URLs, and .env files loaded.
|
|
@@ -15456,6 +16194,10 @@ function printSummary(workspace, stage) {
|
|
|
15456
16194
|
async function testCommand(options = {}) {
|
|
15457
16195
|
const cwd = process.cwd();
|
|
15458
16196
|
const stage = options.stage ?? await loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace.stages.local).catch(() => void 0);
|
|
16197
|
+
if (options.teardown) {
|
|
16198
|
+
await teardown(cwd);
|
|
16199
|
+
return;
|
|
16200
|
+
}
|
|
15459
16201
|
if (!stage) throw new NoStageToTest();
|
|
15460
16202
|
console.log(`\n🧪 Running tests with ${stage} environment...\n`);
|
|
15461
16203
|
if (options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP)) {
|
|
@@ -15464,6 +16206,7 @@ async function testCommand(options = {}) {
|
|
|
15464
16206
|
}
|
|
15465
16207
|
const defaultEnv = loadEnvFiles(".env");
|
|
15466
16208
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
16209
|
+
if (!options.prepare) await teardown(cwd);
|
|
15467
16210
|
const result = await prepareEntryCredentials({
|
|
15468
16211
|
stage,
|
|
15469
16212
|
startDocker: !options.prepare,
|
|
@@ -15504,7 +16247,8 @@ async function testCommand(options = {}) {
|
|
|
15504
16247
|
const readyPath = join(cwd, TEST_READY_FILE);
|
|
15505
16248
|
await writeFile(readyPath, JSON.stringify({
|
|
15506
16249
|
env: result.secretsJsonPath,
|
|
15507
|
-
...manifestPath ? { manifest: manifestPath } : {}
|
|
16250
|
+
...manifestPath ? { manifest: manifestPath } : {},
|
|
16251
|
+
...testDatabases(result.reconciled)
|
|
15508
16252
|
}, null, 2));
|
|
15509
16253
|
if (options.setup) {
|
|
15510
16254
|
console.log(` 🧪 Test stage ready: ${TEST_READY_FILE}`);
|
|
@@ -15543,12 +16287,29 @@ async function testCommand(options = {}) {
|
|
|
15543
16287
|
});
|
|
15544
16288
|
});
|
|
15545
16289
|
}
|
|
16290
|
+
/** Drop what the last setup created — see `dropTestDatabases`. */
|
|
16291
|
+
async function teardown(cwd) {
|
|
16292
|
+
const dropped = await dropTestDatabases(cwd, await loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace.root).catch(() => cwd));
|
|
16293
|
+
if (dropped.length > 0) console.log(` 🧹 Dropped ${dropped.join(", ")}`);
|
|
16294
|
+
}
|
|
16295
|
+
/**
|
|
16296
|
+
* The test stage's Postgres databases, to drop when the suite ends — those
|
|
16297
|
+
* the plan provisions, on the port the shared container answers.
|
|
16298
|
+
*/
|
|
16299
|
+
function testDatabases(reconciled) {
|
|
16300
|
+
const port = reconciled?.ports[primaryPortKey("postgres")];
|
|
16301
|
+
const names = (reconciled?.plan.resources ?? []).filter((resource) => resource.kind === "database" && resource.provisions && resource.container === "postgres").map((resource) => resource.name);
|
|
16302
|
+
return port !== void 0 && names.length > 0 ? { databases: {
|
|
16303
|
+
port,
|
|
16304
|
+
names
|
|
16305
|
+
} } : {};
|
|
16306
|
+
}
|
|
15546
16307
|
/**
|
|
15547
16308
|
* Migrate the test stage just reconciled, then seed it: a test run never
|
|
15548
16309
|
* starts without the reference data the app needs.
|
|
15549
16310
|
*/
|
|
15550
16311
|
async function migrateTestStage(workspace, env) {
|
|
15551
|
-
const { migrateAndSeed } = await import("./migrate-
|
|
16312
|
+
const { migrateAndSeed } = await import("./migrate-C2aYSZTn.mjs");
|
|
15552
16313
|
const { discover } = await import("./discover-CMxesskG.mjs").then((n) => n.n);
|
|
15553
16314
|
const sources = {};
|
|
15554
16315
|
const manifest = await discover({
|
|
@@ -15948,7 +16709,7 @@ program.command("build").description("Build handlers and the manifest from decla
|
|
|
15948
16709
|
process.exit(1);
|
|
15949
16710
|
}
|
|
15950
16711
|
});
|
|
15951
|
-
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) => {
|
|
16712
|
+
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) => {
|
|
15952
16713
|
try {
|
|
15953
16714
|
const globalOptions = program.opts();
|
|
15954
16715
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -15959,7 +16720,8 @@ program.command("dev").description("Reconcile constructs and start the dev serve
|
|
|
15959
16720
|
entry: options.entry,
|
|
15960
16721
|
watch: options.watch,
|
|
15961
16722
|
migrate: options.migrate,
|
|
15962
|
-
seed: options.seed
|
|
16723
|
+
seed: options.seed,
|
|
16724
|
+
fake: options.fake
|
|
15963
16725
|
});
|
|
15964
16726
|
} catch (error) {
|
|
15965
16727
|
console.error(formatError(error));
|
|
@@ -15976,7 +16738,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
|
|
|
15976
16738
|
process.exit(1);
|
|
15977
16739
|
}
|
|
15978
16740
|
});
|
|
15979
|
-
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) => {
|
|
16741
|
+
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) => {
|
|
15980
16742
|
try {
|
|
15981
16743
|
const globalOptions = program.opts();
|
|
15982
16744
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -15993,7 +16755,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
15993
16755
|
try {
|
|
15994
16756
|
const globalOptions = program.opts();
|
|
15995
16757
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
15996
|
-
const { migrateCommand } = await import("./migrate-
|
|
16758
|
+
const { migrateCommand } = await import("./migrate-C2aYSZTn.mjs");
|
|
15997
16759
|
await migrateCommand({
|
|
15998
16760
|
...options,
|
|
15999
16761
|
...construct ? { construct } : {}
|
|
@@ -16007,7 +16769,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
16007
16769
|
try {
|
|
16008
16770
|
const globalOptions = program.opts();
|
|
16009
16771
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16010
|
-
const { seedCommand } = await import("./migrate-
|
|
16772
|
+
const { seedCommand } = await import("./migrate-C2aYSZTn.mjs");
|
|
16011
16773
|
await seedCommand({
|
|
16012
16774
|
...options,
|
|
16013
16775
|
...construct ? { construct } : {}
|
|
@@ -16021,7 +16783,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
16021
16783
|
try {
|
|
16022
16784
|
const globalOptions = program.opts();
|
|
16023
16785
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16024
|
-
const { migrationCommand } = await import("./migrate-
|
|
16786
|
+
const { migrationCommand } = await import("./migrate-C2aYSZTn.mjs");
|
|
16025
16787
|
await migrationCommand({
|
|
16026
16788
|
construct,
|
|
16027
16789
|
...name ? { name } : {}
|
|
@@ -16145,57 +16907,28 @@ program.command("secrets:import").description("Import secrets from a JSON file")
|
|
|
16145
16907
|
process.exit(1);
|
|
16146
16908
|
}
|
|
16147
16909
|
});
|
|
16148
|
-
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) => {
|
|
16149
|
-
try {
|
|
16150
|
-
const globalOptions = program.opts();
|
|
16151
|
-
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16152
|
-
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16153
|
-
const { pushStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16154
|
-
const { workspace } = await loadWorkspaceConfig();
|
|
16155
|
-
const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16156
|
-
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
16157
|
-
console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
|
|
16158
|
-
} catch (error) {
|
|
16159
|
-
console.error(formatError(error));
|
|
16160
|
-
process.exit(1);
|
|
16161
|
-
}
|
|
16162
|
-
});
|
|
16163
|
-
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) => {
|
|
16164
|
-
try {
|
|
16165
|
-
const globalOptions = program.opts();
|
|
16166
|
-
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16167
|
-
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16168
|
-
const { pullStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16169
|
-
const { workspace } = await loadWorkspaceConfig();
|
|
16170
|
-
const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16171
|
-
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
16172
|
-
console.log(`\n✓ Secrets pulled for stage "${options.stage}"`);
|
|
16173
|
-
} catch (error) {
|
|
16174
|
-
console.error(formatError(error));
|
|
16175
|
-
process.exit(1);
|
|
16176
|
-
}
|
|
16177
|
-
});
|
|
16178
16910
|
program.command("secrets:reconcile").description("Backfill missing custom secrets from workspace config").option("--stage <stage>", "Stage name (default: stages.local)").action(async (options) => {
|
|
16179
16911
|
try {
|
|
16180
16912
|
const globalOptions = program.opts();
|
|
16181
16913
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16182
16914
|
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16183
|
-
const { reconcileMissingSecrets } = await import("./reconcile
|
|
16184
|
-
const {
|
|
16915
|
+
const { reconcileMissingSecrets } = await import("./reconcile--I7V4Klj.mjs");
|
|
16916
|
+
const { secretsStoreFor } = await import("./store-DeGATCS-.mjs").then((n) => n.r);
|
|
16185
16917
|
const { workspace } = await loadWorkspaceConfig();
|
|
16186
16918
|
const stage = options.stage ?? workspace.stages.local;
|
|
16187
|
-
const
|
|
16919
|
+
const store = await secretsStoreFor(workspace, stage);
|
|
16920
|
+
const secrets = await store.read(stage);
|
|
16188
16921
|
if (!secrets) {
|
|
16189
16922
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16190
16923
|
process.exit(1);
|
|
16191
16924
|
}
|
|
16192
|
-
const { derivedContainers } = await import("./workspace-
|
|
16925
|
+
const { derivedContainers } = await import("./workspace-Lm6dv2nz.mjs").then((n) => n.o);
|
|
16193
16926
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16194
16927
|
if (!result) {
|
|
16195
16928
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
16196
16929
|
return;
|
|
16197
16930
|
}
|
|
16198
|
-
await
|
|
16931
|
+
await store.write(stage, result.secrets);
|
|
16199
16932
|
console.log(`\n✓ Reconciled ${result.addedKeys.length} missing secret(s) for stage "${stage}":`);
|
|
16200
16933
|
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
16201
16934
|
} catch (error) {
|
|
@@ -16248,7 +16981,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16248
16981
|
try {
|
|
16249
16982
|
const globalOptions = program.opts();
|
|
16250
16983
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16251
|
-
const { deployGithubCommand } = await import("./github-
|
|
16984
|
+
const { deployGithubCommand } = await import("./github-BPfmXtqk.mjs");
|
|
16252
16985
|
await deployGithubCommand(options);
|
|
16253
16986
|
} catch (error) {
|
|
16254
16987
|
console.error(formatError(error));
|
|
@@ -16373,6 +17106,6 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
16373
17106
|
});
|
|
16374
17107
|
program.parse();
|
|
16375
17108
|
//#endregion
|
|
16376
|
-
export {
|
|
17109
|
+
export { getHostingerToken as a, encryption_exports as i, slug as n, stamp as r, generateFullstackCustomSecrets as t };
|
|
16377
17110
|
|
|
16378
17111
|
//# sourceMappingURL=index.mjs.map
|