@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.mjs
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
3
3
|
import { i as ConstructGenerator, t as discover } from "./discover-Cf2jAcgF.mjs";
|
|
4
|
-
import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-
|
|
5
|
-
import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-
|
|
4
|
+
import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as NotABackendApp, o as loadAppConfig, p as derivedApps, r as NotInAnApp, s as loadConfig, t as ConfigNotFound, u as loadWorkspaceSettings } from "./config-EuzFEkcH.mjs";
|
|
5
|
+
import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-Bp1xVaOz.mjs";
|
|
6
6
|
import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword, t as initStageSecrets } from "./storage-BzaUxmqe.mjs";
|
|
7
7
|
import { a as getKeyPath, i as FileSecretsStore, n as secretsStoreFor } from "./store-DeGATCS-.mjs";
|
|
8
|
-
import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as fakeKey, a as reconcileWorkspace, b as createEntryWrapper, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as describeServices, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as primaryPortKey, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-
|
|
8
|
+
import { C as loadEnvFiles, S as isPortAvailable, T as prepareEntryCredentials, _ as fakeKey, a as reconcileWorkspace, b as createEntryWrapper, c as LOCAL_CA_PATH, d as postgresDatabaseNames, f as quoteIdentifier, g as describeServices, h as rootSite, i as derivedContainers, l as forgetState, m as resourceName, n as backendsOf$1, p as pgClient, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as primaryPortKey, w as loadSecretsForApp, x as findAvailablePort, y as createCredentialsPreload } from "./workspace-BRZ14o-o.mjs";
|
|
9
9
|
import { i as telemetryFor, n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-BRWD1Wit.mjs";
|
|
10
10
|
import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
|
|
11
11
|
import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-DbDHneLR.mjs";
|
|
12
12
|
import { i as storageBackendFor, n as eventsBackendFor, r as providerOf, t as cacheBackendFor } from "./backends-CjuCiE8h.mjs";
|
|
13
13
|
import { n as withCompute, r as withRoutes, t as manifestModule } from "./emit-C1e8qD08.mjs";
|
|
14
|
+
import { c as createStateStore, i as StateStoreBusy, o as StateVersionConflict, r as StateLocked, s as contentVersion, t as DocumentStateStore } from "./StateStore-CYUfGYcx.mjs";
|
|
14
15
|
import { createRequire } from "node:module";
|
|
15
16
|
import { Command } from "commander";
|
|
16
17
|
import { stdin, stdout } from "node:process";
|
|
17
18
|
import * as readline from "node:readline/promises";
|
|
18
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
19
|
-
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
20
|
-
import { homedir,
|
|
19
|
+
import { copyFileSync, existsSync, linkSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { chmod, mkdir, mkdtemp, open, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
21
|
+
import { homedir, tmpdir } from "node:os";
|
|
21
22
|
import { basename, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
|
|
22
23
|
import { cacheTable, canonicalId, cloudName, cookieDomain, dependentsOf, externalApiUrl, kebabCase, migrationFolder, mobileOrigins, provideKey, provisionOrder, schemeBase, scopedName, serviceKey } from "@geekmidas/manifest";
|
|
23
24
|
import fg from "fast-glob";
|
|
24
25
|
import { execFile, execFileSync, execSync, spawn } from "node:child_process";
|
|
25
26
|
import chokidar from "chokidar";
|
|
26
|
-
import {
|
|
27
|
+
import { connect } from "node:net";
|
|
28
|
+
import { createCipheriv, createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
27
29
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
28
30
|
import { generateKeyring } from "@geekmidas/constructs/encryption";
|
|
29
31
|
import { ownerRole, readerRole, roleStatements } from "@geekmidas/db/pg/roles";
|
|
@@ -32,9 +34,11 @@ import { parse as parse$1, parseDocument } from "yaml";
|
|
|
32
34
|
import { promisify } from "node:util";
|
|
33
35
|
import { cacheTableStatements } from "@geekmidas/cache/postgres";
|
|
34
36
|
import prompts from "prompts";
|
|
35
|
-
import { createServer } from "http";
|
|
37
|
+
import { createServer as createServer$1 } from "node:http";
|
|
38
|
+
import { Readable } from "node:stream";
|
|
39
|
+
import { createServer as createServer$2 } from "http";
|
|
36
40
|
import { Http2ServerRequest } from "http2";
|
|
37
|
-
import { Readable } from "stream";
|
|
41
|
+
import { Readable as Readable$1 } from "stream";
|
|
38
42
|
import crypto from "crypto";
|
|
39
43
|
import { Cron } from "@geekmidas/constructs/crons";
|
|
40
44
|
import { Function } from "@geekmidas/constructs/functions";
|
|
@@ -42,7 +46,7 @@ import { Queue } from "@geekmidas/constructs/queue";
|
|
|
42
46
|
import { Topic } from "@geekmidas/constructs/topic";
|
|
43
47
|
import { lookup } from "node:dns/promises";
|
|
44
48
|
//#region package.json
|
|
45
|
-
var version = "10.0.0-alpha.
|
|
49
|
+
var version = "10.0.0-alpha.59";
|
|
46
50
|
//#endregion
|
|
47
51
|
//#region src/auth/credentials.ts
|
|
48
52
|
/**
|
|
@@ -756,6 +760,621 @@ async function assignAppPorts(workspace, names, deps) {
|
|
|
756
760
|
running
|
|
757
761
|
};
|
|
758
762
|
}
|
|
763
|
+
/** Overrides `dev.discoveryPort` — `0` serves on a port of its own. */
|
|
764
|
+
const DISCOVERY_PORT_ENV = "GKM_DISCOVERY_PORT";
|
|
765
|
+
/**
|
|
766
|
+
* Set by a workspace's `gkm dev` for the app sessions turbo starts under it:
|
|
767
|
+
* the workspace has printed the connect URL, so they do not repeat it.
|
|
768
|
+
*/
|
|
769
|
+
const DISCOVERY_QUIET_ENV = "GKM_DISCOVERY_QUIET";
|
|
770
|
+
/** The route everything is served under. */
|
|
771
|
+
const BASE = "/__gkm";
|
|
772
|
+
/** `GKM_DISCOVERY_PORT` is set to something that is not a port. */
|
|
773
|
+
var InvalidDiscoveryPort = class extends Error {
|
|
774
|
+
value;
|
|
775
|
+
constructor(value) {
|
|
776
|
+
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.`);
|
|
777
|
+
this.value = value;
|
|
778
|
+
this.name = "InvalidDiscoveryPort";
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
/** The port the endpoint uses: the env, then `dev.discoveryPort`, then 4983. */
|
|
782
|
+
function discoveryPort(config, env = process.env) {
|
|
783
|
+
const raw = env[DISCOVERY_PORT_ENV];
|
|
784
|
+
if (raw !== void 0 && raw !== "") {
|
|
785
|
+
const port = Number(raw);
|
|
786
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new InvalidDiscoveryPort(raw);
|
|
787
|
+
return port;
|
|
788
|
+
}
|
|
789
|
+
return config?.discoveryPort ?? 4983;
|
|
790
|
+
}
|
|
791
|
+
/** The machine's registry, shared by every `gkm dev`. */
|
|
792
|
+
function discoveryRegistry(env = process.env) {
|
|
793
|
+
return env["GKM_DEV_REGISTRY"] || join(homedir(), ".gkm", "dev");
|
|
794
|
+
}
|
|
795
|
+
function sessionsDir(registry) {
|
|
796
|
+
return join(registry, "sessions");
|
|
797
|
+
}
|
|
798
|
+
/** Whether a process is alive. `EPERM` is someone else's — alive. */
|
|
799
|
+
function alive(pid) {
|
|
800
|
+
try {
|
|
801
|
+
process.kill(pid, 0);
|
|
802
|
+
return true;
|
|
803
|
+
} catch (error) {
|
|
804
|
+
return error.code === "EPERM";
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Every live session, pruning the ones whose process is gone — a `gkm dev`
|
|
809
|
+
* killed outright never got to remove its own.
|
|
810
|
+
*/
|
|
811
|
+
function liveSessions(registry) {
|
|
812
|
+
const dir = sessionsDir(registry);
|
|
813
|
+
let files;
|
|
814
|
+
try {
|
|
815
|
+
files = readdirSync(dir).filter((file) => file.endsWith(".json"));
|
|
816
|
+
} catch {
|
|
817
|
+
return [];
|
|
818
|
+
}
|
|
819
|
+
const records = [];
|
|
820
|
+
for (const file of files) {
|
|
821
|
+
const path = join(dir, file);
|
|
822
|
+
let record;
|
|
823
|
+
try {
|
|
824
|
+
record = JSON.parse(readFileSync(path, "utf8"));
|
|
825
|
+
} catch {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
if (alive(record.pid)) {
|
|
829
|
+
records.push(record);
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
try {
|
|
833
|
+
unlinkSync(path);
|
|
834
|
+
} catch {}
|
|
835
|
+
}
|
|
836
|
+
return records;
|
|
837
|
+
}
|
|
838
|
+
/** Written whole, then renamed into place, so a reader never sees half. */
|
|
839
|
+
function writeRecord(registry, record) {
|
|
840
|
+
const path = join(sessionsDir(registry), `${record.id}.json`);
|
|
841
|
+
const partial = `${path}.${process.pid}.tmp`;
|
|
842
|
+
writeFileSync(partial, JSON.stringify(record), { mode: 384 });
|
|
843
|
+
renameSync(partial, path);
|
|
844
|
+
}
|
|
845
|
+
function removeRecord(registry, id) {
|
|
846
|
+
try {
|
|
847
|
+
unlinkSync(join(sessionsDir(registry), `${id}.json`));
|
|
848
|
+
} catch {}
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* The token every session prints and every host checks.
|
|
852
|
+
*
|
|
853
|
+
* Shared through the registry, so a URL printed by one session still works
|
|
854
|
+
* after another has taken over hosting. It lives as long as some session does:
|
|
855
|
+
* a session that finds none but itself starts a new one, so a token printed
|
|
856
|
+
* yesterday stops working once everything it was printed by has stopped.
|
|
857
|
+
*/
|
|
858
|
+
function sessionToken(registry, self) {
|
|
859
|
+
const path = join(registry, "token");
|
|
860
|
+
if (liveSessions(registry).filter((r) => r.id !== self).length === 0) {
|
|
861
|
+
const stale = `${path}.${self}.stale`;
|
|
862
|
+
try {
|
|
863
|
+
renameSync(path, stale);
|
|
864
|
+
unlinkSync(stale);
|
|
865
|
+
} catch {}
|
|
866
|
+
}
|
|
867
|
+
const fresh = `${path}.${self}.new`;
|
|
868
|
+
writeFileSync(fresh, randomBytes(32).toString("base64url"), { mode: 384 });
|
|
869
|
+
try {
|
|
870
|
+
linkSync(fresh, path);
|
|
871
|
+
} catch {}
|
|
872
|
+
unlinkSync(fresh);
|
|
873
|
+
return readFileSync(path, "utf8").trim();
|
|
874
|
+
}
|
|
875
|
+
/** `<name>-<8 hex>`: readable, and two checkouts of a project stay apart. */
|
|
876
|
+
function workspaceId(name, root) {
|
|
877
|
+
return `${name.toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "workspace"}-${createHash("sha256").update(root).digest("hex").slice(0, 8)}`;
|
|
878
|
+
}
|
|
879
|
+
/** Whether anything accepts connections on a local port. */
|
|
880
|
+
function listening(port) {
|
|
881
|
+
return new Promise((resolve) => {
|
|
882
|
+
const socket = connect({
|
|
883
|
+
port,
|
|
884
|
+
host: "localhost"
|
|
885
|
+
});
|
|
886
|
+
const done = (open) => {
|
|
887
|
+
socket.destroy();
|
|
888
|
+
resolve(open);
|
|
889
|
+
};
|
|
890
|
+
socket.setTimeout(300, () => done(false));
|
|
891
|
+
socket.once("connect", () => done(true));
|
|
892
|
+
socket.once("error", () => done(false));
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
/** Which data APIs the forwarder matches by prefix, rather than exactly. */
|
|
896
|
+
const PREFIXED = /* @__PURE__ */ new Set(["telescope", "database"]);
|
|
897
|
+
/** Whether `path` on an app is one of its data APIs. */
|
|
898
|
+
function servesData(app, path) {
|
|
899
|
+
return app.dataApis.some((api) => PREFIXED.has(api.kind) ? path === api.path || path.startsWith(`${api.path}/`) : path === api.path);
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Every workspace the sessions describe, one entry per root.
|
|
903
|
+
*
|
|
904
|
+
* An app listed only by its workspace's session — a web app turbo started, an
|
|
905
|
+
* API whose own `gkm dev` has not registered yet — is `ready` when its port
|
|
906
|
+
* answers and `starting` until then.
|
|
907
|
+
*/
|
|
908
|
+
async function collect(records) {
|
|
909
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
910
|
+
for (const record of records) {
|
|
911
|
+
const group = byRoot.get(record.workspace.root) ?? [];
|
|
912
|
+
group.push(record);
|
|
913
|
+
byRoot.set(record.workspace.root, group);
|
|
914
|
+
}
|
|
915
|
+
const views = [];
|
|
916
|
+
for (const [root, group] of byRoot) {
|
|
917
|
+
group.sort((a, b) => Number(b.role === "workspace") - Number(a.role === "workspace") || a.startedAt.localeCompare(b.startedAt));
|
|
918
|
+
const lead = group[0];
|
|
919
|
+
const id = workspaceId(lead.workspace.name, root);
|
|
920
|
+
const apps = /* @__PURE__ */ new Map();
|
|
921
|
+
for (const record of group) for (const app of record.apps) {
|
|
922
|
+
const own = record.role === "app";
|
|
923
|
+
if (!own && apps.has(app.name)) continue;
|
|
924
|
+
apps.set(app.name, {
|
|
925
|
+
app,
|
|
926
|
+
pid: own ? record.pid : null
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
const discovered = await Promise.all([...apps.values()].map(async ({ app, pid }) => {
|
|
930
|
+
const url = `http://localhost:${app.port}`;
|
|
931
|
+
const status = pid === null ? await listening(app.port) ? "ready" : "starting" : app.status;
|
|
932
|
+
return {
|
|
933
|
+
name: app.name,
|
|
934
|
+
type: app.type,
|
|
935
|
+
port: app.port,
|
|
936
|
+
url,
|
|
937
|
+
publicUrl: app.publicUrl ?? null,
|
|
938
|
+
status,
|
|
939
|
+
reloads: app.reloads,
|
|
940
|
+
pid,
|
|
941
|
+
dataApis: app.dataApis.map((api) => ({
|
|
942
|
+
kind: api.kind,
|
|
943
|
+
path: api.path,
|
|
944
|
+
url: `${url}${api.path}`,
|
|
945
|
+
proxy: `${BASE}/workspaces/${id}/apps/${encodeURIComponent(app.name)}${api.path}`
|
|
946
|
+
}))
|
|
947
|
+
};
|
|
948
|
+
}));
|
|
949
|
+
discovered.sort((a, b) => a.name.localeCompare(b.name));
|
|
950
|
+
views.push({
|
|
951
|
+
id,
|
|
952
|
+
name: lead.workspace.name,
|
|
953
|
+
root,
|
|
954
|
+
stage: lead.workspace.stage,
|
|
955
|
+
manifest: group.find((r) => r.manifest)?.manifest ?? null,
|
|
956
|
+
apps: discovered,
|
|
957
|
+
allowedOrigins: [...new Set(group.flatMap((r) => r.allowedOrigins))]
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
return views.sort((a, b) => a.name.localeCompare(b.name) || a.root.localeCompare(b.root));
|
|
961
|
+
}
|
|
962
|
+
/** What changed between two views, as events. */
|
|
963
|
+
function changes(before, after) {
|
|
964
|
+
const out = [];
|
|
965
|
+
const previous = new Map(before.map((w) => [w.id, w]));
|
|
966
|
+
const next = new Map(after.map((w) => [w.id, w]));
|
|
967
|
+
for (const workspace of after) {
|
|
968
|
+
const was = previous.get(workspace.id);
|
|
969
|
+
if (!was) out.push({
|
|
970
|
+
workspace,
|
|
971
|
+
event: {
|
|
972
|
+
type: "workspace.started",
|
|
973
|
+
workspace: workspace.id
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
const wasApps = new Map((was?.apps ?? []).map((a) => [a.name, a]));
|
|
977
|
+
for (const app of workspace.apps) {
|
|
978
|
+
const old = wasApps.get(app.name);
|
|
979
|
+
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;
|
|
980
|
+
if (type) out.push({
|
|
981
|
+
workspace,
|
|
982
|
+
event: {
|
|
983
|
+
type,
|
|
984
|
+
workspace: workspace.id,
|
|
985
|
+
app
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
for (const old of was?.apps ?? []) if (!workspace.apps.some((a) => a.name === old.name)) out.push({
|
|
990
|
+
workspace,
|
|
991
|
+
event: {
|
|
992
|
+
type: "app.stopped",
|
|
993
|
+
workspace: workspace.id,
|
|
994
|
+
app: old
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
for (const workspace of before) {
|
|
999
|
+
if (next.has(workspace.id)) continue;
|
|
1000
|
+
for (const app of workspace.apps) out.push({
|
|
1001
|
+
workspace,
|
|
1002
|
+
event: {
|
|
1003
|
+
type: "app.stopped",
|
|
1004
|
+
workspace: workspace.id,
|
|
1005
|
+
app
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
out.push({
|
|
1009
|
+
workspace,
|
|
1010
|
+
event: {
|
|
1011
|
+
type: "workspace.stopped",
|
|
1012
|
+
workspace: workspace.id
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
/** A browser with no `Origin` — curl, a local process — sees everything. */
|
|
1019
|
+
function visibleTo(workspace, origin) {
|
|
1020
|
+
return origin === void 0 || workspace.allowedOrigins.includes(origin);
|
|
1021
|
+
}
|
|
1022
|
+
function publicView({ allowedOrigins: _, ...workspace }) {
|
|
1023
|
+
return workspace;
|
|
1024
|
+
}
|
|
1025
|
+
/** Headers an app's response is passed on with; nothing that sets state. */
|
|
1026
|
+
const FORWARDED_RESPONSE_HEADERS = [
|
|
1027
|
+
"content-type",
|
|
1028
|
+
"cache-control",
|
|
1029
|
+
"etag",
|
|
1030
|
+
"last-modified"
|
|
1031
|
+
];
|
|
1032
|
+
/**
|
|
1033
|
+
* The HTTP side: serves the registry it reads, checking every request.
|
|
1034
|
+
* @internal Exported for testing
|
|
1035
|
+
*/
|
|
1036
|
+
var DiscoveryHost = class {
|
|
1037
|
+
registry;
|
|
1038
|
+
token;
|
|
1039
|
+
pollMs;
|
|
1040
|
+
server;
|
|
1041
|
+
view = [];
|
|
1042
|
+
streams = /* @__PURE__ */ new Set();
|
|
1043
|
+
scanning;
|
|
1044
|
+
timers = [];
|
|
1045
|
+
sequence = 0;
|
|
1046
|
+
constructor(registry, token, pollMs) {
|
|
1047
|
+
this.registry = registry;
|
|
1048
|
+
this.token = token;
|
|
1049
|
+
this.pollMs = pollMs;
|
|
1050
|
+
}
|
|
1051
|
+
/** The port it is bound to, once it is. */
|
|
1052
|
+
get port() {
|
|
1053
|
+
return (this.server?.address())?.port;
|
|
1054
|
+
}
|
|
1055
|
+
/** Binds the port; `false` when another process already has it. */
|
|
1056
|
+
async listen(port) {
|
|
1057
|
+
const server = createServer$1((request, response) => {
|
|
1058
|
+
this.handle(request, response).catch(() => {
|
|
1059
|
+
if (!response.headersSent) reply(response, 500, { error: "DiscoveryFailed" });
|
|
1060
|
+
else response.destroy();
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
1063
|
+
if (!await new Promise((resolve) => {
|
|
1064
|
+
server.once("error", () => resolve(false));
|
|
1065
|
+
server.listen(port, "127.0.0.1", () => resolve(true));
|
|
1066
|
+
})) return false;
|
|
1067
|
+
server.unref();
|
|
1068
|
+
this.server = server;
|
|
1069
|
+
this.view = await this.scan();
|
|
1070
|
+
this.timers.push(setInterval(() => void this.scan(), this.pollMs).unref(), setInterval(() => {
|
|
1071
|
+
for (const { response } of this.streams) response.write(": ping\n\n");
|
|
1072
|
+
}, 15e3).unref());
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
async close() {
|
|
1076
|
+
for (const timer of this.timers) clearInterval(timer);
|
|
1077
|
+
this.timers = [];
|
|
1078
|
+
for (const { response } of this.streams) response.end();
|
|
1079
|
+
this.streams.clear();
|
|
1080
|
+
const server = this.server;
|
|
1081
|
+
this.server = void 0;
|
|
1082
|
+
if (!server) return;
|
|
1083
|
+
await new Promise((resolve) => {
|
|
1084
|
+
server.close(() => resolve());
|
|
1085
|
+
server.closeAllConnections();
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
/** Reads the registry, and tells every stream what changed. One at a time. */
|
|
1089
|
+
scan() {
|
|
1090
|
+
const run = (this.scanning ?? Promise.resolve()).then(async () => {
|
|
1091
|
+
const next = await collect(liveSessions(this.registry));
|
|
1092
|
+
for (const { workspace, event } of changes(this.view, next)) this.broadcast(workspace, event);
|
|
1093
|
+
this.view = next;
|
|
1094
|
+
return next;
|
|
1095
|
+
});
|
|
1096
|
+
this.scanning = run.catch(() => this.view);
|
|
1097
|
+
return run;
|
|
1098
|
+
}
|
|
1099
|
+
broadcast(workspace, event) {
|
|
1100
|
+
this.sequence += 1;
|
|
1101
|
+
for (const stream of this.streams) {
|
|
1102
|
+
if (!visibleTo(workspace, stream.origin)) continue;
|
|
1103
|
+
send(stream.response, event, this.sequence);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
snapshot(origin) {
|
|
1107
|
+
return {
|
|
1108
|
+
version: 1,
|
|
1109
|
+
host: {
|
|
1110
|
+
pid: process.pid,
|
|
1111
|
+
port: this.port ?? 0
|
|
1112
|
+
},
|
|
1113
|
+
events: `${BASE}/events`,
|
|
1114
|
+
workspaces: this.view.filter((w) => visibleTo(w, origin)).map(publicView)
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
async handle(request, response) {
|
|
1118
|
+
const port = this.port;
|
|
1119
|
+
const host = request.headers.host;
|
|
1120
|
+
if (host !== `127.0.0.1:${port}` && host !== `localhost:${port}`) {
|
|
1121
|
+
reply(response, 403, {
|
|
1122
|
+
error: "HostNotAllowed",
|
|
1123
|
+
message: `Reach discovery at http://127.0.0.1:${port}.`
|
|
1124
|
+
});
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const origin = request.headers.origin;
|
|
1128
|
+
const allowed = this.view.flatMap((w) => w.allowedOrigins);
|
|
1129
|
+
if (origin !== void 0 && !allowed.includes(origin)) {
|
|
1130
|
+
reply(response, 403, {
|
|
1131
|
+
error: "OriginNotAllowed",
|
|
1132
|
+
message: `Add ${origin} to dev.allowedOrigins in gkm.config.ts to let it read gkm dev.`
|
|
1133
|
+
});
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (origin !== void 0) {
|
|
1137
|
+
response.setHeader("access-control-allow-origin", origin);
|
|
1138
|
+
response.setHeader("vary", "Origin");
|
|
1139
|
+
}
|
|
1140
|
+
if (request.method === "OPTIONS") {
|
|
1141
|
+
response.setHeader("access-control-allow-methods", "GET, HEAD");
|
|
1142
|
+
response.setHeader("access-control-allow-headers", "Authorization");
|
|
1143
|
+
response.setHeader("access-control-max-age", "600");
|
|
1144
|
+
if (request.headers["access-control-request-private-network"]) response.setHeader("access-control-allow-private-network", "true");
|
|
1145
|
+
response.writeHead(204).end();
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const url = new URL(request.url ?? "/", `http://${host}`);
|
|
1149
|
+
if (!this.authorized(request, url)) {
|
|
1150
|
+
reply(response, 401, {
|
|
1151
|
+
error: "TokenRequired",
|
|
1152
|
+
message: "Use the connect URL gkm dev printed, or send its token as `Authorization: Bearer <token>`."
|
|
1153
|
+
});
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
1157
|
+
response.setHeader("allow", "GET, HEAD, OPTIONS");
|
|
1158
|
+
reply(response, 405, {
|
|
1159
|
+
error: "ReadOnly",
|
|
1160
|
+
message: "Discovery is read-only: GET and HEAD."
|
|
1161
|
+
});
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const path = url.pathname.replace(/\/+$/, "") || "/";
|
|
1165
|
+
if (path === BASE) {
|
|
1166
|
+
await this.scan();
|
|
1167
|
+
reply(response, 200, this.snapshot(origin));
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (path === `${BASE}/events`) {
|
|
1171
|
+
await this.scan();
|
|
1172
|
+
this.stream(response, origin);
|
|
1173
|
+
response.once("close", () => {
|
|
1174
|
+
for (const stream of this.streams) if (stream.response === response) this.streams.delete(stream);
|
|
1175
|
+
});
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const forwarded = path.match(/^\/__gkm\/workspaces\/([^/]+)\/apps\/([^/]+)(\/.*)$/);
|
|
1179
|
+
if (forwarded) {
|
|
1180
|
+
const [, id, name, rest] = forwarded;
|
|
1181
|
+
await this.forward(request, response, origin, id, decodeURIComponent(name), rest, url);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
reply(response, 404, { error: "NotFound" });
|
|
1185
|
+
}
|
|
1186
|
+
authorized(request, url) {
|
|
1187
|
+
const header = request.headers.authorization;
|
|
1188
|
+
const given = header?.startsWith("Bearer ") ? header.slice(7).trim() : url.searchParams.get("token");
|
|
1189
|
+
if (!given) return false;
|
|
1190
|
+
const a = Buffer.from(given);
|
|
1191
|
+
const b = Buffer.from(this.token);
|
|
1192
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
1193
|
+
}
|
|
1194
|
+
stream(response, origin) {
|
|
1195
|
+
response.writeHead(200, {
|
|
1196
|
+
"content-type": "text/event-stream",
|
|
1197
|
+
"cache-control": "no-cache",
|
|
1198
|
+
connection: "keep-alive"
|
|
1199
|
+
});
|
|
1200
|
+
response.write("retry: 1000\n\n");
|
|
1201
|
+
send(response, {
|
|
1202
|
+
type: "snapshot",
|
|
1203
|
+
snapshot: this.snapshot(origin)
|
|
1204
|
+
});
|
|
1205
|
+
this.streams.add({
|
|
1206
|
+
origin,
|
|
1207
|
+
response
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
/** Passes a read of one data API on to the app that serves it. */
|
|
1211
|
+
async forward(request, response, origin, id, name, path, url) {
|
|
1212
|
+
const app = this.view.find((w) => w.id === id && visibleTo(w, origin))?.apps.find((a) => a.name === name);
|
|
1213
|
+
if (!app) {
|
|
1214
|
+
reply(response, 404, { error: "AppNotFound" });
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (!servesData(app, path)) {
|
|
1218
|
+
reply(response, 404, {
|
|
1219
|
+
error: "NotADataApi",
|
|
1220
|
+
message: `Only ${name}'s data APIs are forwarded: ${app.dataApis.map((a) => a.path).join(", ") || "it has none"}.`
|
|
1221
|
+
});
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const search = new URLSearchParams(url.searchParams);
|
|
1225
|
+
search.delete("token");
|
|
1226
|
+
const query = search.size > 0 ? `?${search}` : "";
|
|
1227
|
+
const abort = new AbortController();
|
|
1228
|
+
response.once("close", () => abort.abort());
|
|
1229
|
+
let upstream;
|
|
1230
|
+
try {
|
|
1231
|
+
upstream = await fetch(`http://localhost:${app.port}${path}${query}`, {
|
|
1232
|
+
method: request.method,
|
|
1233
|
+
headers: { accept: request.headers.accept ?? "*/*" },
|
|
1234
|
+
signal: abort.signal
|
|
1235
|
+
});
|
|
1236
|
+
} catch {
|
|
1237
|
+
reply(response, 502, {
|
|
1238
|
+
error: "AppUnavailable",
|
|
1239
|
+
message: `${name} is not answering on port ${app.port}.`
|
|
1240
|
+
});
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const headers = {};
|
|
1244
|
+
for (const key of FORWARDED_RESPONSE_HEADERS) {
|
|
1245
|
+
const value = upstream.headers.get(key);
|
|
1246
|
+
if (value) headers[key] = value;
|
|
1247
|
+
}
|
|
1248
|
+
response.writeHead(upstream.status, headers);
|
|
1249
|
+
if (!upstream.body || request.method === "HEAD") {
|
|
1250
|
+
response.end();
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
Readable.fromWeb(upstream.body).on("error", () => response.destroy()).pipe(response);
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
function reply(response, status, body) {
|
|
1257
|
+
response.writeHead(status, { "content-type": "application/json" });
|
|
1258
|
+
response.end(JSON.stringify(body));
|
|
1259
|
+
}
|
|
1260
|
+
function send(response, event, id) {
|
|
1261
|
+
response.write(`${id === void 0 ? "" : `id: ${id}\n`}event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* One `gkm dev`'s membership: its record in the registry, and — while it holds
|
|
1265
|
+
* the port — the endpoint itself.
|
|
1266
|
+
*/
|
|
1267
|
+
var DiscoverySession = class DiscoverySession {
|
|
1268
|
+
registry;
|
|
1269
|
+
port;
|
|
1270
|
+
record;
|
|
1271
|
+
token;
|
|
1272
|
+
pollMs;
|
|
1273
|
+
host;
|
|
1274
|
+
election;
|
|
1275
|
+
left = false;
|
|
1276
|
+
cleanup = () => removeRecord(this.registry, this.record.id);
|
|
1277
|
+
constructor(registry, port, record, token, pollMs) {
|
|
1278
|
+
this.registry = registry;
|
|
1279
|
+
this.port = port;
|
|
1280
|
+
this.record = record;
|
|
1281
|
+
this.token = token;
|
|
1282
|
+
this.pollMs = pollMs;
|
|
1283
|
+
}
|
|
1284
|
+
static async join(options) {
|
|
1285
|
+
const registry = options.registry ?? discoveryRegistry();
|
|
1286
|
+
mkdirSync(sessionsDir(registry), {
|
|
1287
|
+
recursive: true,
|
|
1288
|
+
mode: 448
|
|
1289
|
+
});
|
|
1290
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1291
|
+
const record = {
|
|
1292
|
+
id: `${process.pid}-${randomBytes(4).toString("hex")}`,
|
|
1293
|
+
pid: process.pid,
|
|
1294
|
+
role: options.role,
|
|
1295
|
+
startedAt: now,
|
|
1296
|
+
updatedAt: now,
|
|
1297
|
+
workspace: options.workspace,
|
|
1298
|
+
allowedOrigins: [...options.allowedOrigins ?? []],
|
|
1299
|
+
...options.manifest ? { manifest: options.manifest } : {},
|
|
1300
|
+
apps: options.apps
|
|
1301
|
+
};
|
|
1302
|
+
writeRecord(registry, record);
|
|
1303
|
+
const token = sessionToken(registry, record.id);
|
|
1304
|
+
const session = new DiscoverySession(registry, options.port ?? 4983, record, token, options.pollMs ?? 500);
|
|
1305
|
+
process.once("exit", session.cleanup);
|
|
1306
|
+
await session.elect();
|
|
1307
|
+
if (!session.host && session.port !== 0) session.election = setInterval(() => void session.elect(), options.electionMs ?? 1e3).unref();
|
|
1308
|
+
return session;
|
|
1309
|
+
}
|
|
1310
|
+
/** Whether this session is the one serving the endpoint. */
|
|
1311
|
+
get hosting() {
|
|
1312
|
+
return this.host !== void 0;
|
|
1313
|
+
}
|
|
1314
|
+
/** The port clients reach — this host's own, when it chose one. */
|
|
1315
|
+
get endpointPort() {
|
|
1316
|
+
return this.host?.port ?? this.port;
|
|
1317
|
+
}
|
|
1318
|
+
/** Jupyter-style: the endpoint, with the token that opens it. */
|
|
1319
|
+
get connectUrl() {
|
|
1320
|
+
return `http://127.0.0.1:${this.endpointPort}${BASE}?token=${this.token}`;
|
|
1321
|
+
}
|
|
1322
|
+
/** Changes one of this session's apps, and tells the endpoint. */
|
|
1323
|
+
updateApp(name, change) {
|
|
1324
|
+
if (this.left) return;
|
|
1325
|
+
this.record.apps = this.record.apps.map((app) => app.name === name ? change(app) : app);
|
|
1326
|
+
this.record.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1327
|
+
writeRecord(this.registry, this.record);
|
|
1328
|
+
this.host?.scan();
|
|
1329
|
+
}
|
|
1330
|
+
/** Leaves the registry, and the port to whichever session takes it next. */
|
|
1331
|
+
async leave() {
|
|
1332
|
+
if (this.left) return;
|
|
1333
|
+
this.left = true;
|
|
1334
|
+
if (this.election) clearInterval(this.election);
|
|
1335
|
+
process.removeListener("exit", this.cleanup);
|
|
1336
|
+
this.cleanup();
|
|
1337
|
+
await this.host?.close();
|
|
1338
|
+
this.host = void 0;
|
|
1339
|
+
if (liveSessions(this.registry).length === 0) try {
|
|
1340
|
+
unlinkSync(join(this.registry, "token"));
|
|
1341
|
+
} catch {}
|
|
1342
|
+
}
|
|
1343
|
+
async elect() {
|
|
1344
|
+
if (this.host || this.left) return;
|
|
1345
|
+
const host = new DiscoveryHost(this.registry, this.token, this.pollMs);
|
|
1346
|
+
if (!await host.listen(this.port)) return;
|
|
1347
|
+
if (this.left) {
|
|
1348
|
+
await host.close();
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
this.host = host;
|
|
1352
|
+
if (this.election) clearInterval(this.election);
|
|
1353
|
+
this.election = void 0;
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
/** Joins the machine's discovery; see {@link DiscoverySession}. */
|
|
1357
|
+
function joinDiscovery(options) {
|
|
1358
|
+
return DiscoverySession.join(options);
|
|
1359
|
+
}
|
|
1360
|
+
/** The data APIs a generated dev app mounts, from what `gkm dev` built. */
|
|
1361
|
+
function dataApisOf(options) {
|
|
1362
|
+
if (options.selfServing) return [];
|
|
1363
|
+
return [
|
|
1364
|
+
...options.telescopePath ? [{
|
|
1365
|
+
kind: "telescope",
|
|
1366
|
+
path: `${options.telescopePath}/api`
|
|
1367
|
+
}] : [],
|
|
1368
|
+
...options.databaseApi ? [{
|
|
1369
|
+
kind: "database",
|
|
1370
|
+
path: options.databaseApi
|
|
1371
|
+
}] : [],
|
|
1372
|
+
...options.openApi ? [{
|
|
1373
|
+
kind: "openapi",
|
|
1374
|
+
path: "/__docs"
|
|
1375
|
+
}] : []
|
|
1376
|
+
];
|
|
1377
|
+
}
|
|
759
1378
|
//#endregion
|
|
760
1379
|
//#region ../../node_modules/.pnpm/@hono+node-server@1.19.5_hono@4.13.8/node_modules/@hono/node-server/dist/index.mjs
|
|
761
1380
|
var RequestError = class extends Error {
|
|
@@ -809,7 +1428,7 @@ var newRequestFromIncoming = (method, url, headers, incoming, abortController) =
|
|
|
809
1428
|
let reader;
|
|
810
1429
|
init.body = new ReadableStream({ async pull(controller) {
|
|
811
1430
|
try {
|
|
812
|
-
reader ||= Readable.toWeb(incoming).getReader();
|
|
1431
|
+
reader ||= Readable$1.toWeb(incoming).getReader();
|
|
813
1432
|
const { done, value } = await reader.read();
|
|
814
1433
|
if (done) controller.close();
|
|
815
1434
|
else controller.enqueue(value);
|
|
@@ -817,7 +1436,7 @@ var newRequestFromIncoming = (method, url, headers, incoming, abortController) =
|
|
|
817
1436
|
controller.error(error);
|
|
818
1437
|
}
|
|
819
1438
|
} });
|
|
820
|
-
} else init.body = Readable.toWeb(incoming);
|
|
1439
|
+
} else init.body = Readable$1.toWeb(incoming);
|
|
821
1440
|
}
|
|
822
1441
|
return new Request(url, init);
|
|
823
1442
|
};
|
|
@@ -1188,7 +1807,7 @@ var createAdaptorServer = (options) => {
|
|
|
1188
1807
|
overrideGlobalObjects: options.overrideGlobalObjects,
|
|
1189
1808
|
autoCleanupIncoming: options.autoCleanupIncoming
|
|
1190
1809
|
});
|
|
1191
|
-
return (options.createServer || createServer)(options.serverOptions || {}, requestListener);
|
|
1810
|
+
return (options.createServer || createServer$2)(options.serverOptions || {}, requestListener);
|
|
1192
1811
|
};
|
|
1193
1812
|
var serve = (options, listeningListener) => {
|
|
1194
1813
|
const server = createAdaptorServer(options);
|
|
@@ -1421,6 +2040,7 @@ async function devCommand(options) {
|
|
|
1421
2040
|
const runtime = config.runtime ?? "node";
|
|
1422
2041
|
let secretsJsonPath;
|
|
1423
2042
|
let publicUrl;
|
|
2043
|
+
let manifest;
|
|
1424
2044
|
const appSecrets = await loadSecretsForApp(workspace ? await secretsStoreFor(workspace, config.stages.local) : new FileSecretsStore(secretsRoot), config.stages.local, workspaceAppName);
|
|
1425
2045
|
if (workspace) {
|
|
1426
2046
|
const reconciled = await reconcileWorkspace(workspace, {
|
|
@@ -1435,6 +2055,7 @@ async function devCommand(options) {
|
|
|
1435
2055
|
Object.assign(appSecrets, reconciled.env);
|
|
1436
2056
|
const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && appKey(r.id) === workspaceAppName);
|
|
1437
2057
|
publicUrl = own ? reconciled.env[own.envKey] : void 0;
|
|
2058
|
+
manifest = reconciled.manifest;
|
|
1438
2059
|
}
|
|
1439
2060
|
if (Object.keys(appSecrets).length > 0) {
|
|
1440
2061
|
const secretsDir = join(secretsRoot, ".gkm");
|
|
@@ -1447,6 +2068,38 @@ async function devCommand(options) {
|
|
|
1447
2068
|
devServer.label = workspaceAppName ?? "server";
|
|
1448
2069
|
devServer.publicUrl = publicUrl;
|
|
1449
2070
|
await devServer.start();
|
|
2071
|
+
const discovery = await joinDiscovery({
|
|
2072
|
+
role: "app",
|
|
2073
|
+
port: discoveryPort(workspace.dev),
|
|
2074
|
+
workspace: {
|
|
2075
|
+
name: workspace.name,
|
|
2076
|
+
root: workspace.root,
|
|
2077
|
+
stage: workspace.stages.local
|
|
2078
|
+
},
|
|
2079
|
+
allowedOrigins: workspace.dev?.allowedOrigins ?? [],
|
|
2080
|
+
...manifest ? { manifest } : {},
|
|
2081
|
+
apps: [{
|
|
2082
|
+
name: workspaceAppName,
|
|
2083
|
+
type: "backend",
|
|
2084
|
+
port: devServer.port,
|
|
2085
|
+
...publicUrl ? { publicUrl } : {},
|
|
2086
|
+
status: "ready",
|
|
2087
|
+
reloads: 0,
|
|
2088
|
+
dataApis: dataApisOf({
|
|
2089
|
+
selfServing: devServer.selfServing,
|
|
2090
|
+
telescopePath: telescope?.path,
|
|
2091
|
+
databaseApi: initial.databaseApi,
|
|
2092
|
+
openApi: enableOpenApi
|
|
2093
|
+
})
|
|
2094
|
+
}]
|
|
2095
|
+
});
|
|
2096
|
+
if (!process.env["GKM_DISCOVERY_QUIET"]) announce(discovery);
|
|
2097
|
+
devServer.onStatus = (status) => discovery.updateApp(workspaceAppName, (app) => ({
|
|
2098
|
+
...app,
|
|
2099
|
+
status,
|
|
2100
|
+
port: devServer.port,
|
|
2101
|
+
reloads: app.status === "reloading" && status === "ready" ? app.reloads + 1 : app.reloads
|
|
2102
|
+
}));
|
|
1450
2103
|
const hooksFile = (config.hooks?.server?.split("#"))?.[0];
|
|
1451
2104
|
const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) => isAbsolute(p) ? relative(appRoot, p) : p.replace(/^\.\//, ""));
|
|
1452
2105
|
const resolvedFiles = await fg(normalizedPatterns, {
|
|
@@ -1487,7 +2140,11 @@ async function devCommand(options) {
|
|
|
1487
2140
|
if (isShuttingDown) return;
|
|
1488
2141
|
isShuttingDown = true;
|
|
1489
2142
|
logger$9.log("\n🛑 Shutting down...");
|
|
1490
|
-
Promise.all([
|
|
2143
|
+
Promise.all([
|
|
2144
|
+
watcher.close(),
|
|
2145
|
+
devServer.stop(),
|
|
2146
|
+
discovery.leave()
|
|
2147
|
+
]).catch((err) => {
|
|
1491
2148
|
logger$9.error("Error during shutdown:", err);
|
|
1492
2149
|
}).finally(() => {
|
|
1493
2150
|
process.exit(0);
|
|
@@ -1731,9 +2388,9 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1731
2388
|
for (const { id, port } of fakes) logger$9.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
1732
2389
|
logger$9.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
|
|
1733
2390
|
if (options.migrate || options.seed) {
|
|
1734
|
-
const { prepareDevDatabases } = await import("./migrate-
|
|
2391
|
+
const { prepareDevDatabases } = await import("./migrate-BrFREmgQ.mjs");
|
|
1735
2392
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1736
|
-
} else await import("./migrate-
|
|
2393
|
+
} else await import("./migrate-BrFREmgQ.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1737
2394
|
logger$9.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1738
2395
|
});
|
|
1739
2396
|
const appUrls = {};
|
|
@@ -1788,12 +2445,39 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1788
2445
|
break;
|
|
1789
2446
|
}
|
|
1790
2447
|
}
|
|
2448
|
+
const discovery = await joinDiscovery({
|
|
2449
|
+
role: "workspace",
|
|
2450
|
+
port: discoveryPort(workspace.dev),
|
|
2451
|
+
workspace: {
|
|
2452
|
+
name: workspace.name,
|
|
2453
|
+
root: workspace.root,
|
|
2454
|
+
stage: workspace.stages.local
|
|
2455
|
+
},
|
|
2456
|
+
allowedOrigins: workspace.dev?.allowedOrigins ?? [],
|
|
2457
|
+
...reconciled.manifest ? { manifest: reconciled.manifest } : {},
|
|
2458
|
+
apps: buildOrder.flatMap((name) => {
|
|
2459
|
+
const app = workspace.apps[name];
|
|
2460
|
+
if (!app) return [];
|
|
2461
|
+
const publicUrl = Object.entries(appUrls).find(([id]) => appKey(id) === name)?.[1];
|
|
2462
|
+
return [{
|
|
2463
|
+
name,
|
|
2464
|
+
type: app.type,
|
|
2465
|
+
port: app.port,
|
|
2466
|
+
...publicUrl ? { publicUrl } : {},
|
|
2467
|
+
status: "starting",
|
|
2468
|
+
reloads: 0,
|
|
2469
|
+
dataApis: []
|
|
2470
|
+
}];
|
|
2471
|
+
})
|
|
2472
|
+
});
|
|
2473
|
+
announce(discovery);
|
|
1791
2474
|
const turboEnv = {
|
|
1792
2475
|
...process.env,
|
|
1793
2476
|
...secretsEnv,
|
|
1794
2477
|
NODE_ENV: "development",
|
|
1795
2478
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {},
|
|
1796
|
-
...options.fake ? { [FAKE_ENV]: "1" } : {}
|
|
2479
|
+
...options.fake ? { [FAKE_ENV]: "1" } : {},
|
|
2480
|
+
[DISCOVERY_QUIET_ENV]: "1"
|
|
1797
2481
|
};
|
|
1798
2482
|
logger$9.log(`🚀 Starting ${appCount} app(s) — each builds before it answers, and says so when it is ready`);
|
|
1799
2483
|
const turboProcess = spawn("pnpm", [
|
|
@@ -1813,6 +2497,7 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1813
2497
|
isShuttingDown = true;
|
|
1814
2498
|
logger$9.log("\n🛑 Shutting down workspace...");
|
|
1815
2499
|
closeFakes(fakes);
|
|
2500
|
+
discovery.leave();
|
|
1816
2501
|
const pid = turboProcess.pid;
|
|
1817
2502
|
if (pid) try {
|
|
1818
2503
|
process.kill(-pid, "SIGTERM");
|
|
@@ -2053,6 +2738,13 @@ start({
|
|
|
2053
2738
|
});
|
|
2054
2739
|
`;
|
|
2055
2740
|
}
|
|
2741
|
+
/**
|
|
2742
|
+
* The discovery endpoint's connect URL, Jupyter-style: the token in it is what
|
|
2743
|
+
* lets a console read what this machine is running.
|
|
2744
|
+
*/
|
|
2745
|
+
function announce(discovery) {
|
|
2746
|
+
logger$9.log(`🧭 Discovery: ${discovery.connectUrl}`);
|
|
2747
|
+
}
|
|
2056
2748
|
/** Run `fn` with `console.log` muted — warnings and errors still print. */
|
|
2057
2749
|
async function quietly(fn) {
|
|
2058
2750
|
const log = console.log;
|
|
@@ -2150,6 +2842,12 @@ var DevServer = class {
|
|
|
2150
2842
|
}
|
|
2151
2843
|
/** What the ready line calls this app. */
|
|
2152
2844
|
label = "server";
|
|
2845
|
+
/** Told when the server is ready, reloading, or exits on its own. */
|
|
2846
|
+
onStatus;
|
|
2847
|
+
/** The port it is serving on. */
|
|
2848
|
+
get port() {
|
|
2849
|
+
return this.actualPort;
|
|
2850
|
+
}
|
|
2153
2851
|
/**
|
|
2154
2852
|
* Where the app is reached — its HTTPS address behind the edge, when it has
|
|
2155
2853
|
* one. The local port is only what the edge forwards to.
|
|
@@ -2185,9 +2883,11 @@ var DevServer = class {
|
|
|
2185
2883
|
this.serverProcess.on("error", (error) => {
|
|
2186
2884
|
logger$9.error("❌ Server error:", error);
|
|
2187
2885
|
});
|
|
2188
|
-
this.serverProcess
|
|
2886
|
+
const child = this.serverProcess;
|
|
2887
|
+
child.on("exit", (code, signal) => {
|
|
2189
2888
|
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$9.error(`❌ Server exited with code ${code}`);
|
|
2190
2889
|
this.isRunning = false;
|
|
2890
|
+
if (this.serverProcess === child) this.onStatus?.("stopped");
|
|
2191
2891
|
});
|
|
2192
2892
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
2193
2893
|
if (this.isRunning) {
|
|
@@ -2201,6 +2901,7 @@ var DevServer = class {
|
|
|
2201
2901
|
...this.databaseApi ? [`db ${this.databaseApi}`] : []
|
|
2202
2902
|
];
|
|
2203
2903
|
if (tools.length > 0) logger$9.log(` ${tools.join(" · ")}`);
|
|
2904
|
+
this.onStatus?.("ready");
|
|
2204
2905
|
}
|
|
2205
2906
|
}
|
|
2206
2907
|
async stop() {
|
|
@@ -2226,6 +2927,7 @@ var DevServer = class {
|
|
|
2226
2927
|
}
|
|
2227
2928
|
async restart() {
|
|
2228
2929
|
const portToReuse = this.actualPort;
|
|
2930
|
+
this.onStatus?.("reloading");
|
|
2229
2931
|
await this.stop();
|
|
2230
2932
|
let attempts = 0;
|
|
2231
2933
|
while (attempts < 30) {
|
|
@@ -3299,7 +4001,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3299
4001
|
let masterKey;
|
|
3300
4002
|
if (context.production?.bundle && !skipBundle) {
|
|
3301
4003
|
logger$8.log(`\n📦 Bundling production server...`);
|
|
3302
|
-
const { bundleServer } = await import("./bundler-
|
|
4004
|
+
const { bundleServer } = await import("./bundler-ouDdNvk9.mjs");
|
|
3303
4005
|
const allConstructs = [
|
|
3304
4006
|
...endpoints.map((e) => e.construct),
|
|
3305
4007
|
...functions.map((f) => f.construct),
|
|
@@ -4301,12 +5003,6 @@ function createEmptyState(stage, projectId, environmentId) {
|
|
|
4301
5003
|
};
|
|
4302
5004
|
}
|
|
4303
5005
|
/**
|
|
4304
|
-
* Get application ID from state
|
|
4305
|
-
*/
|
|
4306
|
-
function getApplicationId(state, appName) {
|
|
4307
|
-
return state?.applications[appName];
|
|
4308
|
-
}
|
|
4309
|
-
/**
|
|
4310
5006
|
* Set application ID in state (mutates state)
|
|
4311
5007
|
*/
|
|
4312
5008
|
function setApplicationId(state, appName, applicationId) {
|
|
@@ -4408,7 +5104,7 @@ async function createDnsProvider(options) {
|
|
|
4408
5104
|
if (isDnsProvider(config.provider)) return config.provider;
|
|
4409
5105
|
const provider = config.provider;
|
|
4410
5106
|
if (provider === "hostinger") {
|
|
4411
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
5107
|
+
const { HostingerProvider } = await import("./HostingerProvider-YQY3rMpV.mjs");
|
|
4412
5108
|
return new HostingerProvider();
|
|
4413
5109
|
}
|
|
4414
5110
|
if (provider === "route53") {
|
|
@@ -7189,6 +7885,113 @@ function applicationName(identity, app) {
|
|
|
7189
7885
|
return slug$1(app) === identity.project ? `${identity.stage}-${identity.scope}` : scopedName([identity.stage, identity.scope], app);
|
|
7190
7886
|
}
|
|
7191
7887
|
//#endregion
|
|
7888
|
+
//#region src/deploy/journal.ts
|
|
7889
|
+
var DeployJournal = class DeployJournal {
|
|
7890
|
+
store;
|
|
7891
|
+
stage;
|
|
7892
|
+
state;
|
|
7893
|
+
resources;
|
|
7894
|
+
version;
|
|
7895
|
+
existed;
|
|
7896
|
+
constructor(store, stage, state, resources, version, existed) {
|
|
7897
|
+
this.store = store;
|
|
7898
|
+
this.stage = stage;
|
|
7899
|
+
this.state = state;
|
|
7900
|
+
this.resources = resources;
|
|
7901
|
+
this.version = version;
|
|
7902
|
+
this.existed = existed;
|
|
7903
|
+
}
|
|
7904
|
+
/**
|
|
7905
|
+
* The stage's journal — its stored state, or `initial` written as the
|
|
7906
|
+
* stage's first state, so there is somewhere to record the very first
|
|
7907
|
+
* resource before it is created.
|
|
7908
|
+
*/
|
|
7909
|
+
static async open(store, stage, initial) {
|
|
7910
|
+
const stored = await store.read(stage);
|
|
7911
|
+
if (stored) return new DeployJournal(store, stage, stored.state, stored.resources, stored.version, true);
|
|
7912
|
+
const state = initial();
|
|
7913
|
+
const version = await store.write(stage, state, { expectedVersion: null });
|
|
7914
|
+
return new DeployJournal(store, stage, state, {}, version, false);
|
|
7915
|
+
}
|
|
7916
|
+
record(key) {
|
|
7917
|
+
return this.resources[key];
|
|
7918
|
+
}
|
|
7919
|
+
/** The keys a previous run marked `pending` and never saw created. */
|
|
7920
|
+
unfinished() {
|
|
7921
|
+
return Object.values(this.resources).filter((record) => record.status === "pending").map((record) => record.key);
|
|
7922
|
+
}
|
|
7923
|
+
/** Records that `entry` is about to be created. */
|
|
7924
|
+
async pending(entry) {
|
|
7925
|
+
await this.put({
|
|
7926
|
+
...entry,
|
|
7927
|
+
status: "pending"
|
|
7928
|
+
});
|
|
7929
|
+
}
|
|
7930
|
+
/** Records `entry` as existing with `id`; a no-op if it already is. */
|
|
7931
|
+
async ready(entry, id) {
|
|
7932
|
+
const current = this.resources[entry.key];
|
|
7933
|
+
if (current?.status === "ready" && current.id === id) return;
|
|
7934
|
+
await this.put({
|
|
7935
|
+
...entry,
|
|
7936
|
+
status: "ready",
|
|
7937
|
+
id
|
|
7938
|
+
});
|
|
7939
|
+
}
|
|
7940
|
+
/**
|
|
7941
|
+
* The resource `entry` names: by its recorded id, else by looking it up,
|
|
7942
|
+
* else created — with `pending` written before the create and `ready`
|
|
7943
|
+
* after, so a run that dies in between leaves a record the next run
|
|
7944
|
+
* resolves by looking the resource up rather than creating another.
|
|
7945
|
+
*/
|
|
7946
|
+
async ensure(entry, steps) {
|
|
7947
|
+
const record = this.resources[entry.key];
|
|
7948
|
+
let staleId;
|
|
7949
|
+
if (record?.status === "ready" && record.id && steps.get) {
|
|
7950
|
+
const known = await steps.get(record.id);
|
|
7951
|
+
if (known) {
|
|
7952
|
+
await this.ready(entry, steps.id(known));
|
|
7953
|
+
return {
|
|
7954
|
+
resource: known,
|
|
7955
|
+
via: "recorded"
|
|
7956
|
+
};
|
|
7957
|
+
}
|
|
7958
|
+
staleId = record.id;
|
|
7959
|
+
}
|
|
7960
|
+
const existing = await steps.find();
|
|
7961
|
+
if (existing) {
|
|
7962
|
+
await this.ready(entry, steps.id(existing));
|
|
7963
|
+
return {
|
|
7964
|
+
resource: existing,
|
|
7965
|
+
via: record?.status === "pending" ? "resumed" : "found",
|
|
7966
|
+
...staleId ? { staleId } : {}
|
|
7967
|
+
};
|
|
7968
|
+
}
|
|
7969
|
+
await this.pending(entry);
|
|
7970
|
+
const created = await steps.create();
|
|
7971
|
+
await this.ready(entry, steps.id(created));
|
|
7972
|
+
return {
|
|
7973
|
+
resource: created,
|
|
7974
|
+
via: "created",
|
|
7975
|
+
...staleId ? { staleId } : {}
|
|
7976
|
+
};
|
|
7977
|
+
}
|
|
7978
|
+
/** Writes the stage's state, keeping its resource records. */
|
|
7979
|
+
async save() {
|
|
7980
|
+
this.state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7981
|
+
this.version = await this.store.write(this.stage, this.state, { expectedVersion: this.version });
|
|
7982
|
+
}
|
|
7983
|
+
async put(record) {
|
|
7984
|
+
this.version = await this.store.putResource(this.stage, record, { expectedVersion: this.version });
|
|
7985
|
+
this.resources = {
|
|
7986
|
+
...this.resources,
|
|
7987
|
+
[record.key]: {
|
|
7988
|
+
...record,
|
|
7989
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7990
|
+
}
|
|
7991
|
+
};
|
|
7992
|
+
}
|
|
7993
|
+
};
|
|
7994
|
+
//#endregion
|
|
7192
7995
|
//#region src/deploy/ownership.ts
|
|
7193
7996
|
/**
|
|
7194
7997
|
* Which Dokploy project a deploy is allowed to deploy into.
|
|
@@ -7244,7 +8047,7 @@ async function projectById(api, projectId) {
|
|
|
7244
8047
|
* A project that matches by name (in any case) but carries no marker, or
|
|
7245
8048
|
* another identity's, raises `ProjectNotOwned` — it is never adopted.
|
|
7246
8049
|
*/
|
|
7247
|
-
async function resolveProject(api, identity, stateProjectId, log = () => {}) {
|
|
8050
|
+
async function resolveProject(api, identity, stateProjectId, log = () => {}, hooks = {}) {
|
|
7248
8051
|
const name = projectName(identity);
|
|
7249
8052
|
const marker = ownershipMarker(identity);
|
|
7250
8053
|
if (stateProjectId) {
|
|
@@ -7280,6 +8083,7 @@ async function resolveProject(api, identity, stateProjectId, log = () => {}) {
|
|
|
7280
8083
|
}
|
|
7281
8084
|
const clash = projects.find((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
7282
8085
|
if (clash) throw new ProjectNotOwned(clash.name, clash.projectId, identity.key, markerOf(clash.description));
|
|
8086
|
+
await hooks.beforeCreate?.();
|
|
7283
8087
|
const created = await api.createProject(name, withMarker("Deployed by gkm.", marker));
|
|
7284
8088
|
return {
|
|
7285
8089
|
projectId: created.project.projectId,
|
|
@@ -7382,65 +8186,6 @@ async function resolveRegistry(api, options) {
|
|
|
7382
8186
|
return options.create(registry);
|
|
7383
8187
|
}
|
|
7384
8188
|
//#endregion
|
|
7385
|
-
//#region src/deploy/StateProvider.ts
|
|
7386
|
-
/**
|
|
7387
|
-
* Check if value is a StateProvider implementation.
|
|
7388
|
-
*/
|
|
7389
|
-
function isStateProvider(value) {
|
|
7390
|
-
return typeof value === "object" && value !== null && typeof value.read === "function" && typeof value.write === "function";
|
|
7391
|
-
}
|
|
7392
|
-
/**
|
|
7393
|
-
* Create a state provider based on configuration.
|
|
7394
|
-
*
|
|
7395
|
-
* - 'local': LocalStateProvider (default)
|
|
7396
|
-
* - 'ssm': CachedStateProvider with SSM as source of truth
|
|
7397
|
-
* - 's3': S3StateStore behind the provider interface
|
|
7398
|
-
* - Custom: Use provided StateProvider implementation
|
|
7399
|
-
*
|
|
7400
|
-
* Deploy moves to `createStateStore` (locks, versions, per-resource records);
|
|
7401
|
-
* this stays for the call sites that read and write whole states.
|
|
7402
|
-
*/
|
|
7403
|
-
async function createStateProvider(options) {
|
|
7404
|
-
const { config, workspaceRoot, workspaceName } = options;
|
|
7405
|
-
if (!config) {
|
|
7406
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-D7QyJoLM.mjs");
|
|
7407
|
-
return new LocalStateProvider(workspaceRoot);
|
|
7408
|
-
}
|
|
7409
|
-
const { isStateStore } = await Promise.resolve().then(() => StateStore_exports);
|
|
7410
|
-
if (isStateStore(config.provider)) {
|
|
7411
|
-
const { StateStoreProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7412
|
-
return new StateStoreProvider(config.provider);
|
|
7413
|
-
}
|
|
7414
|
-
if (isStateProvider(config.provider)) return config.provider;
|
|
7415
|
-
const provider = config.provider;
|
|
7416
|
-
if (provider === "local") {
|
|
7417
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-D7QyJoLM.mjs");
|
|
7418
|
-
return new LocalStateProvider(workspaceRoot);
|
|
7419
|
-
}
|
|
7420
|
-
if (provider === "ssm") {
|
|
7421
|
-
if (!workspaceName) {
|
|
7422
|
-
const { StateStoreNeedsWorkspaceName } = await Promise.resolve().then(() => StateStore_exports);
|
|
7423
|
-
throw new StateStoreNeedsWorkspaceName("ssm");
|
|
7424
|
-
}
|
|
7425
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-D7QyJoLM.mjs");
|
|
7426
|
-
const { SSMStateProvider } = await import("./SSMStateProvider-B0Hu1MKZ.mjs");
|
|
7427
|
-
const { CachedStateProvider } = await Promise.resolve().then(() => CachedStateProvider_exports);
|
|
7428
|
-
const ssmConfig = config;
|
|
7429
|
-
const local = new LocalStateProvider(workspaceRoot);
|
|
7430
|
-
return new CachedStateProvider(SSMStateProvider.create({
|
|
7431
|
-
workspaceName,
|
|
7432
|
-
region: ssmConfig.region,
|
|
7433
|
-
profile: ssmConfig.profile
|
|
7434
|
-
}), local);
|
|
7435
|
-
}
|
|
7436
|
-
if (provider === "s3") {
|
|
7437
|
-
const { createStateStore, StateStoreProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7438
|
-
return new StateStoreProvider(await createStateStore(options));
|
|
7439
|
-
}
|
|
7440
|
-
const { UnknownStateProvider } = await Promise.resolve().then(() => StateStore_exports);
|
|
7441
|
-
throw new UnknownStateProvider(config);
|
|
7442
|
-
}
|
|
7443
|
-
//#endregion
|
|
7444
8189
|
//#region src/deploy/secrets.ts
|
|
7445
8190
|
/**
|
|
7446
8191
|
* Filter secrets to only include the env vars that an app requires.
|
|
@@ -8181,6 +8926,28 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8181
8926
|
});
|
|
8182
8927
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
8183
8928
|
appsToDeployNames = dokployApps;
|
|
8929
|
+
const store = await createStateStore({
|
|
8930
|
+
config: workspace.state,
|
|
8931
|
+
workspaceRoot: workspace.root,
|
|
8932
|
+
workspaceName: workspace.name
|
|
8933
|
+
});
|
|
8934
|
+
const lock = await store.lock(stage, { operation: "deploy" });
|
|
8935
|
+
try {
|
|
8936
|
+
return await deployLocked({
|
|
8937
|
+
workspace,
|
|
8938
|
+
manifest,
|
|
8939
|
+
stage,
|
|
8940
|
+
imageTag,
|
|
8941
|
+
appsToDeployNames,
|
|
8942
|
+
identity,
|
|
8943
|
+
store
|
|
8944
|
+
});
|
|
8945
|
+
} finally {
|
|
8946
|
+
await lock.release();
|
|
8947
|
+
}
|
|
8948
|
+
}
|
|
8949
|
+
/** Everything a deploy does while it holds the stage's lock. */
|
|
8950
|
+
async function deployLocked({ workspace, manifest, stage, imageTag, appsToDeployNames, identity, store }) {
|
|
8184
8951
|
logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
8185
8952
|
const secretsStore = await secretsStoreFor(workspace, stage);
|
|
8186
8953
|
const stored = await secretsStore.read(stage);
|
|
@@ -8222,42 +8989,48 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8222
8989
|
token: creds.token
|
|
8223
8990
|
});
|
|
8224
8991
|
logger$4.log("\n📋 Loading deploy state...");
|
|
8225
|
-
const
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8992
|
+
const journal = await DeployJournal.open(store, stage, () => createEmptyState(stage, "", ""));
|
|
8993
|
+
const state = journal.state;
|
|
8994
|
+
if (journal.existed) logger$4.log(` Found existing state for stage "${stage}"`);
|
|
8995
|
+
else logger$4.log(` Creating new state for stage "${stage}"`);
|
|
8996
|
+
const unfinished = journal.unfinished();
|
|
8997
|
+
if (unfinished.length > 0) logger$4.log(` ⚠ A previous run stopped while creating: ${unfinished.join(", ")} — looking for them before creating anything`);
|
|
8231
8998
|
logger$4.log("\n📁 Setting up Dokploy project...");
|
|
8232
8999
|
logger$4.log(` Identity: ${identity.key}`);
|
|
8233
|
-
const
|
|
9000
|
+
const projectEntry = {
|
|
9001
|
+
key: "project",
|
|
9002
|
+
type: "project",
|
|
9003
|
+
data: {
|
|
9004
|
+
name: projectName(identity),
|
|
9005
|
+
identity: identity.key
|
|
9006
|
+
}
|
|
9007
|
+
};
|
|
9008
|
+
const project = await resolveProject(api, identity, state.projectId || void 0, (message) => logger$4.log(message), { beforeCreate: () => journal.pending(projectEntry) });
|
|
9009
|
+
await journal.ready(projectEntry, project.projectId);
|
|
8234
9010
|
if (project.via === "created") logger$4.log(` ✓ Created project: ${project.projectId}`);
|
|
8235
9011
|
else logger$4.log(` Found existing project: ${project.name}`);
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
if (state.
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
logger$4.log(` Creating new state for stage "${stage}"`);
|
|
8259
|
-
state = createEmptyState(stage, project.projectId, environmentId);
|
|
8260
|
-
}
|
|
9012
|
+
const environmentEntry = {
|
|
9013
|
+
key: "environment",
|
|
9014
|
+
type: "environment",
|
|
9015
|
+
data: { name: stage }
|
|
9016
|
+
};
|
|
9017
|
+
const environment = await journal.ensure(environmentEntry, {
|
|
9018
|
+
find: async () => project.environments.find((e) => e.name.toLowerCase() === stage.toLowerCase()),
|
|
9019
|
+
create: () => {
|
|
9020
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
9021
|
+
return api.createEnvironment(project.projectId, stage);
|
|
9022
|
+
},
|
|
9023
|
+
id: (e) => e.environmentId
|
|
9024
|
+
});
|
|
9025
|
+
const environmentId = environment.resource.environmentId;
|
|
9026
|
+
if (environment.via === "created") logger$4.log(` ✓ Created environment: ${stage}`);
|
|
9027
|
+
else logger$4.log(` Using environment: ${environment.resource.name}`);
|
|
9028
|
+
if (journal.existed) {
|
|
9029
|
+
if (state.projectId && state.projectId !== project.projectId) logger$4.log(` ⚠ Project ID changed, updating state`);
|
|
9030
|
+
if (state.environmentId && state.environmentId !== environmentId) logger$4.log(` ⚠ Environment ID changed, updating state`);
|
|
9031
|
+
}
|
|
9032
|
+
state.projectId = project.projectId;
|
|
9033
|
+
state.environmentId = environmentId;
|
|
8261
9034
|
state.identity = identity.key;
|
|
8262
9035
|
logger$4.log("\n🐳 Checking registry...");
|
|
8263
9036
|
const registry = workspace.deploy.dokploy?.registry;
|
|
@@ -8279,6 +9052,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8279
9052
|
const registryId = dokployRegistry.registryId;
|
|
8280
9053
|
state.registryId = registryId;
|
|
8281
9054
|
logger$4.log(` Using registry: ${dokployRegistry.registryName}`);
|
|
9055
|
+
await journal.save();
|
|
8282
9056
|
const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
|
|
8283
9057
|
if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
8284
9058
|
const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
|
|
@@ -8336,7 +9110,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8336
9110
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
8337
9111
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
8338
9112
|
logger$4.log(" Creating postgres backup schedule...");
|
|
8339
|
-
|
|
9113
|
+
setPostgresBackupId(state, (await api.createPostgresBackup({
|
|
8340
9114
|
schedule: backupSchedule,
|
|
8341
9115
|
prefix: `${stage}/postgres`,
|
|
8342
9116
|
destinationId: backupState.destinationId,
|
|
@@ -8344,8 +9118,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8344
9118
|
postgresId: backupCluster.postgresId,
|
|
8345
9119
|
enabled: true,
|
|
8346
9120
|
keepLatestCount: backupRetention
|
|
8347
|
-
});
|
|
8348
|
-
setPostgresBackupId(state, backup.backupId);
|
|
9121
|
+
})).backupId);
|
|
8349
9122
|
logger$4.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
8350
9123
|
} else logger$4.log(" ✓ Using existing postgres backup schedule");
|
|
8351
9124
|
}
|
|
@@ -8366,22 +9139,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8366
9139
|
const app = workspace.apps[appName];
|
|
8367
9140
|
logger$4.log(`\n ⚙️ Deploying ${appName}...`);
|
|
8368
9141
|
try {
|
|
8369
|
-
const
|
|
8370
|
-
let application = null;
|
|
8371
|
-
const cachedAppId = getApplicationId(state, appName);
|
|
8372
|
-
if (cachedAppId) {
|
|
8373
|
-
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
8374
|
-
application = await api.getApplication(cachedAppId);
|
|
8375
|
-
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
8376
|
-
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
8377
|
-
}
|
|
8378
|
-
if (!application) {
|
|
8379
|
-
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
8380
|
-
application = result.application;
|
|
8381
|
-
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
8382
|
-
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
8383
|
-
}
|
|
8384
|
-
setApplicationId(state, appName, application.applicationId);
|
|
9142
|
+
const application = await ensureApplication(api, journal, appName, applicationName(identity, appName), project.projectId, environmentId);
|
|
8385
9143
|
const appSecrets = encryptedSecrets.get(appName);
|
|
8386
9144
|
const credentials = appSecrets && appSecrets.secretCount > 0 ? {
|
|
8387
9145
|
encrypted: appSecrets.payload.encrypted,
|
|
@@ -8405,6 +9163,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8405
9163
|
ref: built.imageRef ?? imageRef$1,
|
|
8406
9164
|
...built.digest ? { digest: built.digest } : {}
|
|
8407
9165
|
});
|
|
9166
|
+
await journal.save();
|
|
8408
9167
|
const backendHost = resolveHost(appName, app, stage, workspace.deploy?.domains, false);
|
|
8409
9168
|
const dependencyUrls = {};
|
|
8410
9169
|
if (app.dependencies) {
|
|
@@ -8439,30 +9198,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8439
9198
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
8440
9199
|
logger$4.log(` Deploying to Dokploy...`);
|
|
8441
9200
|
await api.deployApplication(application.applicationId);
|
|
8442
|
-
const
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
publicUrls[appName] = `https://${backendHost}`;
|
|
8447
|
-
logger$4.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
8448
|
-
} else try {
|
|
8449
|
-
const domain = await api.createDomain({
|
|
8450
|
-
host: backendHost,
|
|
8451
|
-
port: app.port,
|
|
8452
|
-
https: true,
|
|
8453
|
-
certificateType: "letsencrypt",
|
|
8454
|
-
applicationId: application.applicationId
|
|
8455
|
-
});
|
|
8456
|
-
appHostnames.set(appName, backendHost);
|
|
8457
|
-
appDomainIds.set(appName, domain.domainId);
|
|
8458
|
-
publicUrls[appName] = `https://${backendHost}`;
|
|
8459
|
-
logger$4.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
8460
|
-
} catch (domainError) {
|
|
8461
|
-
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
8462
|
-
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
8463
|
-
appHostnames.set(appName, backendHost);
|
|
8464
|
-
publicUrls[appName] = `https://${backendHost}`;
|
|
8465
|
-
}
|
|
9201
|
+
const domainId = await ensureDomain(api, journal, backendHost, app.port, application.applicationId);
|
|
9202
|
+
appHostnames.set(appName, backendHost);
|
|
9203
|
+
if (domainId) appDomainIds.set(appName, domainId);
|
|
9204
|
+
publicUrls[appName] = `https://${backendHost}`;
|
|
8466
9205
|
results.push({
|
|
8467
9206
|
appName,
|
|
8468
9207
|
type: app.type,
|
|
@@ -8491,22 +9230,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8491
9230
|
const app = workspace.apps[appName];
|
|
8492
9231
|
logger$4.log(`\n 🌐 Deploying ${appName}...`);
|
|
8493
9232
|
try {
|
|
8494
|
-
const
|
|
8495
|
-
let application = null;
|
|
8496
|
-
const cachedAppId = getApplicationId(state, appName);
|
|
8497
|
-
if (cachedAppId) {
|
|
8498
|
-
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
8499
|
-
application = await api.getApplication(cachedAppId);
|
|
8500
|
-
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
8501
|
-
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
8502
|
-
}
|
|
8503
|
-
if (!application) {
|
|
8504
|
-
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
8505
|
-
application = result.application;
|
|
8506
|
-
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
8507
|
-
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
8508
|
-
}
|
|
8509
|
-
setApplicationId(state, appName, application.applicationId);
|
|
9233
|
+
const application = await ensureApplication(api, journal, appName, applicationName(identity, appName), project.projectId, environmentId);
|
|
8510
9234
|
const dependencyUrls = {};
|
|
8511
9235
|
if (app.dependencies) {
|
|
8512
9236
|
for (const dep of app.dependencies) if (publicUrls[dep]) dependencyUrls[dep] = publicUrls[dep];
|
|
@@ -8554,6 +9278,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8554
9278
|
ref: built.imageRef ?? imageRef$2,
|
|
8555
9279
|
...built.digest ? { digest: built.digest } : {}
|
|
8556
9280
|
});
|
|
9281
|
+
await journal.save();
|
|
8557
9282
|
const envVars = [
|
|
8558
9283
|
`NODE_ENV=production`,
|
|
8559
9284
|
`PORT=${app.port}`,
|
|
@@ -8564,30 +9289,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8564
9289
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
8565
9290
|
logger$4.log(` Deploying to Dokploy...`);
|
|
8566
9291
|
await api.deployApplication(application.applicationId);
|
|
8567
|
-
const
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
publicUrls[appName] = `https://${frontendHost}`;
|
|
8572
|
-
logger$4.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
8573
|
-
} else try {
|
|
8574
|
-
const domain = await api.createDomain({
|
|
8575
|
-
host: frontendHost,
|
|
8576
|
-
port: app.port,
|
|
8577
|
-
https: true,
|
|
8578
|
-
certificateType: "letsencrypt",
|
|
8579
|
-
applicationId: application.applicationId
|
|
8580
|
-
});
|
|
8581
|
-
appHostnames.set(appName, frontendHost);
|
|
8582
|
-
appDomainIds.set(appName, domain.domainId);
|
|
8583
|
-
publicUrls[appName] = `https://${frontendHost}`;
|
|
8584
|
-
logger$4.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
8585
|
-
} catch (domainError) {
|
|
8586
|
-
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
8587
|
-
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
8588
|
-
appHostnames.set(appName, frontendHost);
|
|
8589
|
-
publicUrls[appName] = `https://${frontendHost}`;
|
|
8590
|
-
}
|
|
9292
|
+
const domainId = await ensureDomain(api, journal, frontendHost, app.port, application.applicationId);
|
|
9293
|
+
appHostnames.set(appName, frontendHost);
|
|
9294
|
+
if (domainId) appDomainIds.set(appName, domainId);
|
|
9295
|
+
publicUrls[appName] = `https://${frontendHost}`;
|
|
8591
9296
|
results.push({
|
|
8592
9297
|
appName,
|
|
8593
9298
|
type: app.type,
|
|
@@ -8610,14 +9315,14 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8610
9315
|
}
|
|
8611
9316
|
}
|
|
8612
9317
|
logger$4.log("\n📋 Saving deploy state...");
|
|
8613
|
-
await
|
|
9318
|
+
await journal.save();
|
|
8614
9319
|
logger$4.log(" ✓ State saved");
|
|
8615
9320
|
const dnsConfig = workspace.deploy.dns;
|
|
8616
9321
|
if (dnsConfig && appHostnames.size > 0) {
|
|
8617
9322
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
8618
9323
|
if (dnsResult?.serverIp && appHostnames.size > 0) {
|
|
8619
9324
|
await verifyDnsRecords(appHostnames, dnsResult.serverIp, state);
|
|
8620
|
-
await
|
|
9325
|
+
await journal.save();
|
|
8621
9326
|
}
|
|
8622
9327
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
8623
9328
|
logger$4.log("\n🔒 Validating domains for SSL certificates...");
|
|
@@ -8650,6 +9355,71 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
8650
9355
|
};
|
|
8651
9356
|
}
|
|
8652
9357
|
/**
|
|
9358
|
+
* An app's Dokploy application, through the journal: by the id recorded for
|
|
9359
|
+
* it, else by its name in the stage's environment, else created. Its id goes
|
|
9360
|
+
* into `state.applications` too, which the env resolver and `state:show` read.
|
|
9361
|
+
*/
|
|
9362
|
+
async function ensureApplication(api, journal, appName, dokployAppName, projectId, environmentId) {
|
|
9363
|
+
const recorded = journal.record(`application:${appName}`);
|
|
9364
|
+
if (recorded?.status === "ready" && recorded.id) logger$4.log(` Using cached ID: ${recorded.id}`);
|
|
9365
|
+
const { resource, via, staleId } = await journal.ensure({
|
|
9366
|
+
key: `application:${appName}`,
|
|
9367
|
+
type: "application",
|
|
9368
|
+
data: { name: dokployAppName }
|
|
9369
|
+
}, {
|
|
9370
|
+
get: (id) => api.getApplication(id),
|
|
9371
|
+
find: () => api.findApplicationByName(projectId, dokployAppName, environmentId),
|
|
9372
|
+
create: () => api.createApplication(dokployAppName, projectId, environmentId),
|
|
9373
|
+
id: (application) => application.applicationId
|
|
9374
|
+
});
|
|
9375
|
+
if (staleId) logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
9376
|
+
logger$4.log(` ${{
|
|
9377
|
+
recorded: "✓ Application found",
|
|
9378
|
+
resumed: "Resumed application a stopped run created",
|
|
9379
|
+
found: "Found existing application",
|
|
9380
|
+
created: "Created application"
|
|
9381
|
+
}[via]}: ${resource.applicationId}`);
|
|
9382
|
+
setApplicationId(journal.state, appName, resource.applicationId);
|
|
9383
|
+
return resource;
|
|
9384
|
+
}
|
|
9385
|
+
/**
|
|
9386
|
+
* The domain `host` routes to an application, through the journal — found on
|
|
9387
|
+
* the application before it is created, so a re-run never adds a second.
|
|
9388
|
+
*
|
|
9389
|
+
* A domain that cannot be created is reported and skipped rather than
|
|
9390
|
+
* failing the app: the application is deployed and reachable on Dokploy's
|
|
9391
|
+
* own address, and DNS is fixed by hand more easily than a half deploy.
|
|
9392
|
+
*/
|
|
9393
|
+
async function ensureDomain(api, journal, host, port, applicationId) {
|
|
9394
|
+
try {
|
|
9395
|
+
const { resource, via } = await journal.ensure({
|
|
9396
|
+
key: `domain:${host}`,
|
|
9397
|
+
type: "domain",
|
|
9398
|
+
data: {
|
|
9399
|
+
host,
|
|
9400
|
+
applicationId
|
|
9401
|
+
}
|
|
9402
|
+
}, {
|
|
9403
|
+
find: async () => (await api.getDomainsByApplicationId(applicationId)).find((d) => d.host === host),
|
|
9404
|
+
create: () => api.createDomain({
|
|
9405
|
+
host,
|
|
9406
|
+
port,
|
|
9407
|
+
https: true,
|
|
9408
|
+
certificateType: "letsencrypt",
|
|
9409
|
+
applicationId
|
|
9410
|
+
}),
|
|
9411
|
+
id: (domain) => domain.domainId
|
|
9412
|
+
});
|
|
9413
|
+
logger$4.log(` ✓ Domain: https://${host} (${via === "created" ? "created" : "existing"})`);
|
|
9414
|
+
return resource.domainId;
|
|
9415
|
+
} catch (domainError) {
|
|
9416
|
+
if (domainError instanceof StateVersionConflict || domainError instanceof StateStoreBusy) throw domainError;
|
|
9417
|
+
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
9418
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
9419
|
+
return;
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
/**
|
|
8653
9423
|
* Main deploy command
|
|
8654
9424
|
*/
|
|
8655
9425
|
async function deployCommand(options) {
|
|
@@ -8786,453 +9556,160 @@ async function deployListCommand(options) {
|
|
|
8786
9556
|
}
|
|
8787
9557
|
}
|
|
8788
9558
|
//#endregion
|
|
8789
|
-
//#region src/deploy/
|
|
8790
|
-
var CachedStateProvider_exports = /* @__PURE__ */ __exportAll({ CachedStateProvider: () => CachedStateProvider });
|
|
8791
|
-
/**
|
|
8792
|
-
* Cached state provider that wraps a remote provider with local cache.
|
|
8793
|
-
*
|
|
8794
|
-
* @deprecated for deploys: a read prefers the local copy, so a stale one wins
|
|
8795
|
-
* over the remote without warning, and a write overwrites the remote blindly.
|
|
8796
|
-
* Deploy uses `createStateStore`, which reads SSM directly and writes
|
|
8797
|
-
* conditionally. Kept for `gkm state:pull | push | diff`.
|
|
8798
|
-
*/
|
|
8799
|
-
var CachedStateProvider = class {
|
|
8800
|
-
remote;
|
|
8801
|
-
local;
|
|
8802
|
-
constructor(remote, local) {
|
|
8803
|
-
this.remote = remote;
|
|
8804
|
-
this.local = local;
|
|
8805
|
-
}
|
|
8806
|
-
async read(stage) {
|
|
8807
|
-
const localState = await this.local.read(stage);
|
|
8808
|
-
if (localState) return localState;
|
|
8809
|
-
const remoteState = await this.remote.read(stage);
|
|
8810
|
-
if (remoteState) await this.local.write(stage, remoteState);
|
|
8811
|
-
return remoteState;
|
|
8812
|
-
}
|
|
8813
|
-
async write(stage, state) {
|
|
8814
|
-
await this.remote.write(stage, state);
|
|
8815
|
-
await this.local.write(stage, state);
|
|
8816
|
-
}
|
|
8817
|
-
/**
|
|
8818
|
-
* Force pull from remote to local.
|
|
8819
|
-
* Used by `gkm state pull` command.
|
|
8820
|
-
*/
|
|
8821
|
-
async pull(stage) {
|
|
8822
|
-
const remoteState = await this.remote.read(stage);
|
|
8823
|
-
if (remoteState) await this.local.write(stage, remoteState);
|
|
8824
|
-
return remoteState;
|
|
8825
|
-
}
|
|
8826
|
-
/**
|
|
8827
|
-
* Force push from local to remote.
|
|
8828
|
-
* Used by `gkm state push` command.
|
|
8829
|
-
*/
|
|
8830
|
-
async push(stage) {
|
|
8831
|
-
const localState = await this.local.read(stage);
|
|
8832
|
-
if (localState) await this.remote.write(stage, localState);
|
|
8833
|
-
return localState;
|
|
8834
|
-
}
|
|
8835
|
-
/**
|
|
8836
|
-
* Get both local and remote state for comparison.
|
|
8837
|
-
* Used by `gkm state diff` command.
|
|
8838
|
-
*/
|
|
8839
|
-
async diff(stage) {
|
|
8840
|
-
const [local, remote] = await Promise.all([this.local.read(stage), this.remote.read(stage)]);
|
|
8841
|
-
return {
|
|
8842
|
-
local,
|
|
8843
|
-
remote
|
|
8844
|
-
};
|
|
8845
|
-
}
|
|
8846
|
-
};
|
|
8847
|
-
//#endregion
|
|
8848
|
-
//#region src/deploy/StateStore.ts
|
|
9559
|
+
//#region src/deploy/LocalStateStore.ts
|
|
8849
9560
|
/**
|
|
8850
|
-
*
|
|
8851
|
-
*
|
|
8852
|
-
* `StateProvider` reads and writes a whole stage at once, with nothing to stop
|
|
8853
|
-
* two runs of the same stage from overwriting each other and nowhere to record
|
|
8854
|
-
* a resource the moment it exists. A `StateStore` adds the three things a
|
|
8855
|
-
* deploy needs to be safe against crashes and concurrent runs:
|
|
9561
|
+
* Local filesystem state store.
|
|
8856
9562
|
*
|
|
8857
|
-
*
|
|
8858
|
-
*
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
*
|
|
8865
|
-
* Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A
|
|
8866
|
-
* custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it
|
|
8867
|
-
* working but cannot lock.
|
|
8868
|
-
*/
|
|
8869
|
-
var StateStore_exports = /* @__PURE__ */ __exportAll({
|
|
8870
|
-
DocumentStateStore: () => DocumentStateStore,
|
|
8871
|
-
STATE_SCHEMA_VERSION: () => 2,
|
|
8872
|
-
StageStateMissing: () => StageStateMissing,
|
|
8873
|
-
StateLocked: () => StateLocked,
|
|
8874
|
-
StateSchemaTooNew: () => StateSchemaTooNew,
|
|
8875
|
-
StateStoreBusy: () => StateStoreBusy,
|
|
8876
|
-
StateStoreNeedsWorkspaceName: () => StateStoreNeedsWorkspaceName,
|
|
8877
|
-
StateStoreProvider: () => StateStoreProvider,
|
|
8878
|
-
StateStoreWithoutLocking: () => StateStoreWithoutLocking,
|
|
8879
|
-
StateUnreadable: () => StateUnreadable,
|
|
8880
|
-
StateVersionConflict: () => StateVersionConflict,
|
|
8881
|
-
UnknownStateProvider: () => UnknownStateProvider,
|
|
8882
|
-
contentVersion: () => contentVersion,
|
|
8883
|
-
createStateStore: () => createStateStore,
|
|
8884
|
-
decodeDocument: () => decodeDocument,
|
|
8885
|
-
encodeDocument: () => encodeDocument,
|
|
8886
|
-
isStateStore: () => isStateStore,
|
|
8887
|
-
migrateV1: () => migrateV1,
|
|
8888
|
-
newLockHolder: () => newLockHolder
|
|
8889
|
-
});
|
|
8890
|
-
var StateLocked = class extends Error {
|
|
8891
|
-
stage;
|
|
8892
|
-
holder;
|
|
8893
|
-
location;
|
|
8894
|
-
constructor(stage, holder, location) {
|
|
8895
|
-
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";
|
|
8896
|
-
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}\`.`);
|
|
8897
|
-
this.stage = stage;
|
|
8898
|
-
this.holder = holder;
|
|
8899
|
-
this.location = location;
|
|
8900
|
-
this.name = "StateLocked";
|
|
8901
|
-
}
|
|
8902
|
-
};
|
|
8903
|
-
var StateVersionConflict = class extends Error {
|
|
8904
|
-
stage;
|
|
8905
|
-
expectedVersion;
|
|
8906
|
-
actualVersion;
|
|
8907
|
-
constructor(stage, expectedVersion, actualVersion) {
|
|
8908
|
-
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.`);
|
|
8909
|
-
this.stage = stage;
|
|
8910
|
-
this.expectedVersion = expectedVersion;
|
|
8911
|
-
this.actualVersion = actualVersion;
|
|
8912
|
-
this.name = "StateVersionConflict";
|
|
8913
|
-
}
|
|
8914
|
-
};
|
|
8915
|
-
var StageStateMissing = class extends Error {
|
|
8916
|
-
stage;
|
|
8917
|
-
constructor(stage) {
|
|
8918
|
-
super(`There is no deploy state for stage '${stage}' to record a resource in. Write the stage's state first (write with expectedVersion: null).`);
|
|
8919
|
-
this.stage = stage;
|
|
8920
|
-
this.name = "StageStateMissing";
|
|
8921
|
-
}
|
|
8922
|
-
};
|
|
8923
|
-
var StateUnreadable = class extends Error {
|
|
8924
|
-
stage;
|
|
8925
|
-
location;
|
|
8926
|
-
constructor(stage, location, cause) {
|
|
8927
|
-
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 });
|
|
8928
|
-
this.stage = stage;
|
|
8929
|
-
this.location = location;
|
|
8930
|
-
this.name = "StateUnreadable";
|
|
8931
|
-
}
|
|
8932
|
-
};
|
|
8933
|
-
var StateSchemaTooNew = class extends Error {
|
|
8934
|
-
stage;
|
|
8935
|
-
schemaVersion;
|
|
8936
|
-
constructor(stage, schemaVersion) {
|
|
8937
|
-
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.`);
|
|
8938
|
-
this.stage = stage;
|
|
8939
|
-
this.schemaVersion = schemaVersion;
|
|
8940
|
-
this.name = "StateSchemaTooNew";
|
|
8941
|
-
}
|
|
8942
|
-
};
|
|
9563
|
+
* `.gkm/deploy-<stage>.json` holds the state, `.gkm/deploy-<stage>.lock` the
|
|
9564
|
+
* lock. Every file is mode 0600: state holds database passwords and IAM keys.
|
|
9565
|
+
*/
|
|
9566
|
+
var LocalStateStore_exports = /* @__PURE__ */ __exportAll({ LocalStateStore: () => LocalStateStore });
|
|
9567
|
+
const FILE_MODE = 384;
|
|
9568
|
+
/** How long a write waits for another process's write to finish. */
|
|
9569
|
+
const MUTEX_TIMEOUT_MS = 5e3;
|
|
8943
9570
|
/**
|
|
8944
|
-
*
|
|
8945
|
-
*
|
|
8946
|
-
* says so.
|
|
9571
|
+
* A write holds the mutex for milliseconds, so one this old was left by a
|
|
9572
|
+
* process that died mid-write and is safe to take over.
|
|
8947
9573
|
*/
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
9574
|
+
const MUTEX_STALE_MS = 3e4;
|
|
9575
|
+
function isCode(error, code) {
|
|
9576
|
+
return error?.code === code;
|
|
9577
|
+
}
|
|
9578
|
+
var LocalStateStore = class extends DocumentStateStore {
|
|
9579
|
+
workspaceRoot;
|
|
9580
|
+
constructor(workspaceRoot) {
|
|
9581
|
+
super();
|
|
9582
|
+
this.workspaceRoot = workspaceRoot;
|
|
8954
9583
|
}
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
provider;
|
|
8958
|
-
constructor(provider) {
|
|
8959
|
-
super(`The '${provider}' state provider keys state by workspace name. Set "name" in gkm.config.ts.`);
|
|
8960
|
-
this.provider = provider;
|
|
8961
|
-
this.name = "StateStoreNeedsWorkspaceName";
|
|
9584
|
+
get dir() {
|
|
9585
|
+
return join(this.workspaceRoot, ".gkm");
|
|
8962
9586
|
}
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
config;
|
|
8966
|
-
constructor(config) {
|
|
8967
|
-
super(`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', 's3', or an object implementing StateStore or StateProvider.`);
|
|
8968
|
-
this.config = config;
|
|
8969
|
-
this.name = "UnknownStateProvider";
|
|
9587
|
+
statePath(stage) {
|
|
9588
|
+
return join(this.dir, `deploy-${stage}.json`);
|
|
8970
9589
|
}
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
var StateStoreBusy = class extends Error {
|
|
8974
|
-
stage;
|
|
8975
|
-
location;
|
|
8976
|
-
constructor(stage, location) {
|
|
8977
|
-
super(`Timed out waiting to write deploy state for stage '${stage}' (${location}). If no other gkm process is running, remove ${location}.`);
|
|
8978
|
-
this.stage = stage;
|
|
8979
|
-
this.location = location;
|
|
8980
|
-
this.name = "StateStoreBusy";
|
|
9590
|
+
lockPath(stage) {
|
|
9591
|
+
return join(this.dir, `deploy-${stage}.lock`);
|
|
8981
9592
|
}
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
let parsed;
|
|
8985
|
-
try {
|
|
8986
|
-
parsed = JSON.parse(body);
|
|
8987
|
-
} catch (error) {
|
|
8988
|
-
throw new StateUnreadable(stage, location, error);
|
|
9593
|
+
backupPath(stage) {
|
|
9594
|
+
return join(this.dir, `deploy-${stage}.v1.json`);
|
|
8989
9595
|
}
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
if (schemaVersion === void 0) return {
|
|
8993
|
-
schemaVersion: 1,
|
|
8994
|
-
state: parsed
|
|
8995
|
-
};
|
|
8996
|
-
if (schemaVersion === 2) return {
|
|
8997
|
-
schemaVersion: 2,
|
|
8998
|
-
document: parsed
|
|
8999
|
-
};
|
|
9000
|
-
throw new StateSchemaTooNew(stage, schemaVersion);
|
|
9001
|
-
}
|
|
9002
|
-
function encodeDocument(document) {
|
|
9003
|
-
return JSON.stringify(document, null, 2);
|
|
9004
|
-
}
|
|
9005
|
-
/**
|
|
9006
|
-
* v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,
|
|
9007
|
-
* so the first journaled deploy after an upgrade adopts them rather than
|
|
9008
|
-
* treating them as unknown.
|
|
9009
|
-
*/
|
|
9010
|
-
function migrateV1(stage, state) {
|
|
9011
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9012
|
-
const resources = {};
|
|
9013
|
-
const seed = (type, name, id) => {
|
|
9014
|
-
if (!id) return;
|
|
9015
|
-
const key = name ? `${type}:${name}` : type;
|
|
9016
|
-
resources[key] = {
|
|
9017
|
-
key,
|
|
9018
|
-
type,
|
|
9019
|
-
id,
|
|
9020
|
-
status: "ready",
|
|
9021
|
-
updatedAt: now
|
|
9022
|
-
};
|
|
9023
|
-
};
|
|
9024
|
-
seed("project", null, state.projectId);
|
|
9025
|
-
seed("environment", null, state.environmentId);
|
|
9026
|
-
for (const [app, id] of Object.entries(state.applications ?? {})) seed("application", app, id);
|
|
9027
|
-
seed("postgres", null, state.services?.postgresId);
|
|
9028
|
-
seed("redis", null, state.services?.redisId);
|
|
9029
|
-
seed("backup-destination", null, state.backups?.destinationId);
|
|
9030
|
-
return {
|
|
9031
|
-
schemaVersion: 2,
|
|
9032
|
-
stage,
|
|
9033
|
-
serial: 1,
|
|
9034
|
-
state,
|
|
9035
|
-
resources,
|
|
9036
|
-
updatedAt: now
|
|
9037
|
-
};
|
|
9038
|
-
}
|
|
9039
|
-
function newLockHolder(options) {
|
|
9040
|
-
let owner = "unknown";
|
|
9041
|
-
try {
|
|
9042
|
-
owner = userInfo().username;
|
|
9043
|
-
} catch {}
|
|
9044
|
-
return {
|
|
9045
|
-
id: randomUUID(),
|
|
9046
|
-
owner,
|
|
9047
|
-
host: hostname(),
|
|
9048
|
-
pid: process.pid,
|
|
9049
|
-
acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9050
|
-
operation: options?.operation
|
|
9051
|
-
};
|
|
9052
|
-
}
|
|
9053
|
-
function contentVersion(body) {
|
|
9054
|
-
return createHash("sha256").update(body).digest("hex").slice(0, 16);
|
|
9055
|
-
}
|
|
9056
|
-
/**
|
|
9057
|
-
* A store over a backend that can read and conditionally write one blob per
|
|
9058
|
-
* stage, and create a lock object only if it does not exist. Migration and
|
|
9059
|
-
* resource records are the same for every such backend, so they live here.
|
|
9060
|
-
*/
|
|
9061
|
-
var DocumentStateStore = class {
|
|
9062
|
-
async lock(stage, options) {
|
|
9063
|
-
const holder = newLockHolder(options);
|
|
9064
|
-
await this.createLock(stage, holder);
|
|
9065
|
-
let released = false;
|
|
9066
|
-
return {
|
|
9067
|
-
stage,
|
|
9068
|
-
holder,
|
|
9069
|
-
release: async () => {
|
|
9070
|
-
if (released) return;
|
|
9071
|
-
released = true;
|
|
9072
|
-
await this.removeLock(stage, holder);
|
|
9073
|
-
}
|
|
9074
|
-
};
|
|
9596
|
+
mutexPath(stage) {
|
|
9597
|
+
return join(this.dir, `deploy-${stage}.json.mutex`);
|
|
9075
9598
|
}
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
await this.removeLock(stage, null);
|
|
9079
|
-
return holder;
|
|
9599
|
+
location(stage) {
|
|
9600
|
+
return this.statePath(stage);
|
|
9080
9601
|
}
|
|
9081
|
-
async
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
version: current.version
|
|
9088
|
-
};
|
|
9089
|
-
}
|
|
9090
|
-
async write(stage, state, options) {
|
|
9091
|
-
if (options.expectedVersion === null) {
|
|
9092
|
-
const document = {
|
|
9093
|
-
schemaVersion: 2,
|
|
9094
|
-
stage,
|
|
9095
|
-
serial: 1,
|
|
9096
|
-
state,
|
|
9097
|
-
resources: {},
|
|
9098
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9602
|
+
async readRaw(stage) {
|
|
9603
|
+
try {
|
|
9604
|
+
const body = await readFile(this.statePath(stage), "utf-8");
|
|
9605
|
+
return {
|
|
9606
|
+
body,
|
|
9607
|
+
version: contentVersion(body)
|
|
9099
9608
|
};
|
|
9100
|
-
|
|
9609
|
+
} catch (error) {
|
|
9610
|
+
if (isCode(error, "ENOENT")) return null;
|
|
9611
|
+
throw error;
|
|
9101
9612
|
}
|
|
9102
|
-
return this.mutate(stage, options.expectedVersion, (document) => ({
|
|
9103
|
-
...document,
|
|
9104
|
-
state
|
|
9105
|
-
}));
|
|
9106
9613
|
}
|
|
9107
|
-
async
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9614
|
+
async writeRaw(stage, body, expectedVersion) {
|
|
9615
|
+
await this.ensureDir();
|
|
9616
|
+
return this.withMutex(stage, async () => {
|
|
9617
|
+
const actual = (await this.readRaw(stage))?.version ?? null;
|
|
9618
|
+
if (actual !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, actual);
|
|
9619
|
+
const target = this.statePath(stage);
|
|
9620
|
+
const temp = `${target}.${randomUUID()}.tmp`;
|
|
9621
|
+
try {
|
|
9622
|
+
const handle = await open(temp, "wx", FILE_MODE);
|
|
9623
|
+
try {
|
|
9624
|
+
await handle.writeFile(body);
|
|
9625
|
+
await handle.sync();
|
|
9626
|
+
} finally {
|
|
9627
|
+
await handle.close();
|
|
9115
9628
|
}
|
|
9629
|
+
await rename(temp, target);
|
|
9630
|
+
} catch (error) {
|
|
9631
|
+
await rm(temp, { force: true });
|
|
9632
|
+
throw error;
|
|
9116
9633
|
}
|
|
9117
|
-
|
|
9118
|
-
}
|
|
9119
|
-
async deleteResource(stage, key, options) {
|
|
9120
|
-
return this.mutate(stage, options?.expectedVersion, (document) => {
|
|
9121
|
-
const { [key]: _removed, ...resources } = document.resources;
|
|
9122
|
-
return {
|
|
9123
|
-
...document,
|
|
9124
|
-
resources
|
|
9125
|
-
};
|
|
9634
|
+
return contentVersion(body);
|
|
9126
9635
|
});
|
|
9127
9636
|
}
|
|
9128
|
-
async
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9637
|
+
async writeV1Backup(stage, body) {
|
|
9638
|
+
try {
|
|
9639
|
+
await writeFile(this.backupPath(stage), body, {
|
|
9640
|
+
flag: "wx",
|
|
9641
|
+
mode: FILE_MODE
|
|
9642
|
+
});
|
|
9643
|
+
} catch (error) {
|
|
9644
|
+
if (!isCode(error, "EEXIST")) throw error;
|
|
9133
9645
|
}
|
|
9134
|
-
if (expectedVersion !== void 0 && current.version !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, current.version);
|
|
9135
|
-
const next = change(current.document);
|
|
9136
|
-
next.serial = current.document.serial + 1;
|
|
9137
|
-
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9138
|
-
return this.writeRaw(stage, encodeDocument(next), current.version);
|
|
9139
9646
|
}
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
* wrote) first, its result is read instead.
|
|
9144
|
-
*/
|
|
9145
|
-
async readDocument(stage, attempt = 0) {
|
|
9146
|
-
const raw = await this.readRaw(stage);
|
|
9147
|
-
if (!raw) return null;
|
|
9148
|
-
const decoded = decodeDocument(stage, raw.body, this.location(stage));
|
|
9149
|
-
if (decoded.schemaVersion === 2) return {
|
|
9150
|
-
document: decoded.document,
|
|
9151
|
-
version: raw.version
|
|
9152
|
-
};
|
|
9153
|
-
await this.writeV1Backup(stage, raw.body);
|
|
9154
|
-
const document = migrateV1(stage, decoded.state);
|
|
9647
|
+
async createLock(stage, holder) {
|
|
9648
|
+
await this.ensureDir();
|
|
9649
|
+
let handle;
|
|
9155
9650
|
try {
|
|
9156
|
-
|
|
9157
|
-
document,
|
|
9158
|
-
version: await this.writeRaw(stage, encodeDocument(document), raw.version)
|
|
9159
|
-
};
|
|
9651
|
+
handle = await open(this.lockPath(stage), "wx", FILE_MODE);
|
|
9160
9652
|
} catch (error) {
|
|
9161
|
-
if (error
|
|
9653
|
+
if (isCode(error, "EEXIST")) throw new StateLocked(stage, await this.readLock(stage), this.lockPath(stage));
|
|
9162
9654
|
throw error;
|
|
9163
9655
|
}
|
|
9656
|
+
try {
|
|
9657
|
+
await handle.writeFile(JSON.stringify(holder, null, 2));
|
|
9658
|
+
} finally {
|
|
9659
|
+
await handle.close();
|
|
9660
|
+
}
|
|
9164
9661
|
}
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
}
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
* `createStateProvider` reads:
|
|
9172
|
-
*
|
|
9173
|
-
* - none / `'local'`: `LocalStateStore` under `.gkm/`
|
|
9174
|
-
* - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so
|
|
9175
|
-
* a stale local copy can never win over it
|
|
9176
|
-
* - `'s3'`: `S3StateStore`
|
|
9177
|
-
* - a `StateStore` object: used as is
|
|
9178
|
-
* - a `StateProvider` object: wrapped by `LegacyStateStore`
|
|
9179
|
-
*/
|
|
9180
|
-
async function createStateStore(options) {
|
|
9181
|
-
const { config, workspaceRoot, workspaceName } = options;
|
|
9182
|
-
if (!config || config.provider === "local") {
|
|
9183
|
-
const { LocalStateStore } = await import("./LocalStateStore-BupdV-VN.mjs");
|
|
9184
|
-
return new LocalStateStore(workspaceRoot);
|
|
9185
|
-
}
|
|
9186
|
-
if (isStateStore(config.provider)) return config.provider;
|
|
9187
|
-
if (isStateProvider(config.provider)) {
|
|
9188
|
-
const { LegacyStateStore } = await import("./LegacyStateStore-ByRWQ9s1.mjs");
|
|
9189
|
-
return new LegacyStateStore(config.provider, { warn: options.warn });
|
|
9190
|
-
}
|
|
9191
|
-
if (config.provider === "ssm") {
|
|
9192
|
-
if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
|
|
9193
|
-
const { SSMStateStore } = await import("./SSMStateStore-BjZZm-nV.mjs");
|
|
9194
|
-
const ssm = config;
|
|
9195
|
-
return SSMStateStore.create({
|
|
9196
|
-
workspaceName,
|
|
9197
|
-
region: ssm.region,
|
|
9198
|
-
profile: ssm.profile
|
|
9199
|
-
});
|
|
9662
|
+
async readLock(stage) {
|
|
9663
|
+
try {
|
|
9664
|
+
return JSON.parse(await readFile(this.lockPath(stage), "utf-8"));
|
|
9665
|
+
} catch {
|
|
9666
|
+
return null;
|
|
9667
|
+
}
|
|
9200
9668
|
}
|
|
9201
|
-
|
|
9202
|
-
if (
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
workspaceName,
|
|
9207
|
-
bucket: s3.bucket,
|
|
9208
|
-
prefix: s3.prefix,
|
|
9209
|
-
region: s3.region,
|
|
9210
|
-
profile: s3.profile
|
|
9211
|
-
});
|
|
9669
|
+
async removeLock(stage, holder) {
|
|
9670
|
+
if (holder) {
|
|
9671
|
+
if ((await this.readLock(stage))?.id !== holder.id) return;
|
|
9672
|
+
}
|
|
9673
|
+
await rm(this.lockPath(stage), { force: true });
|
|
9212
9674
|
}
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
* conditional on the version this provider last read.
|
|
9219
|
-
*/
|
|
9220
|
-
var StateStoreProvider = class {
|
|
9221
|
-
store;
|
|
9222
|
-
versions = /* @__PURE__ */ new Map();
|
|
9223
|
-
constructor(store) {
|
|
9224
|
-
this.store = store;
|
|
9675
|
+
async ensureDir() {
|
|
9676
|
+
await mkdir(this.dir, {
|
|
9677
|
+
recursive: true,
|
|
9678
|
+
mode: 448
|
|
9679
|
+
});
|
|
9225
9680
|
}
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9681
|
+
/**
|
|
9682
|
+
* Serialises the check-then-rename of a conditional write across
|
|
9683
|
+
* processes. The stage lock is for whole runs; this is for the instant of
|
|
9684
|
+
* a single write, so even writers that skip the lock never interleave.
|
|
9685
|
+
*/
|
|
9686
|
+
async withMutex(stage, fn) {
|
|
9687
|
+
const path = this.mutexPath(stage);
|
|
9688
|
+
const deadline = Date.now() + MUTEX_TIMEOUT_MS;
|
|
9689
|
+
for (;;) try {
|
|
9690
|
+
await (await open(path, "wx", FILE_MODE)).close();
|
|
9691
|
+
break;
|
|
9692
|
+
} catch (error) {
|
|
9693
|
+
if (!isCode(error, "EEXIST")) throw error;
|
|
9694
|
+
if (await this.isStale(path)) {
|
|
9695
|
+
await unlink(path).catch(() => {});
|
|
9696
|
+
continue;
|
|
9697
|
+
}
|
|
9698
|
+
if (Date.now() > deadline) throw new StateStoreBusy(stage, path);
|
|
9699
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
9700
|
+
}
|
|
9701
|
+
try {
|
|
9702
|
+
return await fn();
|
|
9703
|
+
} finally {
|
|
9704
|
+
await rm(path, { force: true });
|
|
9705
|
+
}
|
|
9230
9706
|
}
|
|
9231
|
-
async
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9707
|
+
async isStale(path) {
|
|
9708
|
+
try {
|
|
9709
|
+
return Date.now() - (await stat(path)).mtimeMs > MUTEX_STALE_MS;
|
|
9710
|
+
} catch {
|
|
9711
|
+
return false;
|
|
9712
|
+
}
|
|
9236
9713
|
}
|
|
9237
9714
|
};
|
|
9238
9715
|
//#endregion
|
|
@@ -9240,7 +9717,12 @@ var StateStoreProvider = class {
|
|
|
9240
9717
|
/**
|
|
9241
9718
|
* State Management CLI Commands
|
|
9242
9719
|
*
|
|
9243
|
-
* Commands for managing deployment state across local and remote
|
|
9720
|
+
* Commands for managing deployment state across local and remote stores.
|
|
9721
|
+
*
|
|
9722
|
+
* Every command goes through a `StateStore`, the same one deploy writes
|
|
9723
|
+
* through: a v1 state is migrated on the way, resource records travel with
|
|
9724
|
+
* the state, and a copy is a conditional write — so `state:push` cannot
|
|
9725
|
+
* silently replace what a deploy wrote a moment earlier.
|
|
9244
9726
|
*/
|
|
9245
9727
|
/** What `state:show` prints in place of a secret. */
|
|
9246
9728
|
const MASKED = "********";
|
|
@@ -9266,26 +9748,12 @@ function maskStateSecrets(state) {
|
|
|
9266
9748
|
*/
|
|
9267
9749
|
async function statePullCommand(options) {
|
|
9268
9750
|
const { workspace } = await loadWorkspaceConfig();
|
|
9269
|
-
|
|
9270
|
-
console.error("No remote state provider configured.");
|
|
9271
|
-
console.error("Add a remote provider in gkm.config.ts:");
|
|
9272
|
-
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
9273
|
-
process.exit(1);
|
|
9274
|
-
}
|
|
9275
|
-
const provider = await createStateProvider({
|
|
9276
|
-
config: workspace.state,
|
|
9277
|
-
workspaceRoot: workspace.root,
|
|
9278
|
-
workspaceName: workspace.name
|
|
9279
|
-
});
|
|
9280
|
-
if (!(provider instanceof CachedStateProvider)) {
|
|
9281
|
-
console.error("State provider does not support pull operation.");
|
|
9282
|
-
process.exit(1);
|
|
9283
|
-
}
|
|
9751
|
+
const { local, remote } = await remoteAndLocal(workspace);
|
|
9284
9752
|
console.log(`Pulling state for stage: ${options.stage}...`);
|
|
9285
|
-
const
|
|
9286
|
-
if (
|
|
9753
|
+
const pulled = await copyUnderLock(remote, local, local, options.stage, "state:pull");
|
|
9754
|
+
if (pulled) {
|
|
9287
9755
|
console.log("State pulled successfully.");
|
|
9288
|
-
printStateSummary(state);
|
|
9756
|
+
printStateSummary(pulled.state);
|
|
9289
9757
|
} else console.log("No remote state found for this stage.");
|
|
9290
9758
|
}
|
|
9291
9759
|
/**
|
|
@@ -9294,26 +9762,12 @@ async function statePullCommand(options) {
|
|
|
9294
9762
|
*/
|
|
9295
9763
|
async function statePushCommand(options) {
|
|
9296
9764
|
const { workspace } = await loadWorkspaceConfig();
|
|
9297
|
-
|
|
9298
|
-
console.error("No remote state provider configured.");
|
|
9299
|
-
console.error("Add a remote provider in gkm.config.ts:");
|
|
9300
|
-
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
9301
|
-
process.exit(1);
|
|
9302
|
-
}
|
|
9303
|
-
const provider = await createStateProvider({
|
|
9304
|
-
config: workspace.state,
|
|
9305
|
-
workspaceRoot: workspace.root,
|
|
9306
|
-
workspaceName: workspace.name
|
|
9307
|
-
});
|
|
9308
|
-
if (!(provider instanceof CachedStateProvider)) {
|
|
9309
|
-
console.error("State provider does not support push operation.");
|
|
9310
|
-
process.exit(1);
|
|
9311
|
-
}
|
|
9765
|
+
const { local, remote } = await remoteAndLocal(workspace);
|
|
9312
9766
|
console.log(`Pushing state for stage: ${options.stage}...`);
|
|
9313
|
-
const
|
|
9314
|
-
if (
|
|
9767
|
+
const pushed = await copyUnderLock(local, remote, remote, options.stage, "state:push");
|
|
9768
|
+
if (pushed) {
|
|
9315
9769
|
console.log("State pushed successfully.");
|
|
9316
|
-
printStateSummary(state);
|
|
9770
|
+
printStateSummary(pushed.state);
|
|
9317
9771
|
} else console.log("No local state found for this stage.");
|
|
9318
9772
|
}
|
|
9319
9773
|
/**
|
|
@@ -9322,7 +9776,7 @@ async function statePushCommand(options) {
|
|
|
9322
9776
|
*/
|
|
9323
9777
|
async function stateShowCommand(options) {
|
|
9324
9778
|
const { workspace } = await loadWorkspaceConfig();
|
|
9325
|
-
const stored = await (await
|
|
9779
|
+
const stored = await (await createStateStore({
|
|
9326
9780
|
config: workspace.state,
|
|
9327
9781
|
workspaceRoot: workspace.root,
|
|
9328
9782
|
workspaceName: workspace.name
|
|
@@ -9331,9 +9785,12 @@ async function stateShowCommand(options) {
|
|
|
9331
9785
|
console.log(`No state found for stage: ${options.stage}`);
|
|
9332
9786
|
return;
|
|
9333
9787
|
}
|
|
9334
|
-
const state = maskStateSecrets(stored);
|
|
9788
|
+
const state = maskStateSecrets(stored.state);
|
|
9335
9789
|
if (options.json) console.log(JSON.stringify(state, null, 2));
|
|
9336
|
-
else
|
|
9790
|
+
else {
|
|
9791
|
+
printStateDetails(state);
|
|
9792
|
+
printUnfinished(stored.resources);
|
|
9793
|
+
}
|
|
9337
9794
|
}
|
|
9338
9795
|
/**
|
|
9339
9796
|
* Release a stage's deploy lock, whoever holds it.
|
|
@@ -9362,22 +9819,11 @@ async function stateUnlockCommand(options) {
|
|
|
9362
9819
|
*/
|
|
9363
9820
|
async function stateDiffCommand(options) {
|
|
9364
9821
|
const { workspace } = await loadWorkspaceConfig();
|
|
9365
|
-
|
|
9366
|
-
console.error("No remote state provider configured.");
|
|
9367
|
-
console.error("Diff requires a remote provider to compare against.");
|
|
9368
|
-
process.exit(1);
|
|
9369
|
-
}
|
|
9370
|
-
const provider = await createStateProvider({
|
|
9371
|
-
config: workspace.state,
|
|
9372
|
-
workspaceRoot: workspace.root,
|
|
9373
|
-
workspaceName: workspace.name
|
|
9374
|
-
});
|
|
9375
|
-
if (!(provider instanceof CachedStateProvider)) {
|
|
9376
|
-
console.error("State provider does not support diff operation.");
|
|
9377
|
-
process.exit(1);
|
|
9378
|
-
}
|
|
9822
|
+
const stores = await remoteAndLocal(workspace, ["Diff requires a remote provider to compare against."]);
|
|
9379
9823
|
console.log(`Comparing state for stage: ${options.stage}...\n`);
|
|
9380
|
-
const
|
|
9824
|
+
const [localStored, remoteStored] = await Promise.all([stores.local.read(options.stage), stores.remote.read(options.stage)]);
|
|
9825
|
+
const local = localStored?.state ?? null;
|
|
9826
|
+
const remote = remoteStored?.state ?? null;
|
|
9381
9827
|
if (!local && !remote) {
|
|
9382
9828
|
console.log("No state found (local or remote).");
|
|
9383
9829
|
return;
|
|
@@ -9413,6 +9859,71 @@ async function stateDiffCommand(options) {
|
|
|
9413
9859
|
else console.log(` ${key}: ${localVal ?? "(none)"} (local) != ${remoteVal ?? "(none)"} (remote)`);
|
|
9414
9860
|
}
|
|
9415
9861
|
}
|
|
9862
|
+
const localRecords = localStored?.resources ?? {};
|
|
9863
|
+
const remoteRecords = remoteStored?.resources ?? {};
|
|
9864
|
+
const differing = [.../* @__PURE__ */ new Set([...Object.keys(localRecords), ...Object.keys(remoteRecords)])].filter((key) => describeRecord(localRecords[key]) !== describeRecord(remoteRecords[key]));
|
|
9865
|
+
if (differing.length > 0) {
|
|
9866
|
+
console.log("\nResources:");
|
|
9867
|
+
for (const key of differing) console.log(` ${key}: ${describeRecord(localRecords[key])} (local) != ${describeRecord(remoteRecords[key])} (remote)`);
|
|
9868
|
+
}
|
|
9869
|
+
}
|
|
9870
|
+
/**
|
|
9871
|
+
* The stage's local store and its remote one — or, for a workspace that keeps
|
|
9872
|
+
* state only locally, the reason there is nothing to move it between.
|
|
9873
|
+
*/
|
|
9874
|
+
async function remoteAndLocal(workspace, hint = ["Add a remote provider in gkm.config.ts:", " state: { provider: \"ssm\", region: \"us-east-1\" }"]) {
|
|
9875
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
9876
|
+
console.error("No remote state provider configured.");
|
|
9877
|
+
for (const line of hint) console.error(line);
|
|
9878
|
+
process.exit(1);
|
|
9879
|
+
}
|
|
9880
|
+
return {
|
|
9881
|
+
local: new LocalStateStore(workspace.root),
|
|
9882
|
+
remote: await createStateStore({
|
|
9883
|
+
config: workspace.state,
|
|
9884
|
+
workspaceRoot: workspace.root,
|
|
9885
|
+
workspaceName: workspace.name
|
|
9886
|
+
})
|
|
9887
|
+
};
|
|
9888
|
+
}
|
|
9889
|
+
/**
|
|
9890
|
+
* Makes `to` hold exactly what `from` holds for `stage` — its state and its
|
|
9891
|
+
* resource records — while holding `locked`'s lock for the stage. Every
|
|
9892
|
+
* write is conditional on the version just read, so anything that changes
|
|
9893
|
+
* `to` mid-copy raises `StateVersionConflict` rather than being overwritten.
|
|
9894
|
+
*/
|
|
9895
|
+
async function copyUnderLock(from, to, locked, stage, operation) {
|
|
9896
|
+
const lock = await locked.lock(stage, { operation });
|
|
9897
|
+
try {
|
|
9898
|
+
return await copyStage(from, to, stage);
|
|
9899
|
+
} finally {
|
|
9900
|
+
await lock.release();
|
|
9901
|
+
}
|
|
9902
|
+
}
|
|
9903
|
+
/** @internal Exported for testing */
|
|
9904
|
+
async function copyStage(from, to, stage) {
|
|
9905
|
+
const source = await from.read(stage);
|
|
9906
|
+
if (!source) return null;
|
|
9907
|
+
const target = await to.read(stage);
|
|
9908
|
+
let version = await to.write(stage, source.state, { expectedVersion: target?.version ?? null });
|
|
9909
|
+
for (const key of Object.keys(target?.resources ?? {})) {
|
|
9910
|
+
if (key in source.resources) continue;
|
|
9911
|
+
version = await to.deleteResource(stage, key, { expectedVersion: version });
|
|
9912
|
+
}
|
|
9913
|
+
for (const { updatedAt: _stamped, ...record } of Object.values(source.resources)) version = await to.putResource(stage, record, { expectedVersion: version });
|
|
9914
|
+
return source;
|
|
9915
|
+
}
|
|
9916
|
+
function describeRecord(record) {
|
|
9917
|
+
if (!record) return "(none)";
|
|
9918
|
+
return record.id ? `${record.status} ${record.id}` : record.status;
|
|
9919
|
+
}
|
|
9920
|
+
/** Resources a run marked `pending` and never saw created. */
|
|
9921
|
+
function printUnfinished(resources) {
|
|
9922
|
+
const pending = Object.values(resources).filter((record) => record.status === "pending");
|
|
9923
|
+
if (pending.length === 0) return;
|
|
9924
|
+
console.log("");
|
|
9925
|
+
console.log("Unfinished (a deploy stopped while creating these):");
|
|
9926
|
+
for (const record of pending) console.log(` ${record.key}`);
|
|
9416
9927
|
}
|
|
9417
9928
|
function printStateSummary(state) {
|
|
9418
9929
|
const appCount = Object.keys(state.applications).length;
|
|
@@ -10460,25 +10971,25 @@ function loadPackageJson() {
|
|
|
10460
10971
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
10461
10972
|
*/
|
|
10462
10973
|
const GEEKMIDAS_VERSIONS = {
|
|
10463
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
10464
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
10465
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
10466
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
10467
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
10468
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
10469
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
10470
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
10471
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
10472
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
10473
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
10474
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
10475
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
10476
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
10477
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
10478
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
10479
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
10480
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
10481
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
10974
|
+
"@geekmidas/audit": "~10.0.0-alpha.59",
|
|
10975
|
+
"@geekmidas/auth": "~10.0.0-alpha.59",
|
|
10976
|
+
"@geekmidas/cache": "~10.0.0-alpha.59",
|
|
10977
|
+
"@geekmidas/client": "~10.0.0-alpha.59",
|
|
10978
|
+
"@geekmidas/cloud": "~10.0.0-alpha.59",
|
|
10979
|
+
"@geekmidas/constructs": "~10.0.0-alpha.59",
|
|
10980
|
+
"@geekmidas/db": "~10.0.0-alpha.59",
|
|
10981
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.59",
|
|
10982
|
+
"@geekmidas/envkit": "~10.0.0-alpha.59",
|
|
10983
|
+
"@geekmidas/errors": "~10.0.0-alpha.59",
|
|
10984
|
+
"@geekmidas/events": "~10.0.0-alpha.59",
|
|
10985
|
+
"@geekmidas/logger": "~10.0.0-alpha.59",
|
|
10986
|
+
"@geekmidas/manifest": "~10.0.0-alpha.59",
|
|
10987
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.59",
|
|
10988
|
+
"@geekmidas/schema": "~10.0.0-alpha.59",
|
|
10989
|
+
"@geekmidas/services": "~10.0.0-alpha.59",
|
|
10990
|
+
"@geekmidas/storage": "~10.0.0-alpha.59",
|
|
10991
|
+
"@geekmidas/telescope": "~10.0.0-alpha.59",
|
|
10992
|
+
"@geekmidas/testkit": "~10.0.0-alpha.59",
|
|
10482
10993
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
10483
10994
|
};
|
|
10484
10995
|
//#endregion
|
|
@@ -16360,7 +16871,7 @@ function testDatabases(reconciled) {
|
|
|
16360
16871
|
* starts without the reference data the app needs.
|
|
16361
16872
|
*/
|
|
16362
16873
|
async function migrateTestStage(workspace, env) {
|
|
16363
|
-
const { migrateAndSeed } = await import("./migrate-
|
|
16874
|
+
const { migrateAndSeed } = await import("./migrate-BrFREmgQ.mjs");
|
|
16364
16875
|
const { discover } = await import("./discover-Cf2jAcgF.mjs").then((n) => n.n);
|
|
16365
16876
|
const sources = {};
|
|
16366
16877
|
const manifest = await discover({
|
|
@@ -16791,7 +17302,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
16791
17302
|
try {
|
|
16792
17303
|
const globalOptions = program.opts();
|
|
16793
17304
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16794
|
-
const { migrateCommand } = await import("./migrate-
|
|
17305
|
+
const { migrateCommand } = await import("./migrate-BrFREmgQ.mjs");
|
|
16795
17306
|
await migrateCommand({
|
|
16796
17307
|
...options,
|
|
16797
17308
|
...construct ? { construct } : {}
|
|
@@ -16805,7 +17316,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
16805
17316
|
try {
|
|
16806
17317
|
const globalOptions = program.opts();
|
|
16807
17318
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16808
|
-
const { seedCommand } = await import("./migrate-
|
|
17319
|
+
const { seedCommand } = await import("./migrate-BrFREmgQ.mjs");
|
|
16809
17320
|
await seedCommand({
|
|
16810
17321
|
...options,
|
|
16811
17322
|
...construct ? { construct } : {}
|
|
@@ -16819,7 +17330,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
16819
17330
|
try {
|
|
16820
17331
|
const globalOptions = program.opts();
|
|
16821
17332
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16822
|
-
const { migrationCommand } = await import("./migrate-
|
|
17333
|
+
const { migrationCommand } = await import("./migrate-BrFREmgQ.mjs");
|
|
16823
17334
|
await migrationCommand({
|
|
16824
17335
|
construct,
|
|
16825
17336
|
...name ? { name } : {}
|
|
@@ -16917,7 +17428,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
|
|
|
16917
17428
|
try {
|
|
16918
17429
|
const globalOptions = program.opts();
|
|
16919
17430
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16920
|
-
const { encryptionRotateCommand } = await import("./encryption-
|
|
17431
|
+
const { encryptionRotateCommand } = await import("./encryption-BW7Gh51G.mjs");
|
|
16921
17432
|
await encryptionRotateCommand(construct, options);
|
|
16922
17433
|
} catch (error) {
|
|
16923
17434
|
console.error(formatError(error));
|
|
@@ -16928,7 +17439,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
|
|
|
16928
17439
|
try {
|
|
16929
17440
|
const globalOptions = program.opts();
|
|
16930
17441
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16931
|
-
const { encryptionRetireCommand } = await import("./encryption-
|
|
17442
|
+
const { encryptionRetireCommand } = await import("./encryption-BW7Gh51G.mjs");
|
|
16932
17443
|
await encryptionRetireCommand(construct, key, options);
|
|
16933
17444
|
} catch (error) {
|
|
16934
17445
|
console.error(formatError(error));
|
|
@@ -16980,7 +17491,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16980
17491
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16981
17492
|
process.exit(1);
|
|
16982
17493
|
}
|
|
16983
|
-
const { derivedContainers } = await import("./workspace-
|
|
17494
|
+
const { derivedContainers } = await import("./workspace-BRZ14o-o.mjs").then((n) => n.o);
|
|
16984
17495
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16985
17496
|
if (!result) {
|
|
16986
17497
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -17039,7 +17550,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
17039
17550
|
try {
|
|
17040
17551
|
const globalOptions = program.opts();
|
|
17041
17552
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
17042
|
-
const { deployGithubCommand } = await import("./github-
|
|
17553
|
+
const { deployGithubCommand } = await import("./github-T_6sK3Rz.mjs");
|
|
17043
17554
|
await deployGithubCommand(options);
|
|
17044
17555
|
} catch (error) {
|
|
17045
17556
|
console.error(formatError(error));
|
|
@@ -17174,6 +17685,6 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
17174
17685
|
});
|
|
17175
17686
|
program.parse();
|
|
17176
17687
|
//#endregion
|
|
17177
|
-
export {
|
|
17688
|
+
export { encryption_exports as a, LocalStateStore_exports as i, slug as n, appPorts_exports as o, stamp as r, getHostingerToken as s, generateFullstackCustomSecrets as t };
|
|
17178
17689
|
|
|
17179
17690
|
//# sourceMappingURL=index.mjs.map
|