@geekmidas/cli 10.0.0-alpha.54 → 10.0.0-alpha.56
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/README.md +11 -10
- package/dist/{HostingerProvider-GF6NAZAK.mjs → HostingerProvider-cL-6oQ5r.mjs} +2 -2
- package/dist/{HostingerProvider-GF6NAZAK.mjs.map → HostingerProvider-cL-6oQ5r.mjs.map} +1 -1
- package/dist/LegacyStateStore-ByRWQ9s1.mjs +89 -0
- package/dist/LegacyStateStore-ByRWQ9s1.mjs.map +1 -0
- package/dist/LegacyStateStore-C_OFs1gN.cjs +89 -0
- package/dist/LegacyStateStore-C_OFs1gN.cjs.map +1 -0
- package/dist/LocalStateStore-BupdV-VN.mjs +163 -0
- package/dist/LocalStateStore-BupdV-VN.mjs.map +1 -0
- package/dist/LocalStateStore-DDDU0HkI.cjs +163 -0
- package/dist/LocalStateStore-DDDU0HkI.cjs.map +1 -0
- package/dist/{OpenApiTsGenerator-BN0PIZxd.cjs → OpenApiTsGenerator-BLRQ7Iu9.cjs} +58 -59
- package/dist/OpenApiTsGenerator-BLRQ7Iu9.cjs.map +1 -0
- package/dist/{OpenApiTsGenerator-BzIGy8KE.mjs → OpenApiTsGenerator-NThANByQ.mjs} +58 -59
- package/dist/{OpenApiTsGenerator-BzIGy8KE.mjs.map → OpenApiTsGenerator-NThANByQ.mjs.map} +1 -1
- package/dist/S3StateStore-C7eBae0G.cjs +160 -0
- package/dist/S3StateStore-C7eBae0G.cjs.map +1 -0
- package/dist/S3StateStore-FhzyENrN.mjs +161 -0
- package/dist/S3StateStore-FhzyENrN.mjs.map +1 -0
- package/dist/SSMStateStore-DD7jKrCO.cjs +146 -0
- package/dist/SSMStateStore-DD7jKrCO.cjs.map +1 -0
- package/dist/SSMStateStore-oda9xOGc.mjs +147 -0
- package/dist/SSMStateStore-oda9xOGc.mjs.map +1 -0
- package/dist/{backends-Dfeu5Dgc.mjs → backends-BcHL7Gy_.mjs} +2 -2
- package/dist/{backends-Dfeu5Dgc.mjs.map → backends-BcHL7Gy_.mjs.map} +1 -1
- package/dist/{backends-D5aDWxuA.cjs → backends-_gQdAzBK.cjs} +2 -2
- package/dist/{backends-D5aDWxuA.cjs.map → backends-_gQdAzBK.cjs.map} +1 -1
- package/dist/{bundler--fyybf1N.mjs → bundler-URImKj-A.mjs} +2 -2
- package/dist/{bundler--fyybf1N.mjs.map → bundler-URImKj-A.mjs.map} +1 -1
- package/dist/{config-SOkDH-n1.mjs → config-20V08LZp.mjs} +2 -3
- package/dist/config-20V08LZp.mjs.map +1 -0
- package/dist/{config-Cvr5fCbn.cjs → config-CP6wsLHA.cjs} +2 -3
- package/dist/config-CP6wsLHA.cjs.map +1 -0
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/{encryption-tB4LqLKN.cjs → encryption-10Srqiix.cjs} +4 -4
- package/dist/{encryption-tB4LqLKN.cjs.map → encryption-10Srqiix.cjs.map} +1 -1
- package/dist/{encryption-O-XdeNxe.mjs → encryption-KQTYFEzU.mjs} +4 -4
- package/dist/{encryption-O-XdeNxe.mjs.map → encryption-KQTYFEzU.mjs.map} +1 -1
- package/dist/{github-BGx4U61b.cjs → github-DtRUkHN8.cjs} +3 -3
- package/dist/{github-BGx4U61b.cjs.map → github-DtRUkHN8.cjs.map} +1 -1
- package/dist/{github-U_frvZgD.mjs → github-y7OEj59M.mjs} +3 -3
- package/dist/{github-U_frvZgD.mjs.map → github-y7OEj59M.mjs.map} +1 -1
- package/dist/{index-fU3qHy7c.d.cts → index-DMcjpvd-.d.cts} +2 -2
- package/dist/{index-DYh5TrsZ.d.mts.map → index-DMcjpvd-.d.cts.map} +1 -1
- package/dist/{index-DYh5TrsZ.d.mts → index-RM92lx8A.d.mts} +2 -2
- package/dist/{index-fU3qHy7c.d.cts.map → index-RM92lx8A.d.mts.map} +1 -1
- package/dist/index.cjs +1176 -1281
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1174 -1287
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-DWDaaBfo.mjs → migrate-CS3DkO6y.mjs} +4 -4
- package/dist/{migrate-DWDaaBfo.mjs.map → migrate-CS3DkO6y.mjs.map} +1 -1
- package/dist/{migrate-DN5VM5vy.cjs → migrate-DQ54y2WD.cjs} +4 -4
- package/dist/{migrate-DN5VM5vy.cjs.map → migrate-DQ54y2WD.cjs.map} +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/{ready-C8wG_d0v.mjs → ready-BSpUZnNo.mjs} +2 -2
- package/dist/ready-BSpUZnNo.mjs.map +1 -0
- package/dist/{ready-VkNwfJu7.cjs → ready-MUhVJDHe.cjs} +2 -2
- package/dist/ready-MUhVJDHe.cjs.map +1 -0
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{types-CT0ymlBk.d.mts → types-BIyIQUR-.d.cts} +138 -33
- package/dist/types-BIyIQUR-.d.cts.map +1 -0
- package/dist/{types-DYDT4FfE.d.cts → types-CmQl6zaP.d.mts} +137 -34
- package/dist/types-CmQl6zaP.d.mts.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +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-DFYu-iRP.cjs → workspace-BbPJVCto.cjs} +6 -6
- package/dist/{workspace-DFYu-iRP.cjs.map → workspace-BbPJVCto.cjs.map} +1 -1
- package/dist/{workspace-BcCiLzTO.cjs → workspace-BpdXIeWV.cjs} +22 -18
- package/dist/{workspace-BcCiLzTO.cjs.map → workspace-BpdXIeWV.cjs.map} +1 -1
- package/dist/{workspace-bOsks1uk.mjs → workspace-C2mFV53i.mjs} +22 -18
- package/dist/{workspace-bOsks1uk.mjs.map → workspace-C2mFV53i.mjs.map} +1 -1
- package/dist/{workspace-Bwl0728I.mjs → workspace-CgWcE_Yr.mjs} +8 -8
- package/dist/{workspace-Bwl0728I.mjs.map → workspace-CgWcE_Yr.mjs.map} +1 -1
- package/package.json +13 -14
- package/dist/OpenApiTsGenerator-BN0PIZxd.cjs.map +0 -1
- package/dist/config-Cvr5fCbn.cjs.map +0 -1
- package/dist/config-SOkDH-n1.mjs.map +0 -1
- package/dist/ready-C8wG_d0v.mjs.map +0 -1
- package/dist/ready-VkNwfJu7.cjs.map +0 -1
- package/dist/types-CT0ymlBk.d.mts.map +0 -1
- package/dist/types-DYDT4FfE.d.cts.map +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const require_index = require("./index.cjs");
|
|
2
|
+
let node_fs_promises = require("node:fs/promises");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_crypto = require("node:crypto");
|
|
5
|
+
//#region src/deploy/LocalStateStore.ts
|
|
6
|
+
/**
|
|
7
|
+
* Local filesystem state store.
|
|
8
|
+
*
|
|
9
|
+
* `.gkm/deploy-<stage>.json` holds the state, `.gkm/deploy-<stage>.lock` the
|
|
10
|
+
* lock. Every file is mode 0600: state holds database passwords and IAM keys.
|
|
11
|
+
*/
|
|
12
|
+
const FILE_MODE = 384;
|
|
13
|
+
/** How long a write waits for another process's write to finish. */
|
|
14
|
+
const MUTEX_TIMEOUT_MS = 5e3;
|
|
15
|
+
/**
|
|
16
|
+
* A write holds the mutex for milliseconds, so one this old was left by a
|
|
17
|
+
* process that died mid-write and is safe to take over.
|
|
18
|
+
*/
|
|
19
|
+
const MUTEX_STALE_MS = 3e4;
|
|
20
|
+
function isCode(error, code) {
|
|
21
|
+
return error?.code === code;
|
|
22
|
+
}
|
|
23
|
+
var LocalStateStore = class extends require_index.DocumentStateStore {
|
|
24
|
+
workspaceRoot;
|
|
25
|
+
constructor(workspaceRoot) {
|
|
26
|
+
super();
|
|
27
|
+
this.workspaceRoot = workspaceRoot;
|
|
28
|
+
}
|
|
29
|
+
get dir() {
|
|
30
|
+
return (0, node_path.join)(this.workspaceRoot, ".gkm");
|
|
31
|
+
}
|
|
32
|
+
statePath(stage) {
|
|
33
|
+
return (0, node_path.join)(this.dir, `deploy-${stage}.json`);
|
|
34
|
+
}
|
|
35
|
+
lockPath(stage) {
|
|
36
|
+
return (0, node_path.join)(this.dir, `deploy-${stage}.lock`);
|
|
37
|
+
}
|
|
38
|
+
backupPath(stage) {
|
|
39
|
+
return (0, node_path.join)(this.dir, `deploy-${stage}.v1.json`);
|
|
40
|
+
}
|
|
41
|
+
mutexPath(stage) {
|
|
42
|
+
return (0, node_path.join)(this.dir, `deploy-${stage}.json.mutex`);
|
|
43
|
+
}
|
|
44
|
+
location(stage) {
|
|
45
|
+
return this.statePath(stage);
|
|
46
|
+
}
|
|
47
|
+
async readRaw(stage) {
|
|
48
|
+
try {
|
|
49
|
+
const body = await (0, node_fs_promises.readFile)(this.statePath(stage), "utf-8");
|
|
50
|
+
return {
|
|
51
|
+
body,
|
|
52
|
+
version: require_index.contentVersion(body)
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (isCode(error, "ENOENT")) return null;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async writeRaw(stage, body, expectedVersion) {
|
|
60
|
+
await this.ensureDir();
|
|
61
|
+
return this.withMutex(stage, async () => {
|
|
62
|
+
const actual = (await this.readRaw(stage))?.version ?? null;
|
|
63
|
+
if (actual !== expectedVersion) throw new require_index.StateVersionConflict(stage, expectedVersion, actual);
|
|
64
|
+
const target = this.statePath(stage);
|
|
65
|
+
const temp = `${target}.${(0, node_crypto.randomUUID)()}.tmp`;
|
|
66
|
+
try {
|
|
67
|
+
const handle = await (0, node_fs_promises.open)(temp, "wx", FILE_MODE);
|
|
68
|
+
try {
|
|
69
|
+
await handle.writeFile(body);
|
|
70
|
+
await handle.sync();
|
|
71
|
+
} finally {
|
|
72
|
+
await handle.close();
|
|
73
|
+
}
|
|
74
|
+
await (0, node_fs_promises.rename)(temp, target);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
await (0, node_fs_promises.rm)(temp, { force: true });
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
return require_index.contentVersion(body);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async writeV1Backup(stage, body) {
|
|
83
|
+
try {
|
|
84
|
+
await (0, node_fs_promises.writeFile)(this.backupPath(stage), body, {
|
|
85
|
+
flag: "wx",
|
|
86
|
+
mode: FILE_MODE
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (!isCode(error, "EEXIST")) throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async createLock(stage, holder) {
|
|
93
|
+
await this.ensureDir();
|
|
94
|
+
let handle;
|
|
95
|
+
try {
|
|
96
|
+
handle = await (0, node_fs_promises.open)(this.lockPath(stage), "wx", FILE_MODE);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (isCode(error, "EEXIST")) throw new require_index.StateLocked(stage, await this.readLock(stage), this.lockPath(stage));
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
await handle.writeFile(JSON.stringify(holder, null, 2));
|
|
103
|
+
} finally {
|
|
104
|
+
await handle.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async readLock(stage) {
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(await (0, node_fs_promises.readFile)(this.lockPath(stage), "utf-8"));
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async removeLock(stage, holder) {
|
|
115
|
+
if (holder) {
|
|
116
|
+
if ((await this.readLock(stage))?.id !== holder.id) return;
|
|
117
|
+
}
|
|
118
|
+
await (0, node_fs_promises.rm)(this.lockPath(stage), { force: true });
|
|
119
|
+
}
|
|
120
|
+
async ensureDir() {
|
|
121
|
+
await (0, node_fs_promises.mkdir)(this.dir, {
|
|
122
|
+
recursive: true,
|
|
123
|
+
mode: 448
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Serialises the check-then-rename of a conditional write across
|
|
128
|
+
* processes. The stage lock is for whole runs; this is for the instant of
|
|
129
|
+
* a single write, so even writers that skip the lock never interleave.
|
|
130
|
+
*/
|
|
131
|
+
async withMutex(stage, fn) {
|
|
132
|
+
const path = this.mutexPath(stage);
|
|
133
|
+
const deadline = Date.now() + MUTEX_TIMEOUT_MS;
|
|
134
|
+
for (;;) try {
|
|
135
|
+
await (await (0, node_fs_promises.open)(path, "wx", FILE_MODE)).close();
|
|
136
|
+
break;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (!isCode(error, "EEXIST")) throw error;
|
|
139
|
+
if (await this.isStale(path)) {
|
|
140
|
+
await (0, node_fs_promises.unlink)(path).catch(() => {});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (Date.now() > deadline) throw new require_index.StateStoreBusy(stage, path);
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
return await fn();
|
|
148
|
+
} finally {
|
|
149
|
+
await (0, node_fs_promises.rm)(path, { force: true });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async isStale(path) {
|
|
153
|
+
try {
|
|
154
|
+
return Date.now() - (await (0, node_fs_promises.stat)(path)).mtimeMs > MUTEX_STALE_MS;
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
//#endregion
|
|
161
|
+
exports.LocalStateStore = LocalStateStore;
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=LocalStateStore-DDDU0HkI.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalStateStore-DDDU0HkI.cjs","names":["DocumentStateStore","join","readFile","contentVersion","StateVersionConflict","randomUUID","open","rename","rm","writeFile","StateLocked","mkdir","unlink","StateStoreBusy","stat"],"sources":["../src/deploy/LocalStateStore.ts"],"sourcesContent":["/**\n * Local filesystem state store.\n *\n * `.gkm/deploy-<stage>.json` holds the state, `.gkm/deploy-<stage>.lock` the\n * lock. Every file is mode 0600: state holds database passwords and IAM keys.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport {\n\tmkdir,\n\topen,\n\treadFile,\n\trename,\n\trm,\n\tstat,\n\tunlink,\n\twriteFile,\n} from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n\tcontentVersion,\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\tStateStoreBusy,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nconst FILE_MODE = 0o600;\n\n/** How long a write waits for another process's write to finish. */\nconst MUTEX_TIMEOUT_MS = 5_000;\n/**\n * A write holds the mutex for milliseconds, so one this old was left by a\n * process that died mid-write and is safe to take over.\n */\nconst MUTEX_STALE_MS = 30_000;\n\nfunction isCode(error: unknown, code: string): boolean {\n\treturn (error as NodeJS.ErrnoException)?.code === code;\n}\n\nexport class LocalStateStore extends DocumentStateStore {\n\tconstructor(readonly workspaceRoot: string) {\n\t\tsuper();\n\t}\n\n\tprivate get dir(): string {\n\t\treturn join(this.workspaceRoot, '.gkm');\n\t}\n\n\tstatePath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.json`);\n\t}\n\n\tlockPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.lock`);\n\t}\n\n\tbackupPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.v1.json`);\n\t}\n\n\tprivate mutexPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.json.mutex`);\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn this.statePath(stage);\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\ttry {\n\t\t\tconst body = await readFile(this.statePath(stage), 'utf-8');\n\t\t\treturn { body, version: contentVersion(body) };\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'ENOENT')) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tawait this.ensureDir();\n\t\treturn this.withMutex(stage, async () => {\n\t\t\tconst current = await this.readRaw(stage);\n\t\t\tconst actual = current?.version ?? null;\n\t\t\tif (actual !== expectedVersion) {\n\t\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t\t}\n\n\t\t\t// Written beside the target and renamed over it: a reader sees the\n\t\t\t// old file or the new one, never half of either.\n\t\t\tconst target = this.statePath(stage);\n\t\t\tconst temp = `${target}.${randomUUID()}.tmp`;\n\t\t\ttry {\n\t\t\t\tconst handle = await open(temp, 'wx', FILE_MODE);\n\t\t\t\ttry {\n\t\t\t\t\tawait handle.writeFile(body);\n\t\t\t\t\tawait handle.sync();\n\t\t\t\t} finally {\n\t\t\t\t\tawait handle.close();\n\t\t\t\t}\n\t\t\t\tawait rename(temp, target);\n\t\t\t} catch (error) {\n\t\t\t\tawait rm(temp, { force: true });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn contentVersion(body);\n\t\t});\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait writeFile(this.backupPath(stage), body, {\n\t\t\t\tflag: 'wx',\n\t\t\t\tmode: FILE_MODE,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!isCode(error, 'EEXIST')) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tawait this.ensureDir();\n\t\tlet handle: Awaited<ReturnType<typeof open>>;\n\t\ttry {\n\t\t\thandle = await open(this.lockPath(stage), 'wx', FILE_MODE);\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'EEXIST')) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\tthis.lockPath(stage),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\ttry {\n\t\t\tawait handle.writeFile(JSON.stringify(holder, null, 2));\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\ttry {\n\t\t\treturn JSON.parse(\n\t\t\t\tawait readFile(this.lockPath(stage), 'utf-8'),\n\t\t\t) as LockHolder;\n\t\t} catch {\n\t\t\t// Missing, or created by a process that died before writing to it.\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\tawait rm(this.lockPath(stage), { force: true });\n\t}\n\n\tprivate async ensureDir(): Promise<void> {\n\t\tawait mkdir(this.dir, { recursive: true, mode: 0o700 });\n\t}\n\n\t/**\n\t * Serialises the check-then-rename of a conditional write across\n\t * processes. The stage lock is for whole runs; this is for the instant of\n\t * a single write, so even writers that skip the lock never interleave.\n\t */\n\tprivate async withMutex<T>(stage: string, fn: () => Promise<T>): Promise<T> {\n\t\tconst path = this.mutexPath(stage);\n\t\tconst deadline = Date.now() + MUTEX_TIMEOUT_MS;\n\n\t\tfor (;;) {\n\t\t\ttry {\n\t\t\t\tconst handle = await open(path, 'wx', FILE_MODE);\n\t\t\t\tawait handle.close();\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCode(error, 'EEXIST')) throw error;\n\t\t\t\tif (await this.isStale(path)) {\n\t\t\t\t\tawait unlink(path).catch(() => {});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (Date.now() > deadline) throw new StateStoreBusy(stage, path);\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 10));\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait rm(path, { force: true });\n\t\t}\n\t}\n\n\tprivate async isStale(path: string): Promise<boolean> {\n\t\ttry {\n\t\t\treturn Date.now() - (await stat(path)).mtimeMs > MUTEX_STALE_MS;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -809,35 +809,22 @@ export async function setupEndpoints(
|
|
|
809
809
|
const telescopeEnabled = context.telescope?.enabled;
|
|
810
810
|
const telescopeWebSocketEnabled = context.telescope?.websocket;
|
|
811
811
|
const usesExternalTelescope = !!context.telescope?.telescopePath;
|
|
812
|
-
const studioEnabled = context.studio?.enabled;
|
|
813
|
-
const usesExternalStudio = !!context.studio?.studioPath;
|
|
814
|
-
const studioFromDatabase = !usesExternalStudio && !!context.studio?.database;
|
|
815
812
|
const telescopeFromSurface = !!context.surface?.module && !usesExternalTelescope;
|
|
816
813
|
let telescopeImports = "";
|
|
817
814
|
if (telescopeEnabled) {
|
|
818
815
|
if (telescopeFromSurface) telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
|
|
819
|
-
import {
|
|
816
|
+
import { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;
|
|
820
817
|
else if (usesExternalTelescope) {
|
|
821
818
|
const relativeTelescopePath = (0, node_path.relative)((0, node_path.dirname)(appPath), context.telescope?.telescopePath);
|
|
822
819
|
telescopeImports = `import ${context.telescope?.telescopeImportPattern} from '${relativeTelescopePath}';
|
|
823
|
-
import {
|
|
820
|
+
import { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;
|
|
824
821
|
} else telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
|
|
825
|
-
import {
|
|
826
|
-
}
|
|
827
|
-
let studioImports = "";
|
|
828
|
-
if (studioEnabled) {
|
|
829
|
-
if (usesExternalStudio) {
|
|
830
|
-
const relativeStudioPath = (0, node_path.relative)((0, node_path.dirname)(appPath), context.studio?.studioPath);
|
|
831
|
-
studioImports = `import ${context.studio?.studioImportPattern} from '${relativeStudioPath}';
|
|
832
|
-
import { createStudioApp } from '@geekmidas/studio/server/hono';`;
|
|
833
|
-
} else if (studioFromDatabase) {
|
|
834
|
-
const dbSpecifier = importSpecifier((0, node_path.dirname)(appPath), context.studio.database.specifier);
|
|
835
|
-
studioImports = `import { snifferContext } from '@geekmidas/constructs';
|
|
836
|
-
import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
|
|
837
|
-
import { ${context.studio.database.exportName} as __studioDb } from '${dbSpecifier}';
|
|
838
|
-
import { createStudioApp } from '@geekmidas/studio/server/hono';`;
|
|
839
|
-
} else studioImports = "";
|
|
822
|
+
import { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;
|
|
840
823
|
}
|
|
824
|
+
const databaseApi = context.databaseApi;
|
|
825
|
+
const databaseApiImports = databaseApi ? `import { snifferContext } from '@geekmidas/constructs';
|
|
826
|
+
import { createIntrospectionHandler } from '@geekmidas/db/introspect';
|
|
827
|
+
import { ${databaseApi.database.exportName} as __introspectedDb } from '${importSpecifier((0, node_path.dirname)(appPath), databaseApi.database.specifier)}';` : "";
|
|
841
828
|
const cors = corsFor(context.surface);
|
|
842
829
|
let hooksImports = "";
|
|
843
830
|
let beforeSetupCall = "";
|
|
@@ -895,9 +882,9 @@ ${telescopeWebSocketSetupCode}
|
|
|
895
882
|
// Add telescope middleware (before endpoints to capture all requests)
|
|
896
883
|
honoApp.use('*', createMiddleware(telescope));
|
|
897
884
|
|
|
898
|
-
// Mount
|
|
899
|
-
const
|
|
900
|
-
honoApp.route('${context.telescope?.path}',
|
|
885
|
+
// Mount Telescope's JSON API
|
|
886
|
+
const telescopeApi = createApi(telescope);
|
|
887
|
+
honoApp.route('${context.telescope?.path}', telescopeApi);
|
|
901
888
|
`;
|
|
902
889
|
else telescopeSetup = `
|
|
903
890
|
// Setup Telescope for debugging/monitoring
|
|
@@ -913,32 +900,21 @@ ${telescopeWebSocketSetupCode}
|
|
|
913
900
|
// Add telescope middleware (before endpoints to capture all requests)
|
|
914
901
|
honoApp.use('*', createMiddleware(telescope));
|
|
915
902
|
|
|
916
|
-
// Mount
|
|
917
|
-
const
|
|
918
|
-
honoApp.route('${context.telescope?.path}',
|
|
903
|
+
// Mount Telescope's JSON API
|
|
904
|
+
const telescopeApi = createApi(telescope);
|
|
905
|
+
honoApp.route('${context.telescope?.path}', telescopeApi);
|
|
919
906
|
`;
|
|
920
907
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
db: await __studioDb.service.register({ envParser, context: snifferContext }),
|
|
929
|
-
cursor: { field: 'id', direction: Direction.Desc },
|
|
930
|
-
},
|
|
931
|
-
enabled: true,
|
|
908
|
+
const databaseApiSetup = databaseApi ? `
|
|
909
|
+
// The database's structure and rows as JSON, read-only, for whatever tool
|
|
910
|
+
// the developer points at it. Dev only: it answers anyone who can reach the
|
|
911
|
+
// port.
|
|
912
|
+
const databaseApi = createIntrospectionHandler({
|
|
913
|
+
db: await __introspectedDb.service.register({ envParser, context: snifferContext }),
|
|
914
|
+
basePath: '${databaseApi.path}',
|
|
932
915
|
});
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
honoApp.route('${context.studio?.path}', studioApp);
|
|
936
|
-
`;
|
|
937
|
-
else if (studioEnabled && usesExternalStudio) studioSetup = `
|
|
938
|
-
// Mount Studio data browser UI
|
|
939
|
-
const studioApp = createStudioApp(studio);
|
|
940
|
-
honoApp.route('${context.studio?.path}', studioApp);
|
|
941
|
-
`;
|
|
916
|
+
honoApp.all('${databaseApi.path}/*', (c) => databaseApi(c.req.raw));
|
|
917
|
+
` : "";
|
|
942
918
|
const content = `/**
|
|
943
919
|
* Generated server application
|
|
944
920
|
*
|
|
@@ -954,7 +930,7 @@ import { setupQueues } from './queues.js';
|
|
|
954
930
|
import { setupCrons } from './crons.js';
|
|
955
931
|
${runtime.imports}
|
|
956
932
|
${telescopeImports}
|
|
957
|
-
${
|
|
933
|
+
${databaseApiImports}
|
|
958
934
|
${hooksImports}
|
|
959
935
|
${cors.imports}
|
|
960
936
|
${context.storageDrivers?.imports ?? ""}
|
|
@@ -976,11 +952,15 @@ ${context.storageDrivers?.setup ? `// Which storage drivers exist is the entry p
|
|
|
976
952
|
// a provider.
|
|
977
953
|
${context.storageDrivers.setup}
|
|
978
954
|
` : ""}
|
|
979
|
-
|
|
955
|
+
${context.surface ? `// Who this server is to Postgres: application_name on every connection.
|
|
956
|
+
process.env.GKM_APP_NAME ??= ${JSON.stringify(context.surface.id)};
|
|
957
|
+
|
|
958
|
+
` : ""}export interface ServerApp {
|
|
980
959
|
app: HonoType;
|
|
981
960
|
start: (options?: {
|
|
982
961
|
port?: number;
|
|
983
|
-
|
|
962
|
+
/** Returns the server, so a shutdown can stop it taking requests. */
|
|
963
|
+
serve: (app: HonoType, port: number) => unknown;
|
|
984
964
|
}) => Promise<void>;
|
|
985
965
|
}
|
|
986
966
|
|
|
@@ -1020,7 +1000,7 @@ export interface ServerApp {
|
|
|
1020
1000
|
*/
|
|
1021
1001
|
export async function createApp(app?: HonoType, enableOpenApi: boolean = true): Promise<ServerApp> {
|
|
1022
1002
|
const honoApp = app || new Hono();
|
|
1023
|
-
${telescopeSetup}${cors.setup}${beforeSetupCall}${
|
|
1003
|
+
${telescopeSetup}${cors.setup}${beforeSetupCall}${databaseApiSetup}
|
|
1024
1004
|
// Setup HTTP endpoints
|
|
1025
1005
|
await setupEndpoints(honoApp, envParser, logger, enableOpenApi);
|
|
1026
1006
|
${afterSetupCall}
|
|
@@ -1148,20 +1128,35 @@ export const handler = adapter.handler;
|
|
|
1148
1128
|
import { setupQueues } from './queues.js';
|
|
1149
1129
|
import { setupCrons } from './crons.js';` : "";
|
|
1150
1130
|
const gracefulShutdownCode = enableGracefulShutdown ? `
|
|
1151
|
-
// Graceful shutdown
|
|
1131
|
+
// Graceful shutdown: stop taking requests, let in-flight ones finish, then
|
|
1132
|
+
// close what constructs opened (database pools) — so a rolling deploy does
|
|
1133
|
+
// not leave the old task holding connections. Bounded by
|
|
1134
|
+
// GKM_SHUTDOWN_TIMEOUT_MS, 8s by default: under Docker's 10s stop timeout,
|
|
1135
|
+
// so the process exits on its own terms rather than being killed mid-drain.
|
|
1152
1136
|
let isShuttingDown = false;
|
|
1153
1137
|
|
|
1154
1138
|
const shutdown = async () => {
|
|
1155
1139
|
if (isShuttingDown) return;
|
|
1156
1140
|
isShuttingDown = true;
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
setTimeout(() =>
|
|
1141
|
+
const deadline = Number(process.env.GKM_SHUTDOWN_TIMEOUT_MS) || 8000;
|
|
1142
|
+
logger.info({ deadline }, 'Graceful shutdown initiated');
|
|
1143
|
+
setTimeout(() => {
|
|
1144
|
+
logger.warn({ deadline }, 'Shutdown deadline reached, exiting');
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}, deadline).unref();
|
|
1147
|
+
await new Promise<void>((resolve) => {
|
|
1148
|
+
const close = (server as { close?: (done: () => void) => void } | undefined)?.close;
|
|
1149
|
+
if (typeof close === 'function') close.call(server, () => resolve());
|
|
1150
|
+
else resolve();
|
|
1151
|
+
});
|
|
1152
|
+
await runShutdownHooks((error) => logger.error({ error }, 'Shutdown hook failed'));
|
|
1153
|
+
process.exit(0);
|
|
1160
1154
|
};
|
|
1161
1155
|
|
|
1162
1156
|
process.on('SIGTERM', shutdown);
|
|
1163
1157
|
process.on('SIGINT', shutdown);
|
|
1164
1158
|
` : "";
|
|
1159
|
+
const endpointsImportPath = production.optimizedHandlers ? "./endpoints/index.js" : "./endpoints.js";
|
|
1165
1160
|
const content = `/**
|
|
1166
1161
|
* Generated production server application
|
|
1167
1162
|
*
|
|
@@ -1173,7 +1168,7 @@ import { setupCrons } from './crons.js';` : "";
|
|
|
1173
1168
|
*/
|
|
1174
1169
|
import { Hono } from 'hono';
|
|
1175
1170
|
import type { Hono as HonoType } from 'hono';
|
|
1176
|
-
import {
|
|
1171
|
+
${enableGracefulShutdown ? "import { runShutdownHooks } from '@geekmidas/constructs';\n" : ""}import { setupEndpoints } from '${endpointsImportPath}';
|
|
1177
1172
|
${subscriberImport}
|
|
1178
1173
|
${runtime.imports}
|
|
1179
1174
|
${runtime.bindings}
|
|
@@ -1184,7 +1179,10 @@ ${context.storageDrivers?.imports ?? ""}
|
|
|
1184
1179
|
${context.storageDrivers?.setup ? `// The entry point registers the drivers its target needs.
|
|
1185
1180
|
${context.storageDrivers.setup}
|
|
1186
1181
|
` : ""}
|
|
1187
|
-
|
|
1182
|
+
${context.surface ? `// Who this server is to Postgres: application_name on every connection.
|
|
1183
|
+
process.env.GKM_APP_NAME ??= ${JSON.stringify(context.surface.id)};
|
|
1184
|
+
|
|
1185
|
+
` : ""}export interface ServerApp {
|
|
1188
1186
|
app: HonoType;
|
|
1189
1187
|
start: (options?: {
|
|
1190
1188
|
port?: number;
|
|
@@ -1217,11 +1215,12 @@ ${afterSetupCall}
|
|
|
1217
1215
|
}
|
|
1218
1216
|
|
|
1219
1217
|
const port = options.port ?? Number(process.env.PORT) ?? 3000;
|
|
1218
|
+
let server: unknown;
|
|
1220
1219
|
${gracefulShutdownCode}${subscriberSetup}
|
|
1221
1220
|
logger.info({ port }, 'Starting production server');
|
|
1222
1221
|
|
|
1223
1222
|
// Start HTTP server using provided serve function
|
|
1224
|
-
await options.serve(honoApp, port);
|
|
1223
|
+
server = await options.serve(honoApp, port);
|
|
1225
1224
|
|
|
1226
1225
|
logger.info({ port }, 'Production server started');
|
|
1227
1226
|
${includeSubscribers ? "\n await subscribeForPush(port);" : ""}
|
|
@@ -1255,7 +1254,7 @@ const { start } = await createApp();
|
|
|
1255
1254
|
|
|
1256
1255
|
await start({
|
|
1257
1256
|
port,
|
|
1258
|
-
serve: (app, port) =>
|
|
1257
|
+
serve: (app, port) => serve({ fetch: app.fetch, port }),
|
|
1259
1258
|
});
|
|
1260
1259
|
`);
|
|
1261
1260
|
}
|
|
@@ -1785,4 +1784,4 @@ Object.defineProperty(exports, "runtimeFor", {
|
|
|
1785
1784
|
}
|
|
1786
1785
|
});
|
|
1787
1786
|
|
|
1788
|
-
//# sourceMappingURL=OpenApiTsGenerator-
|
|
1787
|
+
//# sourceMappingURL=OpenApiTsGenerator-BLRQ7Iu9.cjs.map
|