@geekmidas/cli 10.0.0-alpha.58 → 10.0.0-alpha.59
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-cL-6oQ5r.mjs → HostingerProvider-YQY3rMpV.mjs} +2 -2
- package/dist/{HostingerProvider-cL-6oQ5r.mjs.map → HostingerProvider-YQY3rMpV.mjs.map} +1 -1
- package/dist/{LegacyStateStore-ByRWQ9s1.mjs → LegacyStateStore-CB4ch3Dl.mjs} +2 -2
- package/dist/{LegacyStateStore-ByRWQ9s1.mjs.map → LegacyStateStore-CB4ch3Dl.mjs.map} +1 -1
- package/dist/{LegacyStateStore-C_OFs1gN.cjs → LegacyStateStore-CQJqvmXw.cjs} +7 -7
- package/dist/{LegacyStateStore-C_OFs1gN.cjs.map → LegacyStateStore-CQJqvmXw.cjs.map} +1 -1
- package/dist/{S3StateStore-FhzyENrN.mjs → S3StateStore-CXTenDTW.mjs} +2 -2
- package/dist/{S3StateStore-FhzyENrN.mjs.map → S3StateStore-CXTenDTW.mjs.map} +1 -1
- package/dist/{S3StateStore-C7eBae0G.cjs → S3StateStore-DLS4rEc0.cjs} +5 -5
- package/dist/{S3StateStore-C7eBae0G.cjs.map → S3StateStore-DLS4rEc0.cjs.map} +1 -1
- package/dist/{SSMStateStore-DC43R7zG.cjs → SSMStateStore-EqsTBIOs.cjs} +9 -9
- package/dist/{SSMStateStore-DC43R7zG.cjs.map → SSMStateStore-EqsTBIOs.cjs.map} +1 -1
- package/dist/{SSMStateStore-BjZZm-nV.mjs → SSMStateStore-hnP5deFJ.mjs} +2 -2
- package/dist/{SSMStateStore-BjZZm-nV.mjs.map → SSMStateStore-hnP5deFJ.mjs.map} +1 -1
- package/dist/StateStore-B4u0wx2t.cjs +412 -0
- package/dist/StateStore-B4u0wx2t.cjs.map +1 -0
- package/dist/StateStore-CYUfGYcx.mjs +359 -0
- package/dist/StateStore-CYUfGYcx.mjs.map +1 -0
- package/dist/{bundler-URImKj-A.mjs → bundler-ouDdNvk9.mjs} +2 -2
- package/dist/{bundler-URImKj-A.mjs.map → bundler-ouDdNvk9.mjs.map} +1 -1
- package/dist/{config-CSOK73pY.mjs → config-EuzFEkcH.mjs} +2 -2
- package/dist/config-EuzFEkcH.mjs.map +1 -0
- package/dist/{config-DdIu1O_I.cjs → config-WMOB9bLL.cjs} +2 -2
- package/dist/config-WMOB9bLL.cjs.map +1 -0
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +65 -3
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +65 -3
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +2 -2
- package/dist/{encryption-BspqzZIb.cjs → encryption-BP8zfxEJ.cjs} +3 -3
- package/dist/{encryption-BspqzZIb.cjs.map → encryption-BP8zfxEJ.cjs.map} +1 -1
- package/dist/{encryption-Dlnl5XEU.mjs → encryption-BW7Gh51G.mjs} +3 -3
- package/dist/{encryption-Dlnl5XEU.mjs.map → encryption-BW7Gh51G.mjs.map} +1 -1
- package/dist/{github-mQorSS36.cjs → github-DHpDMBT7.cjs} +3 -3
- package/dist/{github-mQorSS36.cjs.map → github-DHpDMBT7.cjs.map} +1 -1
- package/dist/{github-BdF9tsvl.mjs → github-T_6sK3Rz.mjs} +3 -3
- package/dist/{github-BdF9tsvl.mjs.map → github-T_6sK3Rz.mjs.map} +1 -1
- package/dist/{index-BorYE_uO.d.cts → index-B5TJ-mB6.d.mts} +2 -2
- package/dist/{index-BorYE_uO.d.cts.map → index-B5TJ-mB6.d.mts.map} +1 -1
- package/dist/{index-C3JmluyO.d.mts → index-DV6j4Ydq.d.cts} +2 -2
- package/dist/{index-C3JmluyO.d.mts.map → index-DV6j4Ydq.d.cts.map} +1 -1
- package/dist/index.cjs +1210 -701
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1215 -704
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-CXVnh1J3.mjs → migrate-BrFREmgQ.mjs} +4 -4
- package/dist/{migrate-CXVnh1J3.mjs.map → migrate-BrFREmgQ.mjs.map} +1 -1
- package/dist/{migrate-DzYqA-e3.cjs → migrate-CThJfVHR.cjs} +4 -4
- package/dist/{migrate-DzYqA-e3.cjs.map → migrate-CThJfVHR.cjs.map} +1 -1
- 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-DbDHneLR.mjs.map +1 -1
- package/dist/ready-mR96orkJ.cjs.map +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{types-D7j6QWeX.d.mts → types-CipIIm8f.d.mts} +34 -2
- package/dist/{types-D7j6QWeX.d.mts.map → types-CipIIm8f.d.mts.map} +1 -1
- package/dist/{types-JV4XOY01.d.cts → types-D9b-IYdW.d.cts} +34 -2
- package/dist/{types-JV4XOY01.d.cts.map → types-D9b-IYdW.d.cts.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-Bah36xSG.cjs → workspace-B64Xk1Mb.cjs} +14 -4
- package/dist/workspace-B64Xk1Mb.cjs.map +1 -0
- package/dist/{workspace-D-f58jsP.mjs → workspace-BRZ14o-o.mjs} +9 -6
- package/dist/{workspace-D-f58jsP.mjs.map → workspace-BRZ14o-o.mjs.map} +1 -1
- package/dist/{workspace-BvNXAXQI.mjs → workspace-Bp1xVaOz.mjs} +14 -4
- package/dist/workspace-Bp1xVaOz.mjs.map +1 -0
- package/dist/{workspace-B14L2TrY.cjs → workspace-Dbn07ILl.cjs} +7 -4
- package/dist/{workspace-B14L2TrY.cjs.map → workspace-Dbn07ILl.cjs.map} +1 -1
- package/package.json +13 -13
- package/dist/LocalStateProvider-7HR6SS2t.cjs +0 -48
- package/dist/LocalStateProvider-7HR6SS2t.cjs.map +0 -1
- package/dist/LocalStateProvider-D7QyJoLM.mjs +0 -48
- package/dist/LocalStateProvider-D7QyJoLM.mjs.map +0 -1
- package/dist/LocalStateStore-BupdV-VN.mjs +0 -163
- package/dist/LocalStateStore-BupdV-VN.mjs.map +0 -1
- package/dist/LocalStateStore-DDDU0HkI.cjs +0 -163
- package/dist/LocalStateStore-DDDU0HkI.cjs.map +0 -1
- package/dist/SSMStateProvider-B0Hu1MKZ.mjs +0 -75
- package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +0 -1
- package/dist/SSMStateProvider-VrQ-yeqd.cjs +0 -74
- package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +0 -1
- package/dist/config-CSOK73pY.mjs.map +0 -1
- package/dist/config-DdIu1O_I.cjs.map +0 -1
- package/dist/workspace-Bah36xSG.cjs.map +0 -1
- package/dist/workspace-BvNXAXQI.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
4
4
|
const require_discover = require("./discover-DXNviAh-.cjs");
|
|
5
|
-
const require_config = require("./config-
|
|
6
|
-
const require_workspace = require("./workspace-
|
|
5
|
+
const require_config = require("./config-WMOB9bLL.cjs");
|
|
6
|
+
const require_workspace = require("./workspace-B64Xk1Mb.cjs");
|
|
7
7
|
const require_storage = require("./storage-CSGoC3bK.cjs");
|
|
8
8
|
const require_store = require("./store-DQgxT_WR.cjs");
|
|
9
|
-
const require_workspace$1 = require("./workspace-
|
|
9
|
+
const require_workspace$1 = require("./workspace-Dbn07ILl.cjs");
|
|
10
10
|
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-BCWPRZwD.cjs");
|
|
11
11
|
const require_openapi = require("./openapi.cjs");
|
|
12
12
|
const require_ready = require("./ready-mR96orkJ.cjs");
|
|
13
13
|
const require_backends = require("./backends-v3xDuhO0.cjs");
|
|
14
14
|
const require_emit = require("./emit-Dmc1nvdS.cjs");
|
|
15
|
+
const require_StateStore = require("./StateStore-B4u0wx2t.cjs");
|
|
15
16
|
let commander = require("commander");
|
|
16
17
|
let node_process = require("node:process");
|
|
17
18
|
let node_readline_promises = require("node:readline/promises");
|
|
@@ -27,6 +28,7 @@ let node_child_process = require("node:child_process");
|
|
|
27
28
|
let node_module = require("node:module");
|
|
28
29
|
let chokidar = require("chokidar");
|
|
29
30
|
chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
|
|
31
|
+
let node_net = require("node:net");
|
|
30
32
|
let node_crypto = require("node:crypto");
|
|
31
33
|
let node_url = require("node:url");
|
|
32
34
|
let _geekmidas_constructs_encryption = require("@geekmidas/constructs/encryption");
|
|
@@ -37,6 +39,8 @@ let node_util = require("node:util");
|
|
|
37
39
|
let _geekmidas_cache_postgres = require("@geekmidas/cache/postgres");
|
|
38
40
|
let prompts = require("prompts");
|
|
39
41
|
prompts = require_rolldown_runtime.__toESM(prompts, 1);
|
|
42
|
+
let node_http = require("node:http");
|
|
43
|
+
let node_stream = require("node:stream");
|
|
40
44
|
let http = require("http");
|
|
41
45
|
let http2 = require("http2");
|
|
42
46
|
let stream = require("stream");
|
|
@@ -48,7 +52,7 @@ let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
|
|
|
48
52
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
49
53
|
let node_dns_promises = require("node:dns/promises");
|
|
50
54
|
//#region package.json
|
|
51
|
-
var version = "10.0.0-alpha.
|
|
55
|
+
var version = "10.0.0-alpha.59";
|
|
52
56
|
//#endregion
|
|
53
57
|
//#region src/auth/credentials.ts
|
|
54
58
|
/**
|
|
@@ -762,6 +766,621 @@ async function assignAppPorts(workspace, names, deps) {
|
|
|
762
766
|
running
|
|
763
767
|
};
|
|
764
768
|
}
|
|
769
|
+
/** Overrides `dev.discoveryPort` — `0` serves on a port of its own. */
|
|
770
|
+
const DISCOVERY_PORT_ENV = "GKM_DISCOVERY_PORT";
|
|
771
|
+
/**
|
|
772
|
+
* Set by a workspace's `gkm dev` for the app sessions turbo starts under it:
|
|
773
|
+
* the workspace has printed the connect URL, so they do not repeat it.
|
|
774
|
+
*/
|
|
775
|
+
const DISCOVERY_QUIET_ENV = "GKM_DISCOVERY_QUIET";
|
|
776
|
+
/** The route everything is served under. */
|
|
777
|
+
const BASE = "/__gkm";
|
|
778
|
+
/** `GKM_DISCOVERY_PORT` is set to something that is not a port. */
|
|
779
|
+
var InvalidDiscoveryPort = class extends Error {
|
|
780
|
+
value;
|
|
781
|
+
constructor(value) {
|
|
782
|
+
super(`${DISCOVERY_PORT_ENV}=${value} is not a port. Set it to a number from 0 to 65535 (0 serves discovery on a free port of its own), or unset it to use dev.discoveryPort.`);
|
|
783
|
+
this.value = value;
|
|
784
|
+
this.name = "InvalidDiscoveryPort";
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
/** The port the endpoint uses: the env, then `dev.discoveryPort`, then 4983. */
|
|
788
|
+
function discoveryPort(config, env = process.env) {
|
|
789
|
+
const raw = env[DISCOVERY_PORT_ENV];
|
|
790
|
+
if (raw !== void 0 && raw !== "") {
|
|
791
|
+
const port = Number(raw);
|
|
792
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new InvalidDiscoveryPort(raw);
|
|
793
|
+
return port;
|
|
794
|
+
}
|
|
795
|
+
return config?.discoveryPort ?? 4983;
|
|
796
|
+
}
|
|
797
|
+
/** The machine's registry, shared by every `gkm dev`. */
|
|
798
|
+
function discoveryRegistry(env = process.env) {
|
|
799
|
+
return env["GKM_DEV_REGISTRY"] || (0, node_path.join)((0, node_os.homedir)(), ".gkm", "dev");
|
|
800
|
+
}
|
|
801
|
+
function sessionsDir(registry) {
|
|
802
|
+
return (0, node_path.join)(registry, "sessions");
|
|
803
|
+
}
|
|
804
|
+
/** Whether a process is alive. `EPERM` is someone else's — alive. */
|
|
805
|
+
function alive(pid) {
|
|
806
|
+
try {
|
|
807
|
+
process.kill(pid, 0);
|
|
808
|
+
return true;
|
|
809
|
+
} catch (error) {
|
|
810
|
+
return error.code === "EPERM";
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Every live session, pruning the ones whose process is gone — a `gkm dev`
|
|
815
|
+
* killed outright never got to remove its own.
|
|
816
|
+
*/
|
|
817
|
+
function liveSessions(registry) {
|
|
818
|
+
const dir = sessionsDir(registry);
|
|
819
|
+
let files;
|
|
820
|
+
try {
|
|
821
|
+
files = (0, node_fs.readdirSync)(dir).filter((file) => file.endsWith(".json"));
|
|
822
|
+
} catch {
|
|
823
|
+
return [];
|
|
824
|
+
}
|
|
825
|
+
const records = [];
|
|
826
|
+
for (const file of files) {
|
|
827
|
+
const path = (0, node_path.join)(dir, file);
|
|
828
|
+
let record;
|
|
829
|
+
try {
|
|
830
|
+
record = JSON.parse((0, node_fs.readFileSync)(path, "utf8"));
|
|
831
|
+
} catch {
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
if (alive(record.pid)) {
|
|
835
|
+
records.push(record);
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
(0, node_fs.unlinkSync)(path);
|
|
840
|
+
} catch {}
|
|
841
|
+
}
|
|
842
|
+
return records;
|
|
843
|
+
}
|
|
844
|
+
/** Written whole, then renamed into place, so a reader never sees half. */
|
|
845
|
+
function writeRecord(registry, record) {
|
|
846
|
+
const path = (0, node_path.join)(sessionsDir(registry), `${record.id}.json`);
|
|
847
|
+
const partial = `${path}.${process.pid}.tmp`;
|
|
848
|
+
(0, node_fs.writeFileSync)(partial, JSON.stringify(record), { mode: 384 });
|
|
849
|
+
(0, node_fs.renameSync)(partial, path);
|
|
850
|
+
}
|
|
851
|
+
function removeRecord(registry, id) {
|
|
852
|
+
try {
|
|
853
|
+
(0, node_fs.unlinkSync)((0, node_path.join)(sessionsDir(registry), `${id}.json`));
|
|
854
|
+
} catch {}
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* The token every session prints and every host checks.
|
|
858
|
+
*
|
|
859
|
+
* Shared through the registry, so a URL printed by one session still works
|
|
860
|
+
* after another has taken over hosting. It lives as long as some session does:
|
|
861
|
+
* a session that finds none but itself starts a new one, so a token printed
|
|
862
|
+
* yesterday stops working once everything it was printed by has stopped.
|
|
863
|
+
*/
|
|
864
|
+
function sessionToken(registry, self) {
|
|
865
|
+
const path = (0, node_path.join)(registry, "token");
|
|
866
|
+
if (liveSessions(registry).filter((r) => r.id !== self).length === 0) {
|
|
867
|
+
const stale = `${path}.${self}.stale`;
|
|
868
|
+
try {
|
|
869
|
+
(0, node_fs.renameSync)(path, stale);
|
|
870
|
+
(0, node_fs.unlinkSync)(stale);
|
|
871
|
+
} catch {}
|
|
872
|
+
}
|
|
873
|
+
const fresh = `${path}.${self}.new`;
|
|
874
|
+
(0, node_fs.writeFileSync)(fresh, (0, node_crypto.randomBytes)(32).toString("base64url"), { mode: 384 });
|
|
875
|
+
try {
|
|
876
|
+
(0, node_fs.linkSync)(fresh, path);
|
|
877
|
+
} catch {}
|
|
878
|
+
(0, node_fs.unlinkSync)(fresh);
|
|
879
|
+
return (0, node_fs.readFileSync)(path, "utf8").trim();
|
|
880
|
+
}
|
|
881
|
+
/** `<name>-<8 hex>`: readable, and two checkouts of a project stay apart. */
|
|
882
|
+
function workspaceId(name, root) {
|
|
883
|
+
return `${name.toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "workspace"}-${(0, node_crypto.createHash)("sha256").update(root).digest("hex").slice(0, 8)}`;
|
|
884
|
+
}
|
|
885
|
+
/** Whether anything accepts connections on a local port. */
|
|
886
|
+
function listening(port) {
|
|
887
|
+
return new Promise((resolve) => {
|
|
888
|
+
const socket = (0, node_net.connect)({
|
|
889
|
+
port,
|
|
890
|
+
host: "localhost"
|
|
891
|
+
});
|
|
892
|
+
const done = (open) => {
|
|
893
|
+
socket.destroy();
|
|
894
|
+
resolve(open);
|
|
895
|
+
};
|
|
896
|
+
socket.setTimeout(300, () => done(false));
|
|
897
|
+
socket.once("connect", () => done(true));
|
|
898
|
+
socket.once("error", () => done(false));
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
/** Which data APIs the forwarder matches by prefix, rather than exactly. */
|
|
902
|
+
const PREFIXED = /* @__PURE__ */ new Set(["telescope", "database"]);
|
|
903
|
+
/** Whether `path` on an app is one of its data APIs. */
|
|
904
|
+
function servesData(app, path) {
|
|
905
|
+
return app.dataApis.some((api) => PREFIXED.has(api.kind) ? path === api.path || path.startsWith(`${api.path}/`) : path === api.path);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Every workspace the sessions describe, one entry per root.
|
|
909
|
+
*
|
|
910
|
+
* An app listed only by its workspace's session — a web app turbo started, an
|
|
911
|
+
* API whose own `gkm dev` has not registered yet — is `ready` when its port
|
|
912
|
+
* answers and `starting` until then.
|
|
913
|
+
*/
|
|
914
|
+
async function collect(records) {
|
|
915
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
916
|
+
for (const record of records) {
|
|
917
|
+
const group = byRoot.get(record.workspace.root) ?? [];
|
|
918
|
+
group.push(record);
|
|
919
|
+
byRoot.set(record.workspace.root, group);
|
|
920
|
+
}
|
|
921
|
+
const views = [];
|
|
922
|
+
for (const [root, group] of byRoot) {
|
|
923
|
+
group.sort((a, b) => Number(b.role === "workspace") - Number(a.role === "workspace") || a.startedAt.localeCompare(b.startedAt));
|
|
924
|
+
const lead = group[0];
|
|
925
|
+
const id = workspaceId(lead.workspace.name, root);
|
|
926
|
+
const apps = /* @__PURE__ */ new Map();
|
|
927
|
+
for (const record of group) for (const app of record.apps) {
|
|
928
|
+
const own = record.role === "app";
|
|
929
|
+
if (!own && apps.has(app.name)) continue;
|
|
930
|
+
apps.set(app.name, {
|
|
931
|
+
app,
|
|
932
|
+
pid: own ? record.pid : null
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
const discovered = await Promise.all([...apps.values()].map(async ({ app, pid }) => {
|
|
936
|
+
const url = `http://localhost:${app.port}`;
|
|
937
|
+
const status = pid === null ? await listening(app.port) ? "ready" : "starting" : app.status;
|
|
938
|
+
return {
|
|
939
|
+
name: app.name,
|
|
940
|
+
type: app.type,
|
|
941
|
+
port: app.port,
|
|
942
|
+
url,
|
|
943
|
+
publicUrl: app.publicUrl ?? null,
|
|
944
|
+
status,
|
|
945
|
+
reloads: app.reloads,
|
|
946
|
+
pid,
|
|
947
|
+
dataApis: app.dataApis.map((api) => ({
|
|
948
|
+
kind: api.kind,
|
|
949
|
+
path: api.path,
|
|
950
|
+
url: `${url}${api.path}`,
|
|
951
|
+
proxy: `${BASE}/workspaces/${id}/apps/${encodeURIComponent(app.name)}${api.path}`
|
|
952
|
+
}))
|
|
953
|
+
};
|
|
954
|
+
}));
|
|
955
|
+
discovered.sort((a, b) => a.name.localeCompare(b.name));
|
|
956
|
+
views.push({
|
|
957
|
+
id,
|
|
958
|
+
name: lead.workspace.name,
|
|
959
|
+
root,
|
|
960
|
+
stage: lead.workspace.stage,
|
|
961
|
+
manifest: group.find((r) => r.manifest)?.manifest ?? null,
|
|
962
|
+
apps: discovered,
|
|
963
|
+
allowedOrigins: [...new Set(group.flatMap((r) => r.allowedOrigins))]
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
return views.sort((a, b) => a.name.localeCompare(b.name) || a.root.localeCompare(b.root));
|
|
967
|
+
}
|
|
968
|
+
/** What changed between two views, as events. */
|
|
969
|
+
function changes(before, after) {
|
|
970
|
+
const out = [];
|
|
971
|
+
const previous = new Map(before.map((w) => [w.id, w]));
|
|
972
|
+
const next = new Map(after.map((w) => [w.id, w]));
|
|
973
|
+
for (const workspace of after) {
|
|
974
|
+
const was = previous.get(workspace.id);
|
|
975
|
+
if (!was) out.push({
|
|
976
|
+
workspace,
|
|
977
|
+
event: {
|
|
978
|
+
type: "workspace.started",
|
|
979
|
+
workspace: workspace.id
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
const wasApps = new Map((was?.apps ?? []).map((a) => [a.name, a]));
|
|
983
|
+
for (const app of workspace.apps) {
|
|
984
|
+
const old = wasApps.get(app.name);
|
|
985
|
+
const type = !old ? "app.started" : app.reloads > old.reloads ? "app.reloaded" : app.status !== old.status || app.port !== old.port || app.dataApis.length !== old.dataApis.length ? "app.status" : void 0;
|
|
986
|
+
if (type) out.push({
|
|
987
|
+
workspace,
|
|
988
|
+
event: {
|
|
989
|
+
type,
|
|
990
|
+
workspace: workspace.id,
|
|
991
|
+
app
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
for (const old of was?.apps ?? []) if (!workspace.apps.some((a) => a.name === old.name)) out.push({
|
|
996
|
+
workspace,
|
|
997
|
+
event: {
|
|
998
|
+
type: "app.stopped",
|
|
999
|
+
workspace: workspace.id,
|
|
1000
|
+
app: old
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
for (const workspace of before) {
|
|
1005
|
+
if (next.has(workspace.id)) continue;
|
|
1006
|
+
for (const app of workspace.apps) out.push({
|
|
1007
|
+
workspace,
|
|
1008
|
+
event: {
|
|
1009
|
+
type: "app.stopped",
|
|
1010
|
+
workspace: workspace.id,
|
|
1011
|
+
app
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
out.push({
|
|
1015
|
+
workspace,
|
|
1016
|
+
event: {
|
|
1017
|
+
type: "workspace.stopped",
|
|
1018
|
+
workspace: workspace.id
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
return out;
|
|
1023
|
+
}
|
|
1024
|
+
/** A browser with no `Origin` — curl, a local process — sees everything. */
|
|
1025
|
+
function visibleTo(workspace, origin) {
|
|
1026
|
+
return origin === void 0 || workspace.allowedOrigins.includes(origin);
|
|
1027
|
+
}
|
|
1028
|
+
function publicView({ allowedOrigins: _, ...workspace }) {
|
|
1029
|
+
return workspace;
|
|
1030
|
+
}
|
|
1031
|
+
/** Headers an app's response is passed on with; nothing that sets state. */
|
|
1032
|
+
const FORWARDED_RESPONSE_HEADERS = [
|
|
1033
|
+
"content-type",
|
|
1034
|
+
"cache-control",
|
|
1035
|
+
"etag",
|
|
1036
|
+
"last-modified"
|
|
1037
|
+
];
|
|
1038
|
+
/**
|
|
1039
|
+
* The HTTP side: serves the registry it reads, checking every request.
|
|
1040
|
+
* @internal Exported for testing
|
|
1041
|
+
*/
|
|
1042
|
+
var DiscoveryHost = class {
|
|
1043
|
+
registry;
|
|
1044
|
+
token;
|
|
1045
|
+
pollMs;
|
|
1046
|
+
server;
|
|
1047
|
+
view = [];
|
|
1048
|
+
streams = /* @__PURE__ */ new Set();
|
|
1049
|
+
scanning;
|
|
1050
|
+
timers = [];
|
|
1051
|
+
sequence = 0;
|
|
1052
|
+
constructor(registry, token, pollMs) {
|
|
1053
|
+
this.registry = registry;
|
|
1054
|
+
this.token = token;
|
|
1055
|
+
this.pollMs = pollMs;
|
|
1056
|
+
}
|
|
1057
|
+
/** The port it is bound to, once it is. */
|
|
1058
|
+
get port() {
|
|
1059
|
+
return (this.server?.address())?.port;
|
|
1060
|
+
}
|
|
1061
|
+
/** Binds the port; `false` when another process already has it. */
|
|
1062
|
+
async listen(port) {
|
|
1063
|
+
const server = (0, node_http.createServer)((request, response) => {
|
|
1064
|
+
this.handle(request, response).catch(() => {
|
|
1065
|
+
if (!response.headersSent) reply(response, 500, { error: "DiscoveryFailed" });
|
|
1066
|
+
else response.destroy();
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
if (!await new Promise((resolve) => {
|
|
1070
|
+
server.once("error", () => resolve(false));
|
|
1071
|
+
server.listen(port, "127.0.0.1", () => resolve(true));
|
|
1072
|
+
})) return false;
|
|
1073
|
+
server.unref();
|
|
1074
|
+
this.server = server;
|
|
1075
|
+
this.view = await this.scan();
|
|
1076
|
+
this.timers.push(setInterval(() => void this.scan(), this.pollMs).unref(), setInterval(() => {
|
|
1077
|
+
for (const { response } of this.streams) response.write(": ping\n\n");
|
|
1078
|
+
}, 15e3).unref());
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
async close() {
|
|
1082
|
+
for (const timer of this.timers) clearInterval(timer);
|
|
1083
|
+
this.timers = [];
|
|
1084
|
+
for (const { response } of this.streams) response.end();
|
|
1085
|
+
this.streams.clear();
|
|
1086
|
+
const server = this.server;
|
|
1087
|
+
this.server = void 0;
|
|
1088
|
+
if (!server) return;
|
|
1089
|
+
await new Promise((resolve) => {
|
|
1090
|
+
server.close(() => resolve());
|
|
1091
|
+
server.closeAllConnections();
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
/** Reads the registry, and tells every stream what changed. One at a time. */
|
|
1095
|
+
scan() {
|
|
1096
|
+
const run = (this.scanning ?? Promise.resolve()).then(async () => {
|
|
1097
|
+
const next = await collect(liveSessions(this.registry));
|
|
1098
|
+
for (const { workspace, event } of changes(this.view, next)) this.broadcast(workspace, event);
|
|
1099
|
+
this.view = next;
|
|
1100
|
+
return next;
|
|
1101
|
+
});
|
|
1102
|
+
this.scanning = run.catch(() => this.view);
|
|
1103
|
+
return run;
|
|
1104
|
+
}
|
|
1105
|
+
broadcast(workspace, event) {
|
|
1106
|
+
this.sequence += 1;
|
|
1107
|
+
for (const stream of this.streams) {
|
|
1108
|
+
if (!visibleTo(workspace, stream.origin)) continue;
|
|
1109
|
+
send(stream.response, event, this.sequence);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
snapshot(origin) {
|
|
1113
|
+
return {
|
|
1114
|
+
version: 1,
|
|
1115
|
+
host: {
|
|
1116
|
+
pid: process.pid,
|
|
1117
|
+
port: this.port ?? 0
|
|
1118
|
+
},
|
|
1119
|
+
events: `${BASE}/events`,
|
|
1120
|
+
workspaces: this.view.filter((w) => visibleTo(w, origin)).map(publicView)
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
async handle(request, response) {
|
|
1124
|
+
const port = this.port;
|
|
1125
|
+
const host = request.headers.host;
|
|
1126
|
+
if (host !== `127.0.0.1:${port}` && host !== `localhost:${port}`) {
|
|
1127
|
+
reply(response, 403, {
|
|
1128
|
+
error: "HostNotAllowed",
|
|
1129
|
+
message: `Reach discovery at http://127.0.0.1:${port}.`
|
|
1130
|
+
});
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const origin = request.headers.origin;
|
|
1134
|
+
const allowed = this.view.flatMap((w) => w.allowedOrigins);
|
|
1135
|
+
if (origin !== void 0 && !allowed.includes(origin)) {
|
|
1136
|
+
reply(response, 403, {
|
|
1137
|
+
error: "OriginNotAllowed",
|
|
1138
|
+
message: `Add ${origin} to dev.allowedOrigins in gkm.config.ts to let it read gkm dev.`
|
|
1139
|
+
});
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
if (origin !== void 0) {
|
|
1143
|
+
response.setHeader("access-control-allow-origin", origin);
|
|
1144
|
+
response.setHeader("vary", "Origin");
|
|
1145
|
+
}
|
|
1146
|
+
if (request.method === "OPTIONS") {
|
|
1147
|
+
response.setHeader("access-control-allow-methods", "GET, HEAD");
|
|
1148
|
+
response.setHeader("access-control-allow-headers", "Authorization");
|
|
1149
|
+
response.setHeader("access-control-max-age", "600");
|
|
1150
|
+
if (request.headers["access-control-request-private-network"]) response.setHeader("access-control-allow-private-network", "true");
|
|
1151
|
+
response.writeHead(204).end();
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const url = new URL(request.url ?? "/", `http://${host}`);
|
|
1155
|
+
if (!this.authorized(request, url)) {
|
|
1156
|
+
reply(response, 401, {
|
|
1157
|
+
error: "TokenRequired",
|
|
1158
|
+
message: "Use the connect URL gkm dev printed, or send its token as `Authorization: Bearer <token>`."
|
|
1159
|
+
});
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
1163
|
+
response.setHeader("allow", "GET, HEAD, OPTIONS");
|
|
1164
|
+
reply(response, 405, {
|
|
1165
|
+
error: "ReadOnly",
|
|
1166
|
+
message: "Discovery is read-only: GET and HEAD."
|
|
1167
|
+
});
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const path = url.pathname.replace(/\/+$/, "") || "/";
|
|
1171
|
+
if (path === BASE) {
|
|
1172
|
+
await this.scan();
|
|
1173
|
+
reply(response, 200, this.snapshot(origin));
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (path === `${BASE}/events`) {
|
|
1177
|
+
await this.scan();
|
|
1178
|
+
this.stream(response, origin);
|
|
1179
|
+
response.once("close", () => {
|
|
1180
|
+
for (const stream of this.streams) if (stream.response === response) this.streams.delete(stream);
|
|
1181
|
+
});
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const forwarded = path.match(/^\/__gkm\/workspaces\/([^/]+)\/apps\/([^/]+)(\/.*)$/);
|
|
1185
|
+
if (forwarded) {
|
|
1186
|
+
const [, id, name, rest] = forwarded;
|
|
1187
|
+
await this.forward(request, response, origin, id, decodeURIComponent(name), rest, url);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
reply(response, 404, { error: "NotFound" });
|
|
1191
|
+
}
|
|
1192
|
+
authorized(request, url) {
|
|
1193
|
+
const header = request.headers.authorization;
|
|
1194
|
+
const given = header?.startsWith("Bearer ") ? header.slice(7).trim() : url.searchParams.get("token");
|
|
1195
|
+
if (!given) return false;
|
|
1196
|
+
const a = Buffer.from(given);
|
|
1197
|
+
const b = Buffer.from(this.token);
|
|
1198
|
+
return a.length === b.length && (0, node_crypto.timingSafeEqual)(a, b);
|
|
1199
|
+
}
|
|
1200
|
+
stream(response, origin) {
|
|
1201
|
+
response.writeHead(200, {
|
|
1202
|
+
"content-type": "text/event-stream",
|
|
1203
|
+
"cache-control": "no-cache",
|
|
1204
|
+
connection: "keep-alive"
|
|
1205
|
+
});
|
|
1206
|
+
response.write("retry: 1000\n\n");
|
|
1207
|
+
send(response, {
|
|
1208
|
+
type: "snapshot",
|
|
1209
|
+
snapshot: this.snapshot(origin)
|
|
1210
|
+
});
|
|
1211
|
+
this.streams.add({
|
|
1212
|
+
origin,
|
|
1213
|
+
response
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
/** Passes a read of one data API on to the app that serves it. */
|
|
1217
|
+
async forward(request, response, origin, id, name, path, url) {
|
|
1218
|
+
const app = this.view.find((w) => w.id === id && visibleTo(w, origin))?.apps.find((a) => a.name === name);
|
|
1219
|
+
if (!app) {
|
|
1220
|
+
reply(response, 404, { error: "AppNotFound" });
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (!servesData(app, path)) {
|
|
1224
|
+
reply(response, 404, {
|
|
1225
|
+
error: "NotADataApi",
|
|
1226
|
+
message: `Only ${name}'s data APIs are forwarded: ${app.dataApis.map((a) => a.path).join(", ") || "it has none"}.`
|
|
1227
|
+
});
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
const search = new URLSearchParams(url.searchParams);
|
|
1231
|
+
search.delete("token");
|
|
1232
|
+
const query = search.size > 0 ? `?${search}` : "";
|
|
1233
|
+
const abort = new AbortController();
|
|
1234
|
+
response.once("close", () => abort.abort());
|
|
1235
|
+
let upstream;
|
|
1236
|
+
try {
|
|
1237
|
+
upstream = await fetch(`http://localhost:${app.port}${path}${query}`, {
|
|
1238
|
+
method: request.method,
|
|
1239
|
+
headers: { accept: request.headers.accept ?? "*/*" },
|
|
1240
|
+
signal: abort.signal
|
|
1241
|
+
});
|
|
1242
|
+
} catch {
|
|
1243
|
+
reply(response, 502, {
|
|
1244
|
+
error: "AppUnavailable",
|
|
1245
|
+
message: `${name} is not answering on port ${app.port}.`
|
|
1246
|
+
});
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
const headers = {};
|
|
1250
|
+
for (const key of FORWARDED_RESPONSE_HEADERS) {
|
|
1251
|
+
const value = upstream.headers.get(key);
|
|
1252
|
+
if (value) headers[key] = value;
|
|
1253
|
+
}
|
|
1254
|
+
response.writeHead(upstream.status, headers);
|
|
1255
|
+
if (!upstream.body || request.method === "HEAD") {
|
|
1256
|
+
response.end();
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
node_stream.Readable.fromWeb(upstream.body).on("error", () => response.destroy()).pipe(response);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
function reply(response, status, body) {
|
|
1263
|
+
response.writeHead(status, { "content-type": "application/json" });
|
|
1264
|
+
response.end(JSON.stringify(body));
|
|
1265
|
+
}
|
|
1266
|
+
function send(response, event, id) {
|
|
1267
|
+
response.write(`${id === void 0 ? "" : `id: ${id}\n`}event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* One `gkm dev`'s membership: its record in the registry, and — while it holds
|
|
1271
|
+
* the port — the endpoint itself.
|
|
1272
|
+
*/
|
|
1273
|
+
var DiscoverySession = class DiscoverySession {
|
|
1274
|
+
registry;
|
|
1275
|
+
port;
|
|
1276
|
+
record;
|
|
1277
|
+
token;
|
|
1278
|
+
pollMs;
|
|
1279
|
+
host;
|
|
1280
|
+
election;
|
|
1281
|
+
left = false;
|
|
1282
|
+
cleanup = () => removeRecord(this.registry, this.record.id);
|
|
1283
|
+
constructor(registry, port, record, token, pollMs) {
|
|
1284
|
+
this.registry = registry;
|
|
1285
|
+
this.port = port;
|
|
1286
|
+
this.record = record;
|
|
1287
|
+
this.token = token;
|
|
1288
|
+
this.pollMs = pollMs;
|
|
1289
|
+
}
|
|
1290
|
+
static async join(options) {
|
|
1291
|
+
const registry = options.registry ?? discoveryRegistry();
|
|
1292
|
+
(0, node_fs.mkdirSync)(sessionsDir(registry), {
|
|
1293
|
+
recursive: true,
|
|
1294
|
+
mode: 448
|
|
1295
|
+
});
|
|
1296
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1297
|
+
const record = {
|
|
1298
|
+
id: `${process.pid}-${(0, node_crypto.randomBytes)(4).toString("hex")}`,
|
|
1299
|
+
pid: process.pid,
|
|
1300
|
+
role: options.role,
|
|
1301
|
+
startedAt: now,
|
|
1302
|
+
updatedAt: now,
|
|
1303
|
+
workspace: options.workspace,
|
|
1304
|
+
allowedOrigins: [...options.allowedOrigins ?? []],
|
|
1305
|
+
...options.manifest ? { manifest: options.manifest } : {},
|
|
1306
|
+
apps: options.apps
|
|
1307
|
+
};
|
|
1308
|
+
writeRecord(registry, record);
|
|
1309
|
+
const token = sessionToken(registry, record.id);
|
|
1310
|
+
const session = new DiscoverySession(registry, options.port ?? 4983, record, token, options.pollMs ?? 500);
|
|
1311
|
+
process.once("exit", session.cleanup);
|
|
1312
|
+
await session.elect();
|
|
1313
|
+
if (!session.host && session.port !== 0) session.election = setInterval(() => void session.elect(), options.electionMs ?? 1e3).unref();
|
|
1314
|
+
return session;
|
|
1315
|
+
}
|
|
1316
|
+
/** Whether this session is the one serving the endpoint. */
|
|
1317
|
+
get hosting() {
|
|
1318
|
+
return this.host !== void 0;
|
|
1319
|
+
}
|
|
1320
|
+
/** The port clients reach — this host's own, when it chose one. */
|
|
1321
|
+
get endpointPort() {
|
|
1322
|
+
return this.host?.port ?? this.port;
|
|
1323
|
+
}
|
|
1324
|
+
/** Jupyter-style: the endpoint, with the token that opens it. */
|
|
1325
|
+
get connectUrl() {
|
|
1326
|
+
return `http://127.0.0.1:${this.endpointPort}${BASE}?token=${this.token}`;
|
|
1327
|
+
}
|
|
1328
|
+
/** Changes one of this session's apps, and tells the endpoint. */
|
|
1329
|
+
updateApp(name, change) {
|
|
1330
|
+
if (this.left) return;
|
|
1331
|
+
this.record.apps = this.record.apps.map((app) => app.name === name ? change(app) : app);
|
|
1332
|
+
this.record.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1333
|
+
writeRecord(this.registry, this.record);
|
|
1334
|
+
this.host?.scan();
|
|
1335
|
+
}
|
|
1336
|
+
/** Leaves the registry, and the port to whichever session takes it next. */
|
|
1337
|
+
async leave() {
|
|
1338
|
+
if (this.left) return;
|
|
1339
|
+
this.left = true;
|
|
1340
|
+
if (this.election) clearInterval(this.election);
|
|
1341
|
+
process.removeListener("exit", this.cleanup);
|
|
1342
|
+
this.cleanup();
|
|
1343
|
+
await this.host?.close();
|
|
1344
|
+
this.host = void 0;
|
|
1345
|
+
if (liveSessions(this.registry).length === 0) try {
|
|
1346
|
+
(0, node_fs.unlinkSync)((0, node_path.join)(this.registry, "token"));
|
|
1347
|
+
} catch {}
|
|
1348
|
+
}
|
|
1349
|
+
async elect() {
|
|
1350
|
+
if (this.host || this.left) return;
|
|
1351
|
+
const host = new DiscoveryHost(this.registry, this.token, this.pollMs);
|
|
1352
|
+
if (!await host.listen(this.port)) return;
|
|
1353
|
+
if (this.left) {
|
|
1354
|
+
await host.close();
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
this.host = host;
|
|
1358
|
+
if (this.election) clearInterval(this.election);
|
|
1359
|
+
this.election = void 0;
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
/** Joins the machine's discovery; see {@link DiscoverySession}. */
|
|
1363
|
+
function joinDiscovery(options) {
|
|
1364
|
+
return DiscoverySession.join(options);
|
|
1365
|
+
}
|
|
1366
|
+
/** The data APIs a generated dev app mounts, from what `gkm dev` built. */
|
|
1367
|
+
function dataApisOf(options) {
|
|
1368
|
+
if (options.selfServing) return [];
|
|
1369
|
+
return [
|
|
1370
|
+
...options.telescopePath ? [{
|
|
1371
|
+
kind: "telescope",
|
|
1372
|
+
path: `${options.telescopePath}/api`
|
|
1373
|
+
}] : [],
|
|
1374
|
+
...options.databaseApi ? [{
|
|
1375
|
+
kind: "database",
|
|
1376
|
+
path: options.databaseApi
|
|
1377
|
+
}] : [],
|
|
1378
|
+
...options.openApi ? [{
|
|
1379
|
+
kind: "openapi",
|
|
1380
|
+
path: "/__docs"
|
|
1381
|
+
}] : []
|
|
1382
|
+
];
|
|
1383
|
+
}
|
|
765
1384
|
//#endregion
|
|
766
1385
|
//#region ../../node_modules/.pnpm/@hono+node-server@1.19.5_hono@4.13.8/node_modules/@hono/node-server/dist/index.mjs
|
|
767
1386
|
var RequestError = class extends Error {
|
|
@@ -1427,6 +2046,7 @@ async function devCommand(options) {
|
|
|
1427
2046
|
const runtime = config.runtime ?? "node";
|
|
1428
2047
|
let secretsJsonPath;
|
|
1429
2048
|
let publicUrl;
|
|
2049
|
+
let manifest;
|
|
1430
2050
|
const appSecrets = await require_workspace$1.loadSecretsForApp(workspace ? await require_store.secretsStoreFor(workspace, config.stages.local) : new require_store.FileSecretsStore(secretsRoot), config.stages.local, workspaceAppName);
|
|
1431
2051
|
if (workspace) {
|
|
1432
2052
|
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, {
|
|
@@ -1441,6 +2061,7 @@ async function devCommand(options) {
|
|
|
1441
2061
|
Object.assign(appSecrets, reconciled.env);
|
|
1442
2062
|
const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && require_config.appKey(r.id) === workspaceAppName);
|
|
1443
2063
|
publicUrl = own ? reconciled.env[own.envKey] : void 0;
|
|
2064
|
+
manifest = reconciled.manifest;
|
|
1444
2065
|
}
|
|
1445
2066
|
if (Object.keys(appSecrets).length > 0) {
|
|
1446
2067
|
const secretsDir = (0, node_path.join)(secretsRoot, ".gkm");
|
|
@@ -1453,6 +2074,38 @@ async function devCommand(options) {
|
|
|
1453
2074
|
devServer.label = workspaceAppName ?? "server";
|
|
1454
2075
|
devServer.publicUrl = publicUrl;
|
|
1455
2076
|
await devServer.start();
|
|
2077
|
+
const discovery = await joinDiscovery({
|
|
2078
|
+
role: "app",
|
|
2079
|
+
port: discoveryPort(workspace.dev),
|
|
2080
|
+
workspace: {
|
|
2081
|
+
name: workspace.name,
|
|
2082
|
+
root: workspace.root,
|
|
2083
|
+
stage: workspace.stages.local
|
|
2084
|
+
},
|
|
2085
|
+
allowedOrigins: workspace.dev?.allowedOrigins ?? [],
|
|
2086
|
+
...manifest ? { manifest } : {},
|
|
2087
|
+
apps: [{
|
|
2088
|
+
name: workspaceAppName,
|
|
2089
|
+
type: "backend",
|
|
2090
|
+
port: devServer.port,
|
|
2091
|
+
...publicUrl ? { publicUrl } : {},
|
|
2092
|
+
status: "ready",
|
|
2093
|
+
reloads: 0,
|
|
2094
|
+
dataApis: dataApisOf({
|
|
2095
|
+
selfServing: devServer.selfServing,
|
|
2096
|
+
telescopePath: telescope?.path,
|
|
2097
|
+
databaseApi: initial.databaseApi,
|
|
2098
|
+
openApi: enableOpenApi
|
|
2099
|
+
})
|
|
2100
|
+
}]
|
|
2101
|
+
});
|
|
2102
|
+
if (!process.env["GKM_DISCOVERY_QUIET"]) announce(discovery);
|
|
2103
|
+
devServer.onStatus = (status) => discovery.updateApp(workspaceAppName, (app) => ({
|
|
2104
|
+
...app,
|
|
2105
|
+
status,
|
|
2106
|
+
port: devServer.port,
|
|
2107
|
+
reloads: app.status === "reloading" && status === "ready" ? app.reloads + 1 : app.reloads
|
|
2108
|
+
}));
|
|
1456
2109
|
const hooksFile = (config.hooks?.server?.split("#"))?.[0];
|
|
1457
2110
|
const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) => (0, node_path.isAbsolute)(p) ? (0, node_path.relative)(appRoot, p) : p.replace(/^\.\//, ""));
|
|
1458
2111
|
const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
|
|
@@ -1493,7 +2146,11 @@ async function devCommand(options) {
|
|
|
1493
2146
|
if (isShuttingDown) return;
|
|
1494
2147
|
isShuttingDown = true;
|
|
1495
2148
|
logger$9.log("\n🛑 Shutting down...");
|
|
1496
|
-
Promise.all([
|
|
2149
|
+
Promise.all([
|
|
2150
|
+
watcher.close(),
|
|
2151
|
+
devServer.stop(),
|
|
2152
|
+
discovery.leave()
|
|
2153
|
+
]).catch((err) => {
|
|
1497
2154
|
logger$9.error("Error during shutdown:", err);
|
|
1498
2155
|
}).finally(() => {
|
|
1499
2156
|
process.exit(0);
|
|
@@ -1737,9 +2394,9 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1737
2394
|
for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
1738
2395
|
logger$9.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
|
|
1739
2396
|
if (options.migrate || options.seed) {
|
|
1740
|
-
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-
|
|
2397
|
+
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-CThJfVHR.cjs"));
|
|
1741
2398
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1742
|
-
} else await Promise.resolve().then(() => require("./migrate-
|
|
2399
|
+
} else await Promise.resolve().then(() => require("./migrate-CThJfVHR.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1743
2400
|
logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1744
2401
|
});
|
|
1745
2402
|
const appUrls = {};
|
|
@@ -1794,12 +2451,39 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1794
2451
|
break;
|
|
1795
2452
|
}
|
|
1796
2453
|
}
|
|
2454
|
+
const discovery = await joinDiscovery({
|
|
2455
|
+
role: "workspace",
|
|
2456
|
+
port: discoveryPort(workspace.dev),
|
|
2457
|
+
workspace: {
|
|
2458
|
+
name: workspace.name,
|
|
2459
|
+
root: workspace.root,
|
|
2460
|
+
stage: workspace.stages.local
|
|
2461
|
+
},
|
|
2462
|
+
allowedOrigins: workspace.dev?.allowedOrigins ?? [],
|
|
2463
|
+
...reconciled.manifest ? { manifest: reconciled.manifest } : {},
|
|
2464
|
+
apps: buildOrder.flatMap((name) => {
|
|
2465
|
+
const app = workspace.apps[name];
|
|
2466
|
+
if (!app) return [];
|
|
2467
|
+
const publicUrl = Object.entries(appUrls).find(([id]) => require_config.appKey(id) === name)?.[1];
|
|
2468
|
+
return [{
|
|
2469
|
+
name,
|
|
2470
|
+
type: app.type,
|
|
2471
|
+
port: app.port,
|
|
2472
|
+
...publicUrl ? { publicUrl } : {},
|
|
2473
|
+
status: "starting",
|
|
2474
|
+
reloads: 0,
|
|
2475
|
+
dataApis: []
|
|
2476
|
+
}];
|
|
2477
|
+
})
|
|
2478
|
+
});
|
|
2479
|
+
announce(discovery);
|
|
1797
2480
|
const turboEnv = {
|
|
1798
2481
|
...process.env,
|
|
1799
2482
|
...secretsEnv,
|
|
1800
2483
|
NODE_ENV: "development",
|
|
1801
2484
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {},
|
|
1802
|
-
...options.fake ? { [require_workspace$1.FAKE_ENV]: "1" } : {}
|
|
2485
|
+
...options.fake ? { [require_workspace$1.FAKE_ENV]: "1" } : {},
|
|
2486
|
+
[DISCOVERY_QUIET_ENV]: "1"
|
|
1803
2487
|
};
|
|
1804
2488
|
logger$9.log(`🚀 Starting ${appCount} app(s) — each builds before it answers, and says so when it is ready`);
|
|
1805
2489
|
const turboProcess = (0, node_child_process.spawn)("pnpm", [
|
|
@@ -1819,6 +2503,7 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1819
2503
|
isShuttingDown = true;
|
|
1820
2504
|
logger$9.log("\n🛑 Shutting down workspace...");
|
|
1821
2505
|
closeFakes(fakes);
|
|
2506
|
+
discovery.leave();
|
|
1822
2507
|
const pid = turboProcess.pid;
|
|
1823
2508
|
if (pid) try {
|
|
1824
2509
|
process.kill(-pid, "SIGTERM");
|
|
@@ -2059,6 +2744,13 @@ start({
|
|
|
2059
2744
|
});
|
|
2060
2745
|
`;
|
|
2061
2746
|
}
|
|
2747
|
+
/**
|
|
2748
|
+
* The discovery endpoint's connect URL, Jupyter-style: the token in it is what
|
|
2749
|
+
* lets a console read what this machine is running.
|
|
2750
|
+
*/
|
|
2751
|
+
function announce(discovery) {
|
|
2752
|
+
logger$9.log(`🧭 Discovery: ${discovery.connectUrl}`);
|
|
2753
|
+
}
|
|
2062
2754
|
/** Run `fn` with `console.log` muted — warnings and errors still print. */
|
|
2063
2755
|
async function quietly(fn) {
|
|
2064
2756
|
const log = console.log;
|
|
@@ -2156,6 +2848,12 @@ var DevServer = class {
|
|
|
2156
2848
|
}
|
|
2157
2849
|
/** What the ready line calls this app. */
|
|
2158
2850
|
label = "server";
|
|
2851
|
+
/** Told when the server is ready, reloading, or exits on its own. */
|
|
2852
|
+
onStatus;
|
|
2853
|
+
/** The port it is serving on. */
|
|
2854
|
+
get port() {
|
|
2855
|
+
return this.actualPort;
|
|
2856
|
+
}
|
|
2159
2857
|
/**
|
|
2160
2858
|
* Where the app is reached — its HTTPS address behind the edge, when it has
|
|
2161
2859
|
* one. The local port is only what the edge forwards to.
|
|
@@ -2191,9 +2889,11 @@ var DevServer = class {
|
|
|
2191
2889
|
this.serverProcess.on("error", (error) => {
|
|
2192
2890
|
logger$9.error("❌ Server error:", error);
|
|
2193
2891
|
});
|
|
2194
|
-
this.serverProcess
|
|
2892
|
+
const child = this.serverProcess;
|
|
2893
|
+
child.on("exit", (code, signal) => {
|
|
2195
2894
|
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$9.error(`❌ Server exited with code ${code}`);
|
|
2196
2895
|
this.isRunning = false;
|
|
2896
|
+
if (this.serverProcess === child) this.onStatus?.("stopped");
|
|
2197
2897
|
});
|
|
2198
2898
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
2199
2899
|
if (this.isRunning) {
|
|
@@ -2207,6 +2907,7 @@ var DevServer = class {
|
|
|
2207
2907
|
...this.databaseApi ? [`db ${this.databaseApi}`] : []
|
|
2208
2908
|
];
|
|
2209
2909
|
if (tools.length > 0) logger$9.log(` ${tools.join(" · ")}`);
|
|
2910
|
+
this.onStatus?.("ready");
|
|
2210
2911
|
}
|
|
2211
2912
|
}
|
|
2212
2913
|
async stop() {
|
|
@@ -2232,6 +2933,7 @@ var DevServer = class {
|
|
|
2232
2933
|
}
|
|
2233
2934
|
async restart() {
|
|
2234
2935
|
const portToReuse = this.actualPort;
|
|
2936
|
+
this.onStatus?.("reloading");
|
|
2235
2937
|
await this.stop();
|
|
2236
2938
|
let attempts = 0;
|
|
2237
2939
|
while (attempts < 30) {
|
|
@@ -4313,12 +5015,6 @@ function createEmptyState(stage, projectId, environmentId) {
|
|
|
4313
5015
|
};
|
|
4314
5016
|
}
|
|
4315
5017
|
/**
|
|
4316
|
-
* Get application ID from state
|
|
4317
|
-
*/
|
|
4318
|
-
function getApplicationId(state, appName) {
|
|
4319
|
-
return state?.applications[appName];
|
|
4320
|
-
}
|
|
4321
|
-
/**
|
|
4322
5018
|
* Set application ID in state (mutates state)
|
|
4323
5019
|
*/
|
|
4324
5020
|
function setApplicationId(state, appName, applicationId) {
|
|
@@ -7201,6 +7897,113 @@ function applicationName(identity, app) {
|
|
|
7201
7897
|
return slug$1(app) === identity.project ? `${identity.stage}-${identity.scope}` : (0, _geekmidas_manifest.scopedName)([identity.stage, identity.scope], app);
|
|
7202
7898
|
}
|
|
7203
7899
|
//#endregion
|
|
7900
|
+
//#region src/deploy/journal.ts
|
|
7901
|
+
var DeployJournal = class DeployJournal {
|
|
7902
|
+
store;
|
|
7903
|
+
stage;
|
|
7904
|
+
state;
|
|
7905
|
+
resources;
|
|
7906
|
+
version;
|
|
7907
|
+
existed;
|
|
7908
|
+
constructor(store, stage, state, resources, version, existed) {
|
|
7909
|
+
this.store = store;
|
|
7910
|
+
this.stage = stage;
|
|
7911
|
+
this.state = state;
|
|
7912
|
+
this.resources = resources;
|
|
7913
|
+
this.version = version;
|
|
7914
|
+
this.existed = existed;
|
|
7915
|
+
}
|
|
7916
|
+
/**
|
|
7917
|
+
* The stage's journal — its stored state, or `initial` written as the
|
|
7918
|
+
* stage's first state, so there is somewhere to record the very first
|
|
7919
|
+
* resource before it is created.
|
|
7920
|
+
*/
|
|
7921
|
+
static async open(store, stage, initial) {
|
|
7922
|
+
const stored = await store.read(stage);
|
|
7923
|
+
if (stored) return new DeployJournal(store, stage, stored.state, stored.resources, stored.version, true);
|
|
7924
|
+
const state = initial();
|
|
7925
|
+
const version = await store.write(stage, state, { expectedVersion: null });
|
|
7926
|
+
return new DeployJournal(store, stage, state, {}, version, false);
|
|
7927
|
+
}
|
|
7928
|
+
record(key) {
|
|
7929
|
+
return this.resources[key];
|
|
7930
|
+
}
|
|
7931
|
+
/** The keys a previous run marked `pending` and never saw created. */
|
|
7932
|
+
unfinished() {
|
|
7933
|
+
return Object.values(this.resources).filter((record) => record.status === "pending").map((record) => record.key);
|
|
7934
|
+
}
|
|
7935
|
+
/** Records that `entry` is about to be created. */
|
|
7936
|
+
async pending(entry) {
|
|
7937
|
+
await this.put({
|
|
7938
|
+
...entry,
|
|
7939
|
+
status: "pending"
|
|
7940
|
+
});
|
|
7941
|
+
}
|
|
7942
|
+
/** Records `entry` as existing with `id`; a no-op if it already is. */
|
|
7943
|
+
async ready(entry, id) {
|
|
7944
|
+
const current = this.resources[entry.key];
|
|
7945
|
+
if (current?.status === "ready" && current.id === id) return;
|
|
7946
|
+
await this.put({
|
|
7947
|
+
...entry,
|
|
7948
|
+
status: "ready",
|
|
7949
|
+
id
|
|
7950
|
+
});
|
|
7951
|
+
}
|
|
7952
|
+
/**
|
|
7953
|
+
* The resource `entry` names: by its recorded id, else by looking it up,
|
|
7954
|
+
* else created — with `pending` written before the create and `ready`
|
|
7955
|
+
* after, so a run that dies in between leaves a record the next run
|
|
7956
|
+
* resolves by looking the resource up rather than creating another.
|
|
7957
|
+
*/
|
|
7958
|
+
async ensure(entry, steps) {
|
|
7959
|
+
const record = this.resources[entry.key];
|
|
7960
|
+
let staleId;
|
|
7961
|
+
if (record?.status === "ready" && record.id && steps.get) {
|
|
7962
|
+
const known = await steps.get(record.id);
|
|
7963
|
+
if (known) {
|
|
7964
|
+
await this.ready(entry, steps.id(known));
|
|
7965
|
+
return {
|
|
7966
|
+
resource: known,
|
|
7967
|
+
via: "recorded"
|
|
7968
|
+
};
|
|
7969
|
+
}
|
|
7970
|
+
staleId = record.id;
|
|
7971
|
+
}
|
|
7972
|
+
const existing = await steps.find();
|
|
7973
|
+
if (existing) {
|
|
7974
|
+
await this.ready(entry, steps.id(existing));
|
|
7975
|
+
return {
|
|
7976
|
+
resource: existing,
|
|
7977
|
+
via: record?.status === "pending" ? "resumed" : "found",
|
|
7978
|
+
...staleId ? { staleId } : {}
|
|
7979
|
+
};
|
|
7980
|
+
}
|
|
7981
|
+
await this.pending(entry);
|
|
7982
|
+
const created = await steps.create();
|
|
7983
|
+
await this.ready(entry, steps.id(created));
|
|
7984
|
+
return {
|
|
7985
|
+
resource: created,
|
|
7986
|
+
via: "created",
|
|
7987
|
+
...staleId ? { staleId } : {}
|
|
7988
|
+
};
|
|
7989
|
+
}
|
|
7990
|
+
/** Writes the stage's state, keeping its resource records. */
|
|
7991
|
+
async save() {
|
|
7992
|
+
this.state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7993
|
+
this.version = await this.store.write(this.stage, this.state, { expectedVersion: this.version });
|
|
7994
|
+
}
|
|
7995
|
+
async put(record) {
|
|
7996
|
+
this.version = await this.store.putResource(this.stage, record, { expectedVersion: this.version });
|
|
7997
|
+
this.resources = {
|
|
7998
|
+
...this.resources,
|
|
7999
|
+
[record.key]: {
|
|
8000
|
+
...record,
|
|
8001
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8002
|
+
}
|
|
8003
|
+
};
|
|
8004
|
+
}
|
|
8005
|
+
};
|
|
8006
|
+
//#endregion
|
|
7204
8007
|
//#region src/deploy/ownership.ts
|
|
7205
8008
|
/**
|
|
7206
8009
|
* Which Dokploy project a deploy is allowed to deploy into.
|
|
@@ -7256,7 +8059,7 @@ async function projectById(api, projectId) {
|
|
|
7256
8059
|
* A project that matches by name (in any case) but carries no marker, or
|
|
7257
8060
|
* another identity's, raises `ProjectNotOwned` — it is never adopted.
|
|
7258
8061
|
*/
|
|
7259
|
-
async function resolveProject(api, identity, stateProjectId, log = () => {}) {
|
|
8062
|
+
async function resolveProject(api, identity, stateProjectId, log = () => {}, hooks = {}) {
|
|
7260
8063
|
const name = projectName(identity);
|
|
7261
8064
|
const marker = ownershipMarker(identity);
|
|
7262
8065
|
if (stateProjectId) {
|
|
@@ -7292,6 +8095,7 @@ async function resolveProject(api, identity, stateProjectId, log = () => {}) {
|
|
|
7292
8095
|
}
|
|
7293
8096
|
const clash = projects.find((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
7294
8097
|
if (clash) throw new ProjectNotOwned(clash.name, clash.projectId, identity.key, markerOf(clash.description));
|
|
8098
|
+
await hooks.beforeCreate?.();
|
|
7295
8099
|
const created = await api.createProject(name, withMarker("Deployed by gkm.", marker));
|
|
7296
8100
|
return {
|
|
7297
8101
|
projectId: created.project.projectId,
|
|
@@ -7394,65 +8198,6 @@ async function resolveRegistry(api, options) {
|
|
|
7394
8198
|
return options.create(registry);
|
|
7395
8199
|
}
|
|
7396
8200
|
//#endregion
|
|
7397
|
-
//#region src/deploy/StateProvider.ts
|
|
7398
|
-
/**
|
|
7399
|
-
* Check if value is a StateProvider implementation.
|
|
7400
|
-
*/
|
|
7401
|
-
function isStateProvider(value) {
|
|
7402
|
-
return typeof value === "object" && value !== null && typeof value.read === "function" && typeof value.write === "function";
|
|
7403
|
-
}
|
|
7404
|
-
/**
|
|
7405
|
-
* Create a state provider based on configuration.
|
|
7406
|
-
*
|
|
7407
|
-
* - 'local': LocalStateProvider (default)
|
|
7408
|
-
* - 'ssm': CachedStateProvider with SSM as source of truth
|
|
7409
|
-
* - 's3': S3StateStore behind the provider interface
|
|
7410
|
-
* - Custom: Use provided StateProvider implementation
|
|
7411
|
-
*
|
|
7412
|
-
* Deploy moves to `createStateStore` (locks, versions, per-resource records);
|
|
7413
|
-
* this stays for the call sites that read and write whole states.
|
|
7414
|
-
*/
|
|
7415
|
-
async function createStateProvider(options) {
|
|
7416
|
-
const { config, workspaceRoot, workspaceName } = options;
|
|
7417
|
-
if (!config) {
|
|
7418
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-7HR6SS2t.cjs"));
|
|
7419
|
-
return new LocalStateProvider(workspaceRoot);
|
|
7420
|
-
}
|
|
7421
|
-
const { isStateStore } = await Promise.resolve().then(() => StateStore_exports);
|
|
7422
|
-
if (isStateStore(config.provider)) {
|
|
7423
|
-
const { StateStoreProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7424
|
-
return new StateStoreProvider(config.provider);
|
|
7425
|
-
}
|
|
7426
|
-
if (isStateProvider(config.provider)) return config.provider;
|
|
7427
|
-
const provider = config.provider;
|
|
7428
|
-
if (provider === "local") {
|
|
7429
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-7HR6SS2t.cjs"));
|
|
7430
|
-
return new LocalStateProvider(workspaceRoot);
|
|
7431
|
-
}
|
|
7432
|
-
if (provider === "ssm") {
|
|
7433
|
-
if (!workspaceName) {
|
|
7434
|
-
const { StateStoreNeedsWorkspaceName } = await Promise.resolve().then(() => StateStore_exports);
|
|
7435
|
-
throw new StateStoreNeedsWorkspaceName("ssm");
|
|
7436
|
-
}
|
|
7437
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-7HR6SS2t.cjs"));
|
|
7438
|
-
const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-VrQ-yeqd.cjs"));
|
|
7439
|
-
const { CachedStateProvider } = await Promise.resolve().then(() => CachedStateProvider_exports);
|
|
7440
|
-
const ssmConfig = config;
|
|
7441
|
-
const local = new LocalStateProvider(workspaceRoot);
|
|
7442
|
-
return new CachedStateProvider(SSMStateProvider.create({
|
|
7443
|
-
workspaceName,
|
|
7444
|
-
region: ssmConfig.region,
|
|
7445
|
-
profile: ssmConfig.profile
|
|
7446
|
-
}), local);
|
|
7447
|
-
}
|
|
7448
|
-
if (provider === "s3") {
|
|
7449
|
-
const { createStateStore, StateStoreProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7450
|
-
return new StateStoreProvider(await createStateStore(options));
|
|
7451
|
-
}
|
|
7452
|
-
const { UnknownStateProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7453
|
-
throw new UnknownStateProvider(config);
|
|
7454
|
-
}
|
|
7455
|
-
//#endregion
|
|
7456
8201
|
//#region src/deploy/secrets.ts
|
|
7457
8202
|
/**
|
|
7458
8203
|
* Filter secrets to only include the env vars that an app requires.
|
|
@@ -8193,6 +8938,28 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8193
8938
|
});
|
|
8194
8939
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
8195
8940
|
appsToDeployNames = dokployApps;
|
|
8941
|
+
const store = await require_StateStore.createStateStore({
|
|
8942
|
+
config: workspace.state,
|
|
8943
|
+
workspaceRoot: workspace.root,
|
|
8944
|
+
workspaceName: workspace.name
|
|
8945
|
+
});
|
|
8946
|
+
const lock = await store.lock(stage, { operation: "deploy" });
|
|
8947
|
+
try {
|
|
8948
|
+
return await deployLocked({
|
|
8949
|
+
workspace,
|
|
8950
|
+
manifest,
|
|
8951
|
+
stage,
|
|
8952
|
+
imageTag,
|
|
8953
|
+
appsToDeployNames,
|
|
8954
|
+
identity,
|
|
8955
|
+
store
|
|
8956
|
+
});
|
|
8957
|
+
} finally {
|
|
8958
|
+
await lock.release();
|
|
8959
|
+
}
|
|
8960
|
+
}
|
|
8961
|
+
/** Everything a deploy does while it holds the stage's lock. */
|
|
8962
|
+
async function deployLocked({ workspace, manifest, stage, imageTag, appsToDeployNames, identity, store }) {
|
|
8196
8963
|
logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
8197
8964
|
const secretsStore = await require_store.secretsStoreFor(workspace, stage);
|
|
8198
8965
|
const stored = await secretsStore.read(stage);
|
|
@@ -8234,42 +9001,48 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8234
9001
|
token: creds.token
|
|
8235
9002
|
});
|
|
8236
9003
|
logger$4.log("\n📋 Loading deploy state...");
|
|
8237
|
-
const
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
9004
|
+
const journal = await DeployJournal.open(store, stage, () => createEmptyState(stage, "", ""));
|
|
9005
|
+
const state = journal.state;
|
|
9006
|
+
if (journal.existed) logger$4.log(` Found existing state for stage "${stage}"`);
|
|
9007
|
+
else logger$4.log(` Creating new state for stage "${stage}"`);
|
|
9008
|
+
const unfinished = journal.unfinished();
|
|
9009
|
+
if (unfinished.length > 0) logger$4.log(` ⚠ A previous run stopped while creating: ${unfinished.join(", ")} — looking for them before creating anything`);
|
|
8243
9010
|
logger$4.log("\n📁 Setting up Dokploy project...");
|
|
8244
9011
|
logger$4.log(` Identity: ${identity.key}`);
|
|
8245
|
-
const
|
|
9012
|
+
const projectEntry = {
|
|
9013
|
+
key: "project",
|
|
9014
|
+
type: "project",
|
|
9015
|
+
data: {
|
|
9016
|
+
name: projectName(identity),
|
|
9017
|
+
identity: identity.key
|
|
9018
|
+
}
|
|
9019
|
+
};
|
|
9020
|
+
const project = await resolveProject(api, identity, state.projectId || void 0, (message) => logger$4.log(message), { beforeCreate: () => journal.pending(projectEntry) });
|
|
9021
|
+
await journal.ready(projectEntry, project.projectId);
|
|
8246
9022
|
if (project.via === "created") logger$4.log(` ✓ Created project: ${project.projectId}`);
|
|
8247
9023
|
else logger$4.log(` Found existing project: ${project.name}`);
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
if (state.
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
logger$4.log(` Creating new state for stage "${stage}"`);
|
|
8271
|
-
state = createEmptyState(stage, project.projectId, environmentId);
|
|
8272
|
-
}
|
|
9024
|
+
const environmentEntry = {
|
|
9025
|
+
key: "environment",
|
|
9026
|
+
type: "environment",
|
|
9027
|
+
data: { name: stage }
|
|
9028
|
+
};
|
|
9029
|
+
const environment = await journal.ensure(environmentEntry, {
|
|
9030
|
+
find: async () => project.environments.find((e) => e.name.toLowerCase() === stage.toLowerCase()),
|
|
9031
|
+
create: () => {
|
|
9032
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
9033
|
+
return api.createEnvironment(project.projectId, stage);
|
|
9034
|
+
},
|
|
9035
|
+
id: (e) => e.environmentId
|
|
9036
|
+
});
|
|
9037
|
+
const environmentId = environment.resource.environmentId;
|
|
9038
|
+
if (environment.via === "created") logger$4.log(` ✓ Created environment: ${stage}`);
|
|
9039
|
+
else logger$4.log(` Using environment: ${environment.resource.name}`);
|
|
9040
|
+
if (journal.existed) {
|
|
9041
|
+
if (state.projectId && state.projectId !== project.projectId) logger$4.log(` ⚠ Project ID changed, updating state`);
|
|
9042
|
+
if (state.environmentId && state.environmentId !== environmentId) logger$4.log(` ⚠ Environment ID changed, updating state`);
|
|
9043
|
+
}
|
|
9044
|
+
state.projectId = project.projectId;
|
|
9045
|
+
state.environmentId = environmentId;
|
|
8273
9046
|
state.identity = identity.key;
|
|
8274
9047
|
logger$4.log("\n🐳 Checking registry...");
|
|
8275
9048
|
const registry = workspace.deploy.dokploy?.registry;
|
|
@@ -8291,6 +9064,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8291
9064
|
const registryId = dokployRegistry.registryId;
|
|
8292
9065
|
state.registryId = registryId;
|
|
8293
9066
|
logger$4.log(` Using registry: ${dokployRegistry.registryName}`);
|
|
9067
|
+
await journal.save();
|
|
8294
9068
|
const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
|
|
8295
9069
|
if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
8296
9070
|
const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
|
|
@@ -8348,7 +9122,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8348
9122
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
8349
9123
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
8350
9124
|
logger$4.log(" Creating postgres backup schedule...");
|
|
8351
|
-
|
|
9125
|
+
setPostgresBackupId(state, (await api.createPostgresBackup({
|
|
8352
9126
|
schedule: backupSchedule,
|
|
8353
9127
|
prefix: `${stage}/postgres`,
|
|
8354
9128
|
destinationId: backupState.destinationId,
|
|
@@ -8356,8 +9130,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8356
9130
|
postgresId: backupCluster.postgresId,
|
|
8357
9131
|
enabled: true,
|
|
8358
9132
|
keepLatestCount: backupRetention
|
|
8359
|
-
});
|
|
8360
|
-
setPostgresBackupId(state, backup.backupId);
|
|
9133
|
+
})).backupId);
|
|
8361
9134
|
logger$4.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
8362
9135
|
} else logger$4.log(" ✓ Using existing postgres backup schedule");
|
|
8363
9136
|
}
|
|
@@ -8378,22 +9151,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8378
9151
|
const app = workspace.apps[appName];
|
|
8379
9152
|
logger$4.log(`\n ⚙️ Deploying ${appName}...`);
|
|
8380
9153
|
try {
|
|
8381
|
-
const
|
|
8382
|
-
let application = null;
|
|
8383
|
-
const cachedAppId = getApplicationId(state, appName);
|
|
8384
|
-
if (cachedAppId) {
|
|
8385
|
-
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
8386
|
-
application = await api.getApplication(cachedAppId);
|
|
8387
|
-
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
8388
|
-
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
8389
|
-
}
|
|
8390
|
-
if (!application) {
|
|
8391
|
-
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
8392
|
-
application = result.application;
|
|
8393
|
-
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
8394
|
-
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
8395
|
-
}
|
|
8396
|
-
setApplicationId(state, appName, application.applicationId);
|
|
9154
|
+
const application = await ensureApplication(api, journal, appName, applicationName(identity, appName), project.projectId, environmentId);
|
|
8397
9155
|
const appSecrets = encryptedSecrets.get(appName);
|
|
8398
9156
|
const credentials = appSecrets && appSecrets.secretCount > 0 ? {
|
|
8399
9157
|
encrypted: appSecrets.payload.encrypted,
|
|
@@ -8417,6 +9175,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8417
9175
|
ref: built.imageRef ?? imageRef$1,
|
|
8418
9176
|
...built.digest ? { digest: built.digest } : {}
|
|
8419
9177
|
});
|
|
9178
|
+
await journal.save();
|
|
8420
9179
|
const backendHost = resolveHost(appName, app, stage, workspace.deploy?.domains, false);
|
|
8421
9180
|
const dependencyUrls = {};
|
|
8422
9181
|
if (app.dependencies) {
|
|
@@ -8451,30 +9210,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8451
9210
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
8452
9211
|
logger$4.log(` Deploying to Dokploy...`);
|
|
8453
9212
|
await api.deployApplication(application.applicationId);
|
|
8454
|
-
const
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
publicUrls[appName] = `https://${backendHost}`;
|
|
8459
|
-
logger$4.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
8460
|
-
} else try {
|
|
8461
|
-
const domain = await api.createDomain({
|
|
8462
|
-
host: backendHost,
|
|
8463
|
-
port: app.port,
|
|
8464
|
-
https: true,
|
|
8465
|
-
certificateType: "letsencrypt",
|
|
8466
|
-
applicationId: application.applicationId
|
|
8467
|
-
});
|
|
8468
|
-
appHostnames.set(appName, backendHost);
|
|
8469
|
-
appDomainIds.set(appName, domain.domainId);
|
|
8470
|
-
publicUrls[appName] = `https://${backendHost}`;
|
|
8471
|
-
logger$4.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
8472
|
-
} catch (domainError) {
|
|
8473
|
-
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
8474
|
-
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
8475
|
-
appHostnames.set(appName, backendHost);
|
|
8476
|
-
publicUrls[appName] = `https://${backendHost}`;
|
|
8477
|
-
}
|
|
9213
|
+
const domainId = await ensureDomain(api, journal, backendHost, app.port, application.applicationId);
|
|
9214
|
+
appHostnames.set(appName, backendHost);
|
|
9215
|
+
if (domainId) appDomainIds.set(appName, domainId);
|
|
9216
|
+
publicUrls[appName] = `https://${backendHost}`;
|
|
8478
9217
|
results.push({
|
|
8479
9218
|
appName,
|
|
8480
9219
|
type: app.type,
|
|
@@ -8503,22 +9242,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8503
9242
|
const app = workspace.apps[appName];
|
|
8504
9243
|
logger$4.log(`\n 🌐 Deploying ${appName}...`);
|
|
8505
9244
|
try {
|
|
8506
|
-
const
|
|
8507
|
-
let application = null;
|
|
8508
|
-
const cachedAppId = getApplicationId(state, appName);
|
|
8509
|
-
if (cachedAppId) {
|
|
8510
|
-
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
8511
|
-
application = await api.getApplication(cachedAppId);
|
|
8512
|
-
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
8513
|
-
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
8514
|
-
}
|
|
8515
|
-
if (!application) {
|
|
8516
|
-
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
8517
|
-
application = result.application;
|
|
8518
|
-
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
8519
|
-
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
8520
|
-
}
|
|
8521
|
-
setApplicationId(state, appName, application.applicationId);
|
|
9245
|
+
const application = await ensureApplication(api, journal, appName, applicationName(identity, appName), project.projectId, environmentId);
|
|
8522
9246
|
const dependencyUrls = {};
|
|
8523
9247
|
if (app.dependencies) {
|
|
8524
9248
|
for (const dep of app.dependencies) if (publicUrls[dep]) dependencyUrls[dep] = publicUrls[dep];
|
|
@@ -8566,6 +9290,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8566
9290
|
ref: built.imageRef ?? imageRef$2,
|
|
8567
9291
|
...built.digest ? { digest: built.digest } : {}
|
|
8568
9292
|
});
|
|
9293
|
+
await journal.save();
|
|
8569
9294
|
const envVars = [
|
|
8570
9295
|
`NODE_ENV=production`,
|
|
8571
9296
|
`PORT=${app.port}`,
|
|
@@ -8576,30 +9301,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8576
9301
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
8577
9302
|
logger$4.log(` Deploying to Dokploy...`);
|
|
8578
9303
|
await api.deployApplication(application.applicationId);
|
|
8579
|
-
const
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
publicUrls[appName] = `https://${frontendHost}`;
|
|
8584
|
-
logger$4.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
8585
|
-
} else try {
|
|
8586
|
-
const domain = await api.createDomain({
|
|
8587
|
-
host: frontendHost,
|
|
8588
|
-
port: app.port,
|
|
8589
|
-
https: true,
|
|
8590
|
-
certificateType: "letsencrypt",
|
|
8591
|
-
applicationId: application.applicationId
|
|
8592
|
-
});
|
|
8593
|
-
appHostnames.set(appName, frontendHost);
|
|
8594
|
-
appDomainIds.set(appName, domain.domainId);
|
|
8595
|
-
publicUrls[appName] = `https://${frontendHost}`;
|
|
8596
|
-
logger$4.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
8597
|
-
} catch (domainError) {
|
|
8598
|
-
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
8599
|
-
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
8600
|
-
appHostnames.set(appName, frontendHost);
|
|
8601
|
-
publicUrls[appName] = `https://${frontendHost}`;
|
|
8602
|
-
}
|
|
9304
|
+
const domainId = await ensureDomain(api, journal, frontendHost, app.port, application.applicationId);
|
|
9305
|
+
appHostnames.set(appName, frontendHost);
|
|
9306
|
+
if (domainId) appDomainIds.set(appName, domainId);
|
|
9307
|
+
publicUrls[appName] = `https://${frontendHost}`;
|
|
8603
9308
|
results.push({
|
|
8604
9309
|
appName,
|
|
8605
9310
|
type: app.type,
|
|
@@ -8622,14 +9327,14 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8622
9327
|
}
|
|
8623
9328
|
}
|
|
8624
9329
|
logger$4.log("\n📋 Saving deploy state...");
|
|
8625
|
-
await
|
|
9330
|
+
await journal.save();
|
|
8626
9331
|
logger$4.log(" ✓ State saved");
|
|
8627
9332
|
const dnsConfig = workspace.deploy.dns;
|
|
8628
9333
|
if (dnsConfig && appHostnames.size > 0) {
|
|
8629
9334
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
8630
9335
|
if (dnsResult?.serverIp && appHostnames.size > 0) {
|
|
8631
9336
|
await verifyDnsRecords(appHostnames, dnsResult.serverIp, state);
|
|
8632
|
-
await
|
|
9337
|
+
await journal.save();
|
|
8633
9338
|
}
|
|
8634
9339
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
8635
9340
|
logger$4.log("\n🔒 Validating domains for SSL certificates...");
|
|
@@ -8662,6 +9367,71 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8662
9367
|
};
|
|
8663
9368
|
}
|
|
8664
9369
|
/**
|
|
9370
|
+
* An app's Dokploy application, through the journal: by the id recorded for
|
|
9371
|
+
* it, else by its name in the stage's environment, else created. Its id goes
|
|
9372
|
+
* into `state.applications` too, which the env resolver and `state:show` read.
|
|
9373
|
+
*/
|
|
9374
|
+
async function ensureApplication(api, journal, appName, dokployAppName, projectId, environmentId) {
|
|
9375
|
+
const recorded = journal.record(`application:${appName}`);
|
|
9376
|
+
if (recorded?.status === "ready" && recorded.id) logger$4.log(` Using cached ID: ${recorded.id}`);
|
|
9377
|
+
const { resource, via, staleId } = await journal.ensure({
|
|
9378
|
+
key: `application:${appName}`,
|
|
9379
|
+
type: "application",
|
|
9380
|
+
data: { name: dokployAppName }
|
|
9381
|
+
}, {
|
|
9382
|
+
get: (id) => api.getApplication(id),
|
|
9383
|
+
find: () => api.findApplicationByName(projectId, dokployAppName, environmentId),
|
|
9384
|
+
create: () => api.createApplication(dokployAppName, projectId, environmentId),
|
|
9385
|
+
id: (application) => application.applicationId
|
|
9386
|
+
});
|
|
9387
|
+
if (staleId) logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
9388
|
+
logger$4.log(` ${{
|
|
9389
|
+
recorded: "✓ Application found",
|
|
9390
|
+
resumed: "Resumed application a stopped run created",
|
|
9391
|
+
found: "Found existing application",
|
|
9392
|
+
created: "Created application"
|
|
9393
|
+
}[via]}: ${resource.applicationId}`);
|
|
9394
|
+
setApplicationId(journal.state, appName, resource.applicationId);
|
|
9395
|
+
return resource;
|
|
9396
|
+
}
|
|
9397
|
+
/**
|
|
9398
|
+
* The domain `host` routes to an application, through the journal — found on
|
|
9399
|
+
* the application before it is created, so a re-run never adds a second.
|
|
9400
|
+
*
|
|
9401
|
+
* A domain that cannot be created is reported and skipped rather than
|
|
9402
|
+
* failing the app: the application is deployed and reachable on Dokploy's
|
|
9403
|
+
* own address, and DNS is fixed by hand more easily than a half deploy.
|
|
9404
|
+
*/
|
|
9405
|
+
async function ensureDomain(api, journal, host, port, applicationId) {
|
|
9406
|
+
try {
|
|
9407
|
+
const { resource, via } = await journal.ensure({
|
|
9408
|
+
key: `domain:${host}`,
|
|
9409
|
+
type: "domain",
|
|
9410
|
+
data: {
|
|
9411
|
+
host,
|
|
9412
|
+
applicationId
|
|
9413
|
+
}
|
|
9414
|
+
}, {
|
|
9415
|
+
find: async () => (await api.getDomainsByApplicationId(applicationId)).find((d) => d.host === host),
|
|
9416
|
+
create: () => api.createDomain({
|
|
9417
|
+
host,
|
|
9418
|
+
port,
|
|
9419
|
+
https: true,
|
|
9420
|
+
certificateType: "letsencrypt",
|
|
9421
|
+
applicationId
|
|
9422
|
+
}),
|
|
9423
|
+
id: (domain) => domain.domainId
|
|
9424
|
+
});
|
|
9425
|
+
logger$4.log(` ✓ Domain: https://${host} (${via === "created" ? "created" : "existing"})`);
|
|
9426
|
+
return resource.domainId;
|
|
9427
|
+
} catch (domainError) {
|
|
9428
|
+
if (domainError instanceof require_StateStore.StateVersionConflict || domainError instanceof require_StateStore.StateStoreBusy) throw domainError;
|
|
9429
|
+
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
9430
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
9431
|
+
return;
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9434
|
+
/**
|
|
8665
9435
|
* Main deploy command
|
|
8666
9436
|
*/
|
|
8667
9437
|
async function deployCommand(options) {
|
|
@@ -8798,453 +9568,160 @@ async function deployListCommand(options) {
|
|
|
8798
9568
|
}
|
|
8799
9569
|
}
|
|
8800
9570
|
//#endregion
|
|
8801
|
-
//#region src/deploy/
|
|
8802
|
-
var CachedStateProvider_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ CachedStateProvider: () => CachedStateProvider });
|
|
8803
|
-
/**
|
|
8804
|
-
* Cached state provider that wraps a remote provider with local cache.
|
|
8805
|
-
*
|
|
8806
|
-
* @deprecated for deploys: a read prefers the local copy, so a stale one wins
|
|
8807
|
-
* over the remote without warning, and a write overwrites the remote blindly.
|
|
8808
|
-
* Deploy uses `createStateStore`, which reads SSM directly and writes
|
|
8809
|
-
* conditionally. Kept for `gkm state:pull | push | diff`.
|
|
8810
|
-
*/
|
|
8811
|
-
var CachedStateProvider = class {
|
|
8812
|
-
remote;
|
|
8813
|
-
local;
|
|
8814
|
-
constructor(remote, local) {
|
|
8815
|
-
this.remote = remote;
|
|
8816
|
-
this.local = local;
|
|
8817
|
-
}
|
|
8818
|
-
async read(stage) {
|
|
8819
|
-
const localState = await this.local.read(stage);
|
|
8820
|
-
if (localState) return localState;
|
|
8821
|
-
const remoteState = await this.remote.read(stage);
|
|
8822
|
-
if (remoteState) await this.local.write(stage, remoteState);
|
|
8823
|
-
return remoteState;
|
|
8824
|
-
}
|
|
8825
|
-
async write(stage, state) {
|
|
8826
|
-
await this.remote.write(stage, state);
|
|
8827
|
-
await this.local.write(stage, state);
|
|
8828
|
-
}
|
|
8829
|
-
/**
|
|
8830
|
-
* Force pull from remote to local.
|
|
8831
|
-
* Used by `gkm state pull` command.
|
|
8832
|
-
*/
|
|
8833
|
-
async pull(stage) {
|
|
8834
|
-
const remoteState = await this.remote.read(stage);
|
|
8835
|
-
if (remoteState) await this.local.write(stage, remoteState);
|
|
8836
|
-
return remoteState;
|
|
8837
|
-
}
|
|
8838
|
-
/**
|
|
8839
|
-
* Force push from local to remote.
|
|
8840
|
-
* Used by `gkm state push` command.
|
|
8841
|
-
*/
|
|
8842
|
-
async push(stage) {
|
|
8843
|
-
const localState = await this.local.read(stage);
|
|
8844
|
-
if (localState) await this.remote.write(stage, localState);
|
|
8845
|
-
return localState;
|
|
8846
|
-
}
|
|
8847
|
-
/**
|
|
8848
|
-
* Get both local and remote state for comparison.
|
|
8849
|
-
* Used by `gkm state diff` command.
|
|
8850
|
-
*/
|
|
8851
|
-
async diff(stage) {
|
|
8852
|
-
const [local, remote] = await Promise.all([this.local.read(stage), this.remote.read(stage)]);
|
|
8853
|
-
return {
|
|
8854
|
-
local,
|
|
8855
|
-
remote
|
|
8856
|
-
};
|
|
8857
|
-
}
|
|
8858
|
-
};
|
|
8859
|
-
//#endregion
|
|
8860
|
-
//#region src/deploy/StateStore.ts
|
|
9571
|
+
//#region src/deploy/LocalStateStore.ts
|
|
8861
9572
|
/**
|
|
8862
|
-
*
|
|
9573
|
+
* Local filesystem state store.
|
|
8863
9574
|
*
|
|
8864
|
-
* `
|
|
8865
|
-
*
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
* - Versioned `read` and conditional `write`: a write names the version it was
|
|
8872
|
-
* based on, and raises `StateVersionConflict` when the stored state moved on
|
|
8873
|
-
* underneath it, so nothing is lost silently even without the lock.
|
|
8874
|
-
* - Per-resource records: `putResource` journals one resource at a time, so a
|
|
8875
|
-
* run that dies half way leaves behind the ids of everything it created.
|
|
8876
|
-
*
|
|
8877
|
-
* Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A
|
|
8878
|
-
* custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it
|
|
8879
|
-
* working but cannot lock.
|
|
8880
|
-
*/
|
|
8881
|
-
var StateStore_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
8882
|
-
DocumentStateStore: () => DocumentStateStore,
|
|
8883
|
-
STATE_SCHEMA_VERSION: () => 2,
|
|
8884
|
-
StageStateMissing: () => StageStateMissing,
|
|
8885
|
-
StateLocked: () => StateLocked,
|
|
8886
|
-
StateSchemaTooNew: () => StateSchemaTooNew,
|
|
8887
|
-
StateStoreBusy: () => StateStoreBusy,
|
|
8888
|
-
StateStoreNeedsWorkspaceName: () => StateStoreNeedsWorkspaceName,
|
|
8889
|
-
StateStoreProvider: () => StateStoreProvider,
|
|
8890
|
-
StateStoreWithoutLocking: () => StateStoreWithoutLocking,
|
|
8891
|
-
StateUnreadable: () => StateUnreadable,
|
|
8892
|
-
StateVersionConflict: () => StateVersionConflict,
|
|
8893
|
-
UnknownStateProvider: () => UnknownStateProvider,
|
|
8894
|
-
contentVersion: () => contentVersion,
|
|
8895
|
-
createStateStore: () => createStateStore,
|
|
8896
|
-
decodeDocument: () => decodeDocument,
|
|
8897
|
-
encodeDocument: () => encodeDocument,
|
|
8898
|
-
isStateStore: () => isStateStore,
|
|
8899
|
-
migrateV1: () => migrateV1,
|
|
8900
|
-
newLockHolder: () => newLockHolder
|
|
8901
|
-
});
|
|
8902
|
-
var StateLocked = class extends Error {
|
|
8903
|
-
stage;
|
|
8904
|
-
holder;
|
|
8905
|
-
location;
|
|
8906
|
-
constructor(stage, holder, location) {
|
|
8907
|
-
const by = holder ? `${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}${holder.operation ? ` for '${holder.operation}'` : ""}` : "a run that did not record who it was";
|
|
8908
|
-
super(`Deploy state for stage '${stage}' is locked by ${by} (${location}). Wait for that run to finish; if it crashed, release the lock with \`gkm state:unlock --stage ${stage}\`.`);
|
|
8909
|
-
this.stage = stage;
|
|
8910
|
-
this.holder = holder;
|
|
8911
|
-
this.location = location;
|
|
8912
|
-
this.name = "StateLocked";
|
|
8913
|
-
}
|
|
8914
|
-
};
|
|
8915
|
-
var StateVersionConflict = class extends Error {
|
|
8916
|
-
stage;
|
|
8917
|
-
expectedVersion;
|
|
8918
|
-
actualVersion;
|
|
8919
|
-
constructor(stage, expectedVersion, actualVersion) {
|
|
8920
|
-
super(`Deploy state for stage '${stage}' changed since it was read (expected ${expectedVersion ? `version ${expectedVersion}` : "no state"}, found ${actualVersion ? `version ${actualVersion}` : "no state"}). Another run wrote it: read the state again and re-apply the change, and hold the stage's lock so runs take turns.`);
|
|
8921
|
-
this.stage = stage;
|
|
8922
|
-
this.expectedVersion = expectedVersion;
|
|
8923
|
-
this.actualVersion = actualVersion;
|
|
8924
|
-
this.name = "StateVersionConflict";
|
|
8925
|
-
}
|
|
8926
|
-
};
|
|
8927
|
-
var StageStateMissing = class extends Error {
|
|
8928
|
-
stage;
|
|
8929
|
-
constructor(stage) {
|
|
8930
|
-
super(`There is no deploy state for stage '${stage}' to record a resource in. Write the stage's state first (write with expectedVersion: null).`);
|
|
8931
|
-
this.stage = stage;
|
|
8932
|
-
this.name = "StageStateMissing";
|
|
8933
|
-
}
|
|
8934
|
-
};
|
|
8935
|
-
var StateUnreadable = class extends Error {
|
|
8936
|
-
stage;
|
|
8937
|
-
location;
|
|
8938
|
-
constructor(stage, location, cause) {
|
|
8939
|
-
super(`Deploy state for stage '${stage}' at ${location} is not valid JSON. Restore it from a backup (or the provider's history) rather than deploying without it — a deploy without state recreates every resource.`, { cause });
|
|
8940
|
-
this.stage = stage;
|
|
8941
|
-
this.location = location;
|
|
8942
|
-
this.name = "StateUnreadable";
|
|
8943
|
-
}
|
|
8944
|
-
};
|
|
8945
|
-
var StateSchemaTooNew = class extends Error {
|
|
8946
|
-
stage;
|
|
8947
|
-
schemaVersion;
|
|
8948
|
-
constructor(stage, schemaVersion) {
|
|
8949
|
-
super(`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, newer than this @geekmidas/cli understands (2). Upgrade @geekmidas/cli to the version that wrote it.`);
|
|
8950
|
-
this.stage = stage;
|
|
8951
|
-
this.schemaVersion = schemaVersion;
|
|
8952
|
-
this.name = "StateSchemaTooNew";
|
|
8953
|
-
}
|
|
8954
|
-
};
|
|
9575
|
+
* `.gkm/deploy-<stage>.json` holds the state, `.gkm/deploy-<stage>.lock` the
|
|
9576
|
+
* lock. Every file is mode 0600: state holds database passwords and IAM keys.
|
|
9577
|
+
*/
|
|
9578
|
+
var LocalStateStore_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ LocalStateStore: () => LocalStateStore });
|
|
9579
|
+
const FILE_MODE = 384;
|
|
9580
|
+
/** How long a write waits for another process's write to finish. */
|
|
9581
|
+
const MUTEX_TIMEOUT_MS = 5e3;
|
|
8955
9582
|
/**
|
|
8956
|
-
*
|
|
8957
|
-
*
|
|
8958
|
-
* says so.
|
|
9583
|
+
* A write holds the mutex for milliseconds, so one this old was left by a
|
|
9584
|
+
* process that died mid-write and is safe to take over.
|
|
8959
9585
|
*/
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
9586
|
+
const MUTEX_STALE_MS = 3e4;
|
|
9587
|
+
function isCode(error, code) {
|
|
9588
|
+
return error?.code === code;
|
|
9589
|
+
}
|
|
9590
|
+
var LocalStateStore = class extends require_StateStore.DocumentStateStore {
|
|
9591
|
+
workspaceRoot;
|
|
9592
|
+
constructor(workspaceRoot) {
|
|
9593
|
+
super();
|
|
9594
|
+
this.workspaceRoot = workspaceRoot;
|
|
8966
9595
|
}
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
provider;
|
|
8970
|
-
constructor(provider) {
|
|
8971
|
-
super(`The '${provider}' state provider keys state by workspace name. Set "name" in gkm.config.ts.`);
|
|
8972
|
-
this.provider = provider;
|
|
8973
|
-
this.name = "StateStoreNeedsWorkspaceName";
|
|
9596
|
+
get dir() {
|
|
9597
|
+
return (0, node_path.join)(this.workspaceRoot, ".gkm");
|
|
8974
9598
|
}
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
config;
|
|
8978
|
-
constructor(config) {
|
|
8979
|
-
super(`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', 's3', or an object implementing StateStore or StateProvider.`);
|
|
8980
|
-
this.config = config;
|
|
8981
|
-
this.name = "UnknownStateProvider";
|
|
9599
|
+
statePath(stage) {
|
|
9600
|
+
return (0, node_path.join)(this.dir, `deploy-${stage}.json`);
|
|
8982
9601
|
}
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
var StateStoreBusy = class extends Error {
|
|
8986
|
-
stage;
|
|
8987
|
-
location;
|
|
8988
|
-
constructor(stage, location) {
|
|
8989
|
-
super(`Timed out waiting to write deploy state for stage '${stage}' (${location}). If no other gkm process is running, remove ${location}.`);
|
|
8990
|
-
this.stage = stage;
|
|
8991
|
-
this.location = location;
|
|
8992
|
-
this.name = "StateStoreBusy";
|
|
9602
|
+
lockPath(stage) {
|
|
9603
|
+
return (0, node_path.join)(this.dir, `deploy-${stage}.lock`);
|
|
8993
9604
|
}
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
let parsed;
|
|
8997
|
-
try {
|
|
8998
|
-
parsed = JSON.parse(body);
|
|
8999
|
-
} catch (error) {
|
|
9000
|
-
throw new StateUnreadable(stage, location, error);
|
|
9605
|
+
backupPath(stage) {
|
|
9606
|
+
return (0, node_path.join)(this.dir, `deploy-${stage}.v1.json`);
|
|
9001
9607
|
}
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
if (schemaVersion === void 0) return {
|
|
9005
|
-
schemaVersion: 1,
|
|
9006
|
-
state: parsed
|
|
9007
|
-
};
|
|
9008
|
-
if (schemaVersion === 2) return {
|
|
9009
|
-
schemaVersion: 2,
|
|
9010
|
-
document: parsed
|
|
9011
|
-
};
|
|
9012
|
-
throw new StateSchemaTooNew(stage, schemaVersion);
|
|
9013
|
-
}
|
|
9014
|
-
function encodeDocument(document) {
|
|
9015
|
-
return JSON.stringify(document, null, 2);
|
|
9016
|
-
}
|
|
9017
|
-
/**
|
|
9018
|
-
* v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,
|
|
9019
|
-
* so the first journaled deploy after an upgrade adopts them rather than
|
|
9020
|
-
* treating them as unknown.
|
|
9021
|
-
*/
|
|
9022
|
-
function migrateV1(stage, state) {
|
|
9023
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9024
|
-
const resources = {};
|
|
9025
|
-
const seed = (type, name, id) => {
|
|
9026
|
-
if (!id) return;
|
|
9027
|
-
const key = name ? `${type}:${name}` : type;
|
|
9028
|
-
resources[key] = {
|
|
9029
|
-
key,
|
|
9030
|
-
type,
|
|
9031
|
-
id,
|
|
9032
|
-
status: "ready",
|
|
9033
|
-
updatedAt: now
|
|
9034
|
-
};
|
|
9035
|
-
};
|
|
9036
|
-
seed("project", null, state.projectId);
|
|
9037
|
-
seed("environment", null, state.environmentId);
|
|
9038
|
-
for (const [app, id] of Object.entries(state.applications ?? {})) seed("application", app, id);
|
|
9039
|
-
seed("postgres", null, state.services?.postgresId);
|
|
9040
|
-
seed("redis", null, state.services?.redisId);
|
|
9041
|
-
seed("backup-destination", null, state.backups?.destinationId);
|
|
9042
|
-
return {
|
|
9043
|
-
schemaVersion: 2,
|
|
9044
|
-
stage,
|
|
9045
|
-
serial: 1,
|
|
9046
|
-
state,
|
|
9047
|
-
resources,
|
|
9048
|
-
updatedAt: now
|
|
9049
|
-
};
|
|
9050
|
-
}
|
|
9051
|
-
function newLockHolder(options) {
|
|
9052
|
-
let owner = "unknown";
|
|
9053
|
-
try {
|
|
9054
|
-
owner = (0, node_os.userInfo)().username;
|
|
9055
|
-
} catch {}
|
|
9056
|
-
return {
|
|
9057
|
-
id: (0, node_crypto.randomUUID)(),
|
|
9058
|
-
owner,
|
|
9059
|
-
host: (0, node_os.hostname)(),
|
|
9060
|
-
pid: process.pid,
|
|
9061
|
-
acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9062
|
-
operation: options?.operation
|
|
9063
|
-
};
|
|
9064
|
-
}
|
|
9065
|
-
function contentVersion(body) {
|
|
9066
|
-
return (0, node_crypto.createHash)("sha256").update(body).digest("hex").slice(0, 16);
|
|
9067
|
-
}
|
|
9068
|
-
/**
|
|
9069
|
-
* A store over a backend that can read and conditionally write one blob per
|
|
9070
|
-
* stage, and create a lock object only if it does not exist. Migration and
|
|
9071
|
-
* resource records are the same for every such backend, so they live here.
|
|
9072
|
-
*/
|
|
9073
|
-
var DocumentStateStore = class {
|
|
9074
|
-
async lock(stage, options) {
|
|
9075
|
-
const holder = newLockHolder(options);
|
|
9076
|
-
await this.createLock(stage, holder);
|
|
9077
|
-
let released = false;
|
|
9078
|
-
return {
|
|
9079
|
-
stage,
|
|
9080
|
-
holder,
|
|
9081
|
-
release: async () => {
|
|
9082
|
-
if (released) return;
|
|
9083
|
-
released = true;
|
|
9084
|
-
await this.removeLock(stage, holder);
|
|
9085
|
-
}
|
|
9086
|
-
};
|
|
9087
|
-
}
|
|
9088
|
-
async forceUnlock(stage) {
|
|
9089
|
-
const holder = await this.readLock(stage);
|
|
9090
|
-
await this.removeLock(stage, null);
|
|
9091
|
-
return holder;
|
|
9608
|
+
mutexPath(stage) {
|
|
9609
|
+
return (0, node_path.join)(this.dir, `deploy-${stage}.json.mutex`);
|
|
9092
9610
|
}
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
if (!current) return null;
|
|
9096
|
-
return {
|
|
9097
|
-
state: current.document.state,
|
|
9098
|
-
resources: current.document.resources,
|
|
9099
|
-
version: current.version
|
|
9100
|
-
};
|
|
9611
|
+
location(stage) {
|
|
9612
|
+
return this.statePath(stage);
|
|
9101
9613
|
}
|
|
9102
|
-
async
|
|
9103
|
-
|
|
9104
|
-
const
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
state,
|
|
9109
|
-
resources: {},
|
|
9110
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9614
|
+
async readRaw(stage) {
|
|
9615
|
+
try {
|
|
9616
|
+
const body = await (0, node_fs_promises.readFile)(this.statePath(stage), "utf-8");
|
|
9617
|
+
return {
|
|
9618
|
+
body,
|
|
9619
|
+
version: require_StateStore.contentVersion(body)
|
|
9111
9620
|
};
|
|
9112
|
-
|
|
9621
|
+
} catch (error) {
|
|
9622
|
+
if (isCode(error, "ENOENT")) return null;
|
|
9623
|
+
throw error;
|
|
9113
9624
|
}
|
|
9114
|
-
return this.mutate(stage, options.expectedVersion, (document) => ({
|
|
9115
|
-
...document,
|
|
9116
|
-
state
|
|
9117
|
-
}));
|
|
9118
9625
|
}
|
|
9119
|
-
async
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9626
|
+
async writeRaw(stage, body, expectedVersion) {
|
|
9627
|
+
await this.ensureDir();
|
|
9628
|
+
return this.withMutex(stage, async () => {
|
|
9629
|
+
const actual = (await this.readRaw(stage))?.version ?? null;
|
|
9630
|
+
if (actual !== expectedVersion) throw new require_StateStore.StateVersionConflict(stage, expectedVersion, actual);
|
|
9631
|
+
const target = this.statePath(stage);
|
|
9632
|
+
const temp = `${target}.${(0, node_crypto.randomUUID)()}.tmp`;
|
|
9633
|
+
try {
|
|
9634
|
+
const handle = await (0, node_fs_promises.open)(temp, "wx", FILE_MODE);
|
|
9635
|
+
try {
|
|
9636
|
+
await handle.writeFile(body);
|
|
9637
|
+
await handle.sync();
|
|
9638
|
+
} finally {
|
|
9639
|
+
await handle.close();
|
|
9127
9640
|
}
|
|
9641
|
+
await (0, node_fs_promises.rename)(temp, target);
|
|
9642
|
+
} catch (error) {
|
|
9643
|
+
await (0, node_fs_promises.rm)(temp, { force: true });
|
|
9644
|
+
throw error;
|
|
9128
9645
|
}
|
|
9129
|
-
|
|
9130
|
-
}
|
|
9131
|
-
async deleteResource(stage, key, options) {
|
|
9132
|
-
return this.mutate(stage, options?.expectedVersion, (document) => {
|
|
9133
|
-
const { [key]: _removed, ...resources } = document.resources;
|
|
9134
|
-
return {
|
|
9135
|
-
...document,
|
|
9136
|
-
resources
|
|
9137
|
-
};
|
|
9646
|
+
return require_StateStore.contentVersion(body);
|
|
9138
9647
|
});
|
|
9139
9648
|
}
|
|
9140
|
-
async
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9649
|
+
async writeV1Backup(stage, body) {
|
|
9650
|
+
try {
|
|
9651
|
+
await (0, node_fs_promises.writeFile)(this.backupPath(stage), body, {
|
|
9652
|
+
flag: "wx",
|
|
9653
|
+
mode: FILE_MODE
|
|
9654
|
+
});
|
|
9655
|
+
} catch (error) {
|
|
9656
|
+
if (!isCode(error, "EEXIST")) throw error;
|
|
9145
9657
|
}
|
|
9146
|
-
if (expectedVersion !== void 0 && current.version !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, current.version);
|
|
9147
|
-
const next = change(current.document);
|
|
9148
|
-
next.serial = current.document.serial + 1;
|
|
9149
|
-
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9150
|
-
return this.writeRaw(stage, encodeDocument(next), current.version);
|
|
9151
9658
|
}
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
* wrote) first, its result is read instead.
|
|
9156
|
-
*/
|
|
9157
|
-
async readDocument(stage, attempt = 0) {
|
|
9158
|
-
const raw = await this.readRaw(stage);
|
|
9159
|
-
if (!raw) return null;
|
|
9160
|
-
const decoded = decodeDocument(stage, raw.body, this.location(stage));
|
|
9161
|
-
if (decoded.schemaVersion === 2) return {
|
|
9162
|
-
document: decoded.document,
|
|
9163
|
-
version: raw.version
|
|
9164
|
-
};
|
|
9165
|
-
await this.writeV1Backup(stage, raw.body);
|
|
9166
|
-
const document = migrateV1(stage, decoded.state);
|
|
9659
|
+
async createLock(stage, holder) {
|
|
9660
|
+
await this.ensureDir();
|
|
9661
|
+
let handle;
|
|
9167
9662
|
try {
|
|
9168
|
-
|
|
9169
|
-
document,
|
|
9170
|
-
version: await this.writeRaw(stage, encodeDocument(document), raw.version)
|
|
9171
|
-
};
|
|
9663
|
+
handle = await (0, node_fs_promises.open)(this.lockPath(stage), "wx", FILE_MODE);
|
|
9172
9664
|
} catch (error) {
|
|
9173
|
-
if (error
|
|
9665
|
+
if (isCode(error, "EEXIST")) throw new require_StateStore.StateLocked(stage, await this.readLock(stage), this.lockPath(stage));
|
|
9174
9666
|
throw error;
|
|
9175
9667
|
}
|
|
9668
|
+
try {
|
|
9669
|
+
await handle.writeFile(JSON.stringify(holder, null, 2));
|
|
9670
|
+
} finally {
|
|
9671
|
+
await handle.close();
|
|
9672
|
+
}
|
|
9176
9673
|
}
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
}
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
* `createStateProvider` reads:
|
|
9184
|
-
*
|
|
9185
|
-
* - none / `'local'`: `LocalStateStore` under `.gkm/`
|
|
9186
|
-
* - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so
|
|
9187
|
-
* a stale local copy can never win over it
|
|
9188
|
-
* - `'s3'`: `S3StateStore`
|
|
9189
|
-
* - a `StateStore` object: used as is
|
|
9190
|
-
* - a `StateProvider` object: wrapped by `LegacyStateStore`
|
|
9191
|
-
*/
|
|
9192
|
-
async function createStateStore(options) {
|
|
9193
|
-
const { config, workspaceRoot, workspaceName } = options;
|
|
9194
|
-
if (!config || config.provider === "local") {
|
|
9195
|
-
const { LocalStateStore } = await Promise.resolve().then(() => require("./LocalStateStore-DDDU0HkI.cjs"));
|
|
9196
|
-
return new LocalStateStore(workspaceRoot);
|
|
9197
|
-
}
|
|
9198
|
-
if (isStateStore(config.provider)) return config.provider;
|
|
9199
|
-
if (isStateProvider(config.provider)) {
|
|
9200
|
-
const { LegacyStateStore } = await Promise.resolve().then(() => require("./LegacyStateStore-C_OFs1gN.cjs"));
|
|
9201
|
-
return new LegacyStateStore(config.provider, { warn: options.warn });
|
|
9202
|
-
}
|
|
9203
|
-
if (config.provider === "ssm") {
|
|
9204
|
-
if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
|
|
9205
|
-
const { SSMStateStore } = await Promise.resolve().then(() => require("./SSMStateStore-DC43R7zG.cjs"));
|
|
9206
|
-
const ssm = config;
|
|
9207
|
-
return SSMStateStore.create({
|
|
9208
|
-
workspaceName,
|
|
9209
|
-
region: ssm.region,
|
|
9210
|
-
profile: ssm.profile
|
|
9211
|
-
});
|
|
9674
|
+
async readLock(stage) {
|
|
9675
|
+
try {
|
|
9676
|
+
return JSON.parse(await (0, node_fs_promises.readFile)(this.lockPath(stage), "utf-8"));
|
|
9677
|
+
} catch {
|
|
9678
|
+
return null;
|
|
9679
|
+
}
|
|
9212
9680
|
}
|
|
9213
|
-
|
|
9214
|
-
if (
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
workspaceName,
|
|
9219
|
-
bucket: s3.bucket,
|
|
9220
|
-
prefix: s3.prefix,
|
|
9221
|
-
region: s3.region,
|
|
9222
|
-
profile: s3.profile
|
|
9223
|
-
});
|
|
9681
|
+
async removeLock(stage, holder) {
|
|
9682
|
+
if (holder) {
|
|
9683
|
+
if ((await this.readLock(stage))?.id !== holder.id) return;
|
|
9684
|
+
}
|
|
9685
|
+
await (0, node_fs_promises.rm)(this.lockPath(stage), { force: true });
|
|
9224
9686
|
}
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
* conditional on the version this provider last read.
|
|
9231
|
-
*/
|
|
9232
|
-
var StateStoreProvider = class {
|
|
9233
|
-
store;
|
|
9234
|
-
versions = /* @__PURE__ */ new Map();
|
|
9235
|
-
constructor(store) {
|
|
9236
|
-
this.store = store;
|
|
9687
|
+
async ensureDir() {
|
|
9688
|
+
await (0, node_fs_promises.mkdir)(this.dir, {
|
|
9689
|
+
recursive: true,
|
|
9690
|
+
mode: 448
|
|
9691
|
+
});
|
|
9237
9692
|
}
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9693
|
+
/**
|
|
9694
|
+
* Serialises the check-then-rename of a conditional write across
|
|
9695
|
+
* processes. The stage lock is for whole runs; this is for the instant of
|
|
9696
|
+
* a single write, so even writers that skip the lock never interleave.
|
|
9697
|
+
*/
|
|
9698
|
+
async withMutex(stage, fn) {
|
|
9699
|
+
const path = this.mutexPath(stage);
|
|
9700
|
+
const deadline = Date.now() + MUTEX_TIMEOUT_MS;
|
|
9701
|
+
for (;;) try {
|
|
9702
|
+
await (await (0, node_fs_promises.open)(path, "wx", FILE_MODE)).close();
|
|
9703
|
+
break;
|
|
9704
|
+
} catch (error) {
|
|
9705
|
+
if (!isCode(error, "EEXIST")) throw error;
|
|
9706
|
+
if (await this.isStale(path)) {
|
|
9707
|
+
await (0, node_fs_promises.unlink)(path).catch(() => {});
|
|
9708
|
+
continue;
|
|
9709
|
+
}
|
|
9710
|
+
if (Date.now() > deadline) throw new require_StateStore.StateStoreBusy(stage, path);
|
|
9711
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
9712
|
+
}
|
|
9713
|
+
try {
|
|
9714
|
+
return await fn();
|
|
9715
|
+
} finally {
|
|
9716
|
+
await (0, node_fs_promises.rm)(path, { force: true });
|
|
9717
|
+
}
|
|
9242
9718
|
}
|
|
9243
|
-
async
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9719
|
+
async isStale(path) {
|
|
9720
|
+
try {
|
|
9721
|
+
return Date.now() - (await (0, node_fs_promises.stat)(path)).mtimeMs > MUTEX_STALE_MS;
|
|
9722
|
+
} catch {
|
|
9723
|
+
return false;
|
|
9724
|
+
}
|
|
9248
9725
|
}
|
|
9249
9726
|
};
|
|
9250
9727
|
//#endregion
|
|
@@ -9252,7 +9729,12 @@ var StateStoreProvider = class {
|
|
|
9252
9729
|
/**
|
|
9253
9730
|
* State Management CLI Commands
|
|
9254
9731
|
*
|
|
9255
|
-
* Commands for managing deployment state across local and remote
|
|
9732
|
+
* Commands for managing deployment state across local and remote stores.
|
|
9733
|
+
*
|
|
9734
|
+
* Every command goes through a `StateStore`, the same one deploy writes
|
|
9735
|
+
* through: a v1 state is migrated on the way, resource records travel with
|
|
9736
|
+
* the state, and a copy is a conditional write — so `state:push` cannot
|
|
9737
|
+
* silently replace what a deploy wrote a moment earlier.
|
|
9256
9738
|
*/
|
|
9257
9739
|
/** What `state:show` prints in place of a secret. */
|
|
9258
9740
|
const MASKED = "********";
|
|
@@ -9278,26 +9760,12 @@ function maskStateSecrets(state) {
|
|
|
9278
9760
|
*/
|
|
9279
9761
|
async function statePullCommand(options) {
|
|
9280
9762
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
9281
|
-
|
|
9282
|
-
console.error("No remote state provider configured.");
|
|
9283
|
-
console.error("Add a remote provider in gkm.config.ts:");
|
|
9284
|
-
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
9285
|
-
process.exit(1);
|
|
9286
|
-
}
|
|
9287
|
-
const provider = await createStateProvider({
|
|
9288
|
-
config: workspace.state,
|
|
9289
|
-
workspaceRoot: workspace.root,
|
|
9290
|
-
workspaceName: workspace.name
|
|
9291
|
-
});
|
|
9292
|
-
if (!(provider instanceof CachedStateProvider)) {
|
|
9293
|
-
console.error("State provider does not support pull operation.");
|
|
9294
|
-
process.exit(1);
|
|
9295
|
-
}
|
|
9763
|
+
const { local, remote } = await remoteAndLocal(workspace);
|
|
9296
9764
|
console.log(`Pulling state for stage: ${options.stage}...`);
|
|
9297
|
-
const
|
|
9298
|
-
if (
|
|
9765
|
+
const pulled = await copyUnderLock(remote, local, local, options.stage, "state:pull");
|
|
9766
|
+
if (pulled) {
|
|
9299
9767
|
console.log("State pulled successfully.");
|
|
9300
|
-
printStateSummary(state);
|
|
9768
|
+
printStateSummary(pulled.state);
|
|
9301
9769
|
} else console.log("No remote state found for this stage.");
|
|
9302
9770
|
}
|
|
9303
9771
|
/**
|
|
@@ -9306,26 +9774,12 @@ async function statePullCommand(options) {
|
|
|
9306
9774
|
*/
|
|
9307
9775
|
async function statePushCommand(options) {
|
|
9308
9776
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
9309
|
-
|
|
9310
|
-
console.error("No remote state provider configured.");
|
|
9311
|
-
console.error("Add a remote provider in gkm.config.ts:");
|
|
9312
|
-
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
9313
|
-
process.exit(1);
|
|
9314
|
-
}
|
|
9315
|
-
const provider = await createStateProvider({
|
|
9316
|
-
config: workspace.state,
|
|
9317
|
-
workspaceRoot: workspace.root,
|
|
9318
|
-
workspaceName: workspace.name
|
|
9319
|
-
});
|
|
9320
|
-
if (!(provider instanceof CachedStateProvider)) {
|
|
9321
|
-
console.error("State provider does not support push operation.");
|
|
9322
|
-
process.exit(1);
|
|
9323
|
-
}
|
|
9777
|
+
const { local, remote } = await remoteAndLocal(workspace);
|
|
9324
9778
|
console.log(`Pushing state for stage: ${options.stage}...`);
|
|
9325
|
-
const
|
|
9326
|
-
if (
|
|
9779
|
+
const pushed = await copyUnderLock(local, remote, remote, options.stage, "state:push");
|
|
9780
|
+
if (pushed) {
|
|
9327
9781
|
console.log("State pushed successfully.");
|
|
9328
|
-
printStateSummary(state);
|
|
9782
|
+
printStateSummary(pushed.state);
|
|
9329
9783
|
} else console.log("No local state found for this stage.");
|
|
9330
9784
|
}
|
|
9331
9785
|
/**
|
|
@@ -9334,7 +9788,7 @@ async function statePushCommand(options) {
|
|
|
9334
9788
|
*/
|
|
9335
9789
|
async function stateShowCommand(options) {
|
|
9336
9790
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
9337
|
-
const stored = await (await
|
|
9791
|
+
const stored = await (await require_StateStore.createStateStore({
|
|
9338
9792
|
config: workspace.state,
|
|
9339
9793
|
workspaceRoot: workspace.root,
|
|
9340
9794
|
workspaceName: workspace.name
|
|
@@ -9343,9 +9797,12 @@ async function stateShowCommand(options) {
|
|
|
9343
9797
|
console.log(`No state found for stage: ${options.stage}`);
|
|
9344
9798
|
return;
|
|
9345
9799
|
}
|
|
9346
|
-
const state = maskStateSecrets(stored);
|
|
9800
|
+
const state = maskStateSecrets(stored.state);
|
|
9347
9801
|
if (options.json) console.log(JSON.stringify(state, null, 2));
|
|
9348
|
-
else
|
|
9802
|
+
else {
|
|
9803
|
+
printStateDetails(state);
|
|
9804
|
+
printUnfinished(stored.resources);
|
|
9805
|
+
}
|
|
9349
9806
|
}
|
|
9350
9807
|
/**
|
|
9351
9808
|
* Release a stage's deploy lock, whoever holds it.
|
|
@@ -9357,7 +9814,7 @@ async function stateShowCommand(options) {
|
|
|
9357
9814
|
*/
|
|
9358
9815
|
async function stateUnlockCommand(options) {
|
|
9359
9816
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
9360
|
-
const holder = await (await createStateStore({
|
|
9817
|
+
const holder = await (await require_StateStore.createStateStore({
|
|
9361
9818
|
config: workspace.state,
|
|
9362
9819
|
workspaceRoot: workspace.root,
|
|
9363
9820
|
workspaceName: workspace.name
|
|
@@ -9374,22 +9831,11 @@ async function stateUnlockCommand(options) {
|
|
|
9374
9831
|
*/
|
|
9375
9832
|
async function stateDiffCommand(options) {
|
|
9376
9833
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
9377
|
-
|
|
9378
|
-
console.error("No remote state provider configured.");
|
|
9379
|
-
console.error("Diff requires a remote provider to compare against.");
|
|
9380
|
-
process.exit(1);
|
|
9381
|
-
}
|
|
9382
|
-
const provider = await createStateProvider({
|
|
9383
|
-
config: workspace.state,
|
|
9384
|
-
workspaceRoot: workspace.root,
|
|
9385
|
-
workspaceName: workspace.name
|
|
9386
|
-
});
|
|
9387
|
-
if (!(provider instanceof CachedStateProvider)) {
|
|
9388
|
-
console.error("State provider does not support diff operation.");
|
|
9389
|
-
process.exit(1);
|
|
9390
|
-
}
|
|
9834
|
+
const stores = await remoteAndLocal(workspace, ["Diff requires a remote provider to compare against."]);
|
|
9391
9835
|
console.log(`Comparing state for stage: ${options.stage}...\n`);
|
|
9392
|
-
const
|
|
9836
|
+
const [localStored, remoteStored] = await Promise.all([stores.local.read(options.stage), stores.remote.read(options.stage)]);
|
|
9837
|
+
const local = localStored?.state ?? null;
|
|
9838
|
+
const remote = remoteStored?.state ?? null;
|
|
9393
9839
|
if (!local && !remote) {
|
|
9394
9840
|
console.log("No state found (local or remote).");
|
|
9395
9841
|
return;
|
|
@@ -9425,6 +9871,71 @@ async function stateDiffCommand(options) {
|
|
|
9425
9871
|
else console.log(` ${key}: ${localVal ?? "(none)"} (local) != ${remoteVal ?? "(none)"} (remote)`);
|
|
9426
9872
|
}
|
|
9427
9873
|
}
|
|
9874
|
+
const localRecords = localStored?.resources ?? {};
|
|
9875
|
+
const remoteRecords = remoteStored?.resources ?? {};
|
|
9876
|
+
const differing = [.../* @__PURE__ */ new Set([...Object.keys(localRecords), ...Object.keys(remoteRecords)])].filter((key) => describeRecord(localRecords[key]) !== describeRecord(remoteRecords[key]));
|
|
9877
|
+
if (differing.length > 0) {
|
|
9878
|
+
console.log("\nResources:");
|
|
9879
|
+
for (const key of differing) console.log(` ${key}: ${describeRecord(localRecords[key])} (local) != ${describeRecord(remoteRecords[key])} (remote)`);
|
|
9880
|
+
}
|
|
9881
|
+
}
|
|
9882
|
+
/**
|
|
9883
|
+
* The stage's local store and its remote one — or, for a workspace that keeps
|
|
9884
|
+
* state only locally, the reason there is nothing to move it between.
|
|
9885
|
+
*/
|
|
9886
|
+
async function remoteAndLocal(workspace, hint = ["Add a remote provider in gkm.config.ts:", " state: { provider: \"ssm\", region: \"us-east-1\" }"]) {
|
|
9887
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
9888
|
+
console.error("No remote state provider configured.");
|
|
9889
|
+
for (const line of hint) console.error(line);
|
|
9890
|
+
process.exit(1);
|
|
9891
|
+
}
|
|
9892
|
+
return {
|
|
9893
|
+
local: new LocalStateStore(workspace.root),
|
|
9894
|
+
remote: await require_StateStore.createStateStore({
|
|
9895
|
+
config: workspace.state,
|
|
9896
|
+
workspaceRoot: workspace.root,
|
|
9897
|
+
workspaceName: workspace.name
|
|
9898
|
+
})
|
|
9899
|
+
};
|
|
9900
|
+
}
|
|
9901
|
+
/**
|
|
9902
|
+
* Makes `to` hold exactly what `from` holds for `stage` — its state and its
|
|
9903
|
+
* resource records — while holding `locked`'s lock for the stage. Every
|
|
9904
|
+
* write is conditional on the version just read, so anything that changes
|
|
9905
|
+
* `to` mid-copy raises `StateVersionConflict` rather than being overwritten.
|
|
9906
|
+
*/
|
|
9907
|
+
async function copyUnderLock(from, to, locked, stage, operation) {
|
|
9908
|
+
const lock = await locked.lock(stage, { operation });
|
|
9909
|
+
try {
|
|
9910
|
+
return await copyStage(from, to, stage);
|
|
9911
|
+
} finally {
|
|
9912
|
+
await lock.release();
|
|
9913
|
+
}
|
|
9914
|
+
}
|
|
9915
|
+
/** @internal Exported for testing */
|
|
9916
|
+
async function copyStage(from, to, stage) {
|
|
9917
|
+
const source = await from.read(stage);
|
|
9918
|
+
if (!source) return null;
|
|
9919
|
+
const target = await to.read(stage);
|
|
9920
|
+
let version = await to.write(stage, source.state, { expectedVersion: target?.version ?? null });
|
|
9921
|
+
for (const key of Object.keys(target?.resources ?? {})) {
|
|
9922
|
+
if (key in source.resources) continue;
|
|
9923
|
+
version = await to.deleteResource(stage, key, { expectedVersion: version });
|
|
9924
|
+
}
|
|
9925
|
+
for (const { updatedAt: _stamped, ...record } of Object.values(source.resources)) version = await to.putResource(stage, record, { expectedVersion: version });
|
|
9926
|
+
return source;
|
|
9927
|
+
}
|
|
9928
|
+
function describeRecord(record) {
|
|
9929
|
+
if (!record) return "(none)";
|
|
9930
|
+
return record.id ? `${record.status} ${record.id}` : record.status;
|
|
9931
|
+
}
|
|
9932
|
+
/** Resources a run marked `pending` and never saw created. */
|
|
9933
|
+
function printUnfinished(resources) {
|
|
9934
|
+
const pending = Object.values(resources).filter((record) => record.status === "pending");
|
|
9935
|
+
if (pending.length === 0) return;
|
|
9936
|
+
console.log("");
|
|
9937
|
+
console.log("Unfinished (a deploy stopped while creating these):");
|
|
9938
|
+
for (const record of pending) console.log(` ${record.key}`);
|
|
9428
9939
|
}
|
|
9429
9940
|
function printStateSummary(state) {
|
|
9430
9941
|
const appCount = Object.keys(state.applications).length;
|
|
@@ -10472,25 +10983,25 @@ function loadPackageJson() {
|
|
|
10472
10983
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
10473
10984
|
*/
|
|
10474
10985
|
const GEEKMIDAS_VERSIONS = {
|
|
10475
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
10476
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
10477
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
10478
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
10479
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
10480
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
10481
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
10482
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
10483
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
10484
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
10485
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
10486
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
10487
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
10488
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
10489
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
10490
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
10491
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
10492
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
10493
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
10986
|
+
"@geekmidas/audit": "~10.0.0-alpha.59",
|
|
10987
|
+
"@geekmidas/auth": "~10.0.0-alpha.59",
|
|
10988
|
+
"@geekmidas/cache": "~10.0.0-alpha.59",
|
|
10989
|
+
"@geekmidas/client": "~10.0.0-alpha.59",
|
|
10990
|
+
"@geekmidas/cloud": "~10.0.0-alpha.59",
|
|
10991
|
+
"@geekmidas/constructs": "~10.0.0-alpha.59",
|
|
10992
|
+
"@geekmidas/db": "~10.0.0-alpha.59",
|
|
10993
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.59",
|
|
10994
|
+
"@geekmidas/envkit": "~10.0.0-alpha.59",
|
|
10995
|
+
"@geekmidas/errors": "~10.0.0-alpha.59",
|
|
10996
|
+
"@geekmidas/events": "~10.0.0-alpha.59",
|
|
10997
|
+
"@geekmidas/logger": "~10.0.0-alpha.59",
|
|
10998
|
+
"@geekmidas/manifest": "~10.0.0-alpha.59",
|
|
10999
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.59",
|
|
11000
|
+
"@geekmidas/schema": "~10.0.0-alpha.59",
|
|
11001
|
+
"@geekmidas/services": "~10.0.0-alpha.59",
|
|
11002
|
+
"@geekmidas/storage": "~10.0.0-alpha.59",
|
|
11003
|
+
"@geekmidas/telescope": "~10.0.0-alpha.59",
|
|
11004
|
+
"@geekmidas/testkit": "~10.0.0-alpha.59",
|
|
10494
11005
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
10495
11006
|
};
|
|
10496
11007
|
//#endregion
|
|
@@ -16372,7 +16883,7 @@ function testDatabases(reconciled) {
|
|
|
16372
16883
|
* starts without the reference data the app needs.
|
|
16373
16884
|
*/
|
|
16374
16885
|
async function migrateTestStage(workspace, env) {
|
|
16375
|
-
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-
|
|
16886
|
+
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-CThJfVHR.cjs"));
|
|
16376
16887
|
const { discover } = await Promise.resolve().then(() => require("./discover-DXNviAh-.cjs")).then((n) => n.discover_exports);
|
|
16377
16888
|
const sources = {};
|
|
16378
16889
|
const manifest = await discover({
|
|
@@ -16803,7 +17314,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
16803
17314
|
try {
|
|
16804
17315
|
const globalOptions = program.opts();
|
|
16805
17316
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16806
|
-
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
17317
|
+
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-CThJfVHR.cjs"));
|
|
16807
17318
|
await migrateCommand({
|
|
16808
17319
|
...options,
|
|
16809
17320
|
...construct ? { construct } : {}
|
|
@@ -16817,7 +17328,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
16817
17328
|
try {
|
|
16818
17329
|
const globalOptions = program.opts();
|
|
16819
17330
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16820
|
-
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
17331
|
+
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-CThJfVHR.cjs"));
|
|
16821
17332
|
await seedCommand({
|
|
16822
17333
|
...options,
|
|
16823
17334
|
...construct ? { construct } : {}
|
|
@@ -16831,7 +17342,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
16831
17342
|
try {
|
|
16832
17343
|
const globalOptions = program.opts();
|
|
16833
17344
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16834
|
-
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
17345
|
+
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-CThJfVHR.cjs"));
|
|
16835
17346
|
await migrationCommand({
|
|
16836
17347
|
construct,
|
|
16837
17348
|
...name ? { name } : {}
|
|
@@ -16929,7 +17440,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
|
|
|
16929
17440
|
try {
|
|
16930
17441
|
const globalOptions = program.opts();
|
|
16931
17442
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16932
|
-
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
17443
|
+
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-BP8zfxEJ.cjs"));
|
|
16933
17444
|
await encryptionRotateCommand(construct, options);
|
|
16934
17445
|
} catch (error) {
|
|
16935
17446
|
console.error(formatError(error));
|
|
@@ -16940,7 +17451,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
|
|
|
16940
17451
|
try {
|
|
16941
17452
|
const globalOptions = program.opts();
|
|
16942
17453
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16943
|
-
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
17454
|
+
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-BP8zfxEJ.cjs"));
|
|
16944
17455
|
await encryptionRetireCommand(construct, key, options);
|
|
16945
17456
|
} catch (error) {
|
|
16946
17457
|
console.error(formatError(error));
|
|
@@ -16992,7 +17503,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16992
17503
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16993
17504
|
process.exit(1);
|
|
16994
17505
|
}
|
|
16995
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-
|
|
17506
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-Dbn07ILl.cjs")).then((n) => n.workspace_exports);
|
|
16996
17507
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16997
17508
|
if (!result) {
|
|
16998
17509
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -17051,7 +17562,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
17051
17562
|
try {
|
|
17052
17563
|
const globalOptions = program.opts();
|
|
17053
17564
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
17054
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
17565
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-DHpDMBT7.cjs"));
|
|
17055
17566
|
await deployGithubCommand(options);
|
|
17056
17567
|
} catch (error) {
|
|
17057
17568
|
console.error(formatError(error));
|
|
@@ -17186,19 +17697,18 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
17186
17697
|
});
|
|
17187
17698
|
program.parse();
|
|
17188
17699
|
//#endregion
|
|
17189
|
-
exports
|
|
17190
|
-
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17700
|
+
Object.defineProperty(exports, "LocalStateStore_exports", {
|
|
17701
|
+
enumerable: true,
|
|
17702
|
+
get: function() {
|
|
17703
|
+
return LocalStateStore_exports;
|
|
17704
|
+
}
|
|
17705
|
+
});
|
|
17195
17706
|
Object.defineProperty(exports, "appPorts_exports", {
|
|
17196
17707
|
enumerable: true,
|
|
17197
17708
|
get: function() {
|
|
17198
17709
|
return appPorts_exports;
|
|
17199
17710
|
}
|
|
17200
17711
|
});
|
|
17201
|
-
exports.contentVersion = contentVersion;
|
|
17202
17712
|
Object.defineProperty(exports, "encryption_exports", {
|
|
17203
17713
|
enumerable: true,
|
|
17204
17714
|
get: function() {
|
|
@@ -17207,7 +17717,6 @@ Object.defineProperty(exports, "encryption_exports", {
|
|
|
17207
17717
|
});
|
|
17208
17718
|
exports.generateFullstackCustomSecrets = generateFullstackCustomSecrets;
|
|
17209
17719
|
exports.getHostingerToken = getHostingerToken;
|
|
17210
|
-
exports.newLockHolder = newLockHolder;
|
|
17211
17720
|
exports.slug = slug;
|
|
17212
17721
|
exports.stamp = stamp;
|
|
17213
17722
|
|