@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
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { hostname, userInfo } from "node:os";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
//#region src/deploy/StateProvider.ts
|
|
4
|
+
/**
|
|
5
|
+
* Check if value is a StateProvider implementation.
|
|
6
|
+
*/
|
|
7
|
+
function isStateProvider(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && typeof value.read === "function" && typeof value.write === "function";
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/deploy/StateStore.ts
|
|
12
|
+
/**
|
|
13
|
+
* Deploy state store.
|
|
14
|
+
*
|
|
15
|
+
* `StateProvider` reads and writes a whole stage at once, with nothing to stop
|
|
16
|
+
* two runs of the same stage from overwriting each other and nowhere to record
|
|
17
|
+
* a resource the moment it exists. A `StateStore` adds the three things a
|
|
18
|
+
* deploy needs to be safe against crashes and concurrent runs:
|
|
19
|
+
*
|
|
20
|
+
* - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the
|
|
21
|
+
* holder, instead of racing the first.
|
|
22
|
+
* - Versioned `read` and conditional `write`: a write names the version it was
|
|
23
|
+
* based on, and raises `StateVersionConflict` when the stored state moved on
|
|
24
|
+
* underneath it, so nothing is lost silently even without the lock.
|
|
25
|
+
* - Per-resource records: `putResource` journals one resource at a time, so a
|
|
26
|
+
* run that dies half way leaves behind the ids of everything it created.
|
|
27
|
+
*
|
|
28
|
+
* Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A
|
|
29
|
+
* custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it
|
|
30
|
+
* working but cannot lock.
|
|
31
|
+
*/
|
|
32
|
+
var StateLocked = class extends Error {
|
|
33
|
+
stage;
|
|
34
|
+
holder;
|
|
35
|
+
location;
|
|
36
|
+
constructor(stage, holder, location) {
|
|
37
|
+
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";
|
|
38
|
+
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}\`.`);
|
|
39
|
+
this.stage = stage;
|
|
40
|
+
this.holder = holder;
|
|
41
|
+
this.location = location;
|
|
42
|
+
this.name = "StateLocked";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var StateVersionConflict = class extends Error {
|
|
46
|
+
stage;
|
|
47
|
+
expectedVersion;
|
|
48
|
+
actualVersion;
|
|
49
|
+
constructor(stage, expectedVersion, actualVersion) {
|
|
50
|
+
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.`);
|
|
51
|
+
this.stage = stage;
|
|
52
|
+
this.expectedVersion = expectedVersion;
|
|
53
|
+
this.actualVersion = actualVersion;
|
|
54
|
+
this.name = "StateVersionConflict";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var StageStateMissing = class extends Error {
|
|
58
|
+
stage;
|
|
59
|
+
constructor(stage) {
|
|
60
|
+
super(`There is no deploy state for stage '${stage}' to record a resource in. Write the stage's state first (write with expectedVersion: null).`);
|
|
61
|
+
this.stage = stage;
|
|
62
|
+
this.name = "StageStateMissing";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var StateUnreadable = class extends Error {
|
|
66
|
+
stage;
|
|
67
|
+
location;
|
|
68
|
+
constructor(stage, location, cause) {
|
|
69
|
+
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 });
|
|
70
|
+
this.stage = stage;
|
|
71
|
+
this.location = location;
|
|
72
|
+
this.name = "StateUnreadable";
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var StateSchemaTooNew = class extends Error {
|
|
76
|
+
stage;
|
|
77
|
+
schemaVersion;
|
|
78
|
+
constructor(stage, schemaVersion) {
|
|
79
|
+
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.`);
|
|
80
|
+
this.stage = stage;
|
|
81
|
+
this.schemaVersion = schemaVersion;
|
|
82
|
+
this.name = "StateSchemaTooNew";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Not thrown: emitted (once per store) when a custom `StateProvider` stands
|
|
87
|
+
* in for a store, so a deploy that cannot be protected from a concurrent one
|
|
88
|
+
* says so.
|
|
89
|
+
*/
|
|
90
|
+
var StateStoreWithoutLocking = class extends Error {
|
|
91
|
+
provider;
|
|
92
|
+
constructor(provider) {
|
|
93
|
+
super(`The custom state provider '${provider}' has no lock and no versions, so two deploys of the same stage can still overwrite each other. Implement StateStore (lock, versioned read, conditional write) or use the 'local', 'ssm' or 's3' state provider.`);
|
|
94
|
+
this.provider = provider;
|
|
95
|
+
this.name = "StateStoreWithoutLocking";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var StateStoreNeedsWorkspaceName = class extends Error {
|
|
99
|
+
provider;
|
|
100
|
+
constructor(provider) {
|
|
101
|
+
super(`The '${provider}' state provider keys state by workspace name. Set "name" in gkm.config.ts.`);
|
|
102
|
+
this.provider = provider;
|
|
103
|
+
this.name = "StateStoreNeedsWorkspaceName";
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var UnknownStateProvider = class extends Error {
|
|
107
|
+
config;
|
|
108
|
+
constructor(config) {
|
|
109
|
+
super(`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', 's3', or an object implementing StateStore or StateProvider.`);
|
|
110
|
+
this.config = config;
|
|
111
|
+
this.name = "UnknownStateProvider";
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
/** A lock or write that could not get at the state backend in time. */
|
|
115
|
+
var StateStoreBusy = class extends Error {
|
|
116
|
+
stage;
|
|
117
|
+
location;
|
|
118
|
+
constructor(stage, location) {
|
|
119
|
+
super(`Timed out waiting to write deploy state for stage '${stage}' (${location}). If no other gkm process is running, remove ${location}.`);
|
|
120
|
+
this.stage = stage;
|
|
121
|
+
this.location = location;
|
|
122
|
+
this.name = "StateStoreBusy";
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function decodeDocument(stage, body, location) {
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = JSON.parse(body);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new StateUnreadable(stage, location, error);
|
|
131
|
+
}
|
|
132
|
+
if (typeof parsed !== "object" || parsed === null) throw new StateUnreadable(stage, location, parsed);
|
|
133
|
+
const schemaVersion = parsed.schemaVersion;
|
|
134
|
+
if (schemaVersion === void 0) return {
|
|
135
|
+
schemaVersion: 1,
|
|
136
|
+
state: parsed
|
|
137
|
+
};
|
|
138
|
+
if (schemaVersion === 2) return {
|
|
139
|
+
schemaVersion: 2,
|
|
140
|
+
document: parsed
|
|
141
|
+
};
|
|
142
|
+
throw new StateSchemaTooNew(stage, schemaVersion);
|
|
143
|
+
}
|
|
144
|
+
function encodeDocument(document) {
|
|
145
|
+
return JSON.stringify(document, null, 2);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,
|
|
149
|
+
* so the first journaled deploy after an upgrade adopts them rather than
|
|
150
|
+
* treating them as unknown.
|
|
151
|
+
*/
|
|
152
|
+
function migrateV1(stage, state) {
|
|
153
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
154
|
+
const resources = {};
|
|
155
|
+
const seed = (type, name, id) => {
|
|
156
|
+
if (!id) return;
|
|
157
|
+
const key = name ? `${type}:${name}` : type;
|
|
158
|
+
resources[key] = {
|
|
159
|
+
key,
|
|
160
|
+
type,
|
|
161
|
+
id,
|
|
162
|
+
status: "ready",
|
|
163
|
+
updatedAt: now
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
seed("project", null, state.projectId);
|
|
167
|
+
seed("environment", null, state.environmentId);
|
|
168
|
+
for (const [app, id] of Object.entries(state.applications ?? {})) seed("application", app, id);
|
|
169
|
+
seed("postgres", null, state.services?.postgresId);
|
|
170
|
+
seed("redis", null, state.services?.redisId);
|
|
171
|
+
seed("backup-destination", null, state.backups?.destinationId);
|
|
172
|
+
return {
|
|
173
|
+
schemaVersion: 2,
|
|
174
|
+
stage,
|
|
175
|
+
serial: 1,
|
|
176
|
+
state,
|
|
177
|
+
resources,
|
|
178
|
+
updatedAt: now
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function newLockHolder(options) {
|
|
182
|
+
let owner = "unknown";
|
|
183
|
+
try {
|
|
184
|
+
owner = userInfo().username;
|
|
185
|
+
} catch {}
|
|
186
|
+
return {
|
|
187
|
+
id: randomUUID(),
|
|
188
|
+
owner,
|
|
189
|
+
host: hostname(),
|
|
190
|
+
pid: process.pid,
|
|
191
|
+
acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
192
|
+
operation: options?.operation
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function contentVersion(body) {
|
|
196
|
+
return createHash("sha256").update(body).digest("hex").slice(0, 16);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* A store over a backend that can read and conditionally write one blob per
|
|
200
|
+
* stage, and create a lock object only if it does not exist. Migration and
|
|
201
|
+
* resource records are the same for every such backend, so they live here.
|
|
202
|
+
*/
|
|
203
|
+
var DocumentStateStore = class {
|
|
204
|
+
async lock(stage, options) {
|
|
205
|
+
const holder = newLockHolder(options);
|
|
206
|
+
await this.createLock(stage, holder);
|
|
207
|
+
let released = false;
|
|
208
|
+
return {
|
|
209
|
+
stage,
|
|
210
|
+
holder,
|
|
211
|
+
release: async () => {
|
|
212
|
+
if (released) return;
|
|
213
|
+
released = true;
|
|
214
|
+
await this.removeLock(stage, holder);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
async forceUnlock(stage) {
|
|
219
|
+
const holder = await this.readLock(stage);
|
|
220
|
+
await this.removeLock(stage, null);
|
|
221
|
+
return holder;
|
|
222
|
+
}
|
|
223
|
+
async read(stage) {
|
|
224
|
+
const current = await this.readDocument(stage);
|
|
225
|
+
if (!current) return null;
|
|
226
|
+
return {
|
|
227
|
+
state: current.document.state,
|
|
228
|
+
resources: current.document.resources,
|
|
229
|
+
version: current.version
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async write(stage, state, options) {
|
|
233
|
+
if (options.expectedVersion === null) {
|
|
234
|
+
const document = {
|
|
235
|
+
schemaVersion: 2,
|
|
236
|
+
stage,
|
|
237
|
+
serial: 1,
|
|
238
|
+
state,
|
|
239
|
+
resources: {},
|
|
240
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
241
|
+
};
|
|
242
|
+
return this.writeRaw(stage, encodeDocument(document), null);
|
|
243
|
+
}
|
|
244
|
+
return this.mutate(stage, options.expectedVersion, (document) => ({
|
|
245
|
+
...document,
|
|
246
|
+
state
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
async putResource(stage, record, options) {
|
|
250
|
+
return this.mutate(stage, options?.expectedVersion, (document) => ({
|
|
251
|
+
...document,
|
|
252
|
+
resources: {
|
|
253
|
+
...document.resources,
|
|
254
|
+
[record.key]: {
|
|
255
|
+
...record,
|
|
256
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
async deleteResource(stage, key, options) {
|
|
262
|
+
return this.mutate(stage, options?.expectedVersion, (document) => {
|
|
263
|
+
const { [key]: _removed, ...resources } = document.resources;
|
|
264
|
+
return {
|
|
265
|
+
...document,
|
|
266
|
+
resources
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async mutate(stage, expectedVersion, change) {
|
|
271
|
+
const current = await this.readDocument(stage);
|
|
272
|
+
if (!current) {
|
|
273
|
+
if (expectedVersion !== void 0) throw new StateVersionConflict(stage, expectedVersion, null);
|
|
274
|
+
throw new StageStateMissing(stage);
|
|
275
|
+
}
|
|
276
|
+
if (expectedVersion !== void 0 && current.version !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, current.version);
|
|
277
|
+
const next = change(current.document);
|
|
278
|
+
next.serial = current.document.serial + 1;
|
|
279
|
+
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
280
|
+
return this.writeRaw(stage, encodeDocument(next), current.version);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Reads the stage's document, migrating v1 in place on the way. The
|
|
284
|
+
* migration is itself a conditional write: if another run migrated (or
|
|
285
|
+
* wrote) first, its result is read instead.
|
|
286
|
+
*/
|
|
287
|
+
async readDocument(stage, attempt = 0) {
|
|
288
|
+
const raw = await this.readRaw(stage);
|
|
289
|
+
if (!raw) return null;
|
|
290
|
+
const decoded = decodeDocument(stage, raw.body, this.location(stage));
|
|
291
|
+
if (decoded.schemaVersion === 2) return {
|
|
292
|
+
document: decoded.document,
|
|
293
|
+
version: raw.version
|
|
294
|
+
};
|
|
295
|
+
await this.writeV1Backup(stage, raw.body);
|
|
296
|
+
const document = migrateV1(stage, decoded.state);
|
|
297
|
+
try {
|
|
298
|
+
return {
|
|
299
|
+
document,
|
|
300
|
+
version: await this.writeRaw(stage, encodeDocument(document), raw.version)
|
|
301
|
+
};
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (error instanceof StateVersionConflict && attempt < 2) return this.readDocument(stage, attempt + 1);
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
function isStateStore(value) {
|
|
309
|
+
return typeof value === "object" && value !== null && typeof value.lock === "function" && typeof value.read === "function" && typeof value.write === "function" && typeof value.putResource === "function";
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* The store for a workspace's `state` config:
|
|
313
|
+
*
|
|
314
|
+
* - none / `'local'`: `LocalStateStore` under `.gkm/`
|
|
315
|
+
* - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so
|
|
316
|
+
* a stale local copy can never win over it
|
|
317
|
+
* - `'s3'`: `S3StateStore`
|
|
318
|
+
* - a `StateStore` object: used as is
|
|
319
|
+
* - a `StateProvider` object: wrapped by `LegacyStateStore`
|
|
320
|
+
*/
|
|
321
|
+
async function createStateStore(options) {
|
|
322
|
+
const { config, workspaceRoot, workspaceName } = options;
|
|
323
|
+
if (!config || config.provider === "local") {
|
|
324
|
+
const { LocalStateStore } = await import("./index.mjs").then((n) => n.i);
|
|
325
|
+
return new LocalStateStore(workspaceRoot);
|
|
326
|
+
}
|
|
327
|
+
if (isStateStore(config.provider)) return config.provider;
|
|
328
|
+
if (isStateProvider(config.provider)) {
|
|
329
|
+
const { LegacyStateStore } = await import("./LegacyStateStore-CB4ch3Dl.mjs");
|
|
330
|
+
return new LegacyStateStore(config.provider, { warn: options.warn });
|
|
331
|
+
}
|
|
332
|
+
if (config.provider === "ssm") {
|
|
333
|
+
if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
|
|
334
|
+
const { SSMStateStore } = await import("./SSMStateStore-hnP5deFJ.mjs");
|
|
335
|
+
const ssm = config;
|
|
336
|
+
return SSMStateStore.create({
|
|
337
|
+
workspaceName,
|
|
338
|
+
region: ssm.region,
|
|
339
|
+
profile: ssm.profile
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
if (config.provider === "s3") {
|
|
343
|
+
if (!workspaceName) throw new StateStoreNeedsWorkspaceName("s3");
|
|
344
|
+
const { S3StateStore } = await import("./S3StateStore-CXTenDTW.mjs");
|
|
345
|
+
const s3 = config;
|
|
346
|
+
return S3StateStore.create({
|
|
347
|
+
workspaceName,
|
|
348
|
+
bucket: s3.bucket,
|
|
349
|
+
prefix: s3.prefix,
|
|
350
|
+
region: s3.region,
|
|
351
|
+
profile: s3.profile
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
throw new UnknownStateProvider(config);
|
|
355
|
+
}
|
|
356
|
+
//#endregion
|
|
357
|
+
export { StateStoreWithoutLocking as a, createStateStore as c, StateStoreBusy as i, newLockHolder as l, StageStateMissing as n, StateVersionConflict as o, StateLocked as r, contentVersion as s, DocumentStateStore as t };
|
|
358
|
+
|
|
359
|
+
//# sourceMappingURL=StateStore-CYUfGYcx.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StateStore-CYUfGYcx.mjs","names":[],"sources":["../src/deploy/StateProvider.ts","../src/deploy/StateStore.ts"],"sourcesContent":["/**\n * State configuration, and the `StateProvider` interface a custom backend can\n * implement.\n *\n * Deploy state is read and written through a `StateStore` (`StateStore.ts`):\n * locks, versions and resource records. A custom `StateProvider` — read and\n * write a whole state, nothing else — still works behind `LegacyStateStore`,\n * which warns that it cannot lock.\n */\n\nimport type { StateStore } from './StateStore';\nimport type { DokployStageState } from './state';\n\n/**\n * Interface for deployment state storage providers.\n *\n * Implementations must handle:\n * - Reading state for a stage (returns null if not found)\n * - Writing state for a stage (creates or updates)\n */\nexport interface StateProvider {\n\t/**\n\t * Read deployment state for a stage.\n\t *\n\t * @param stage - The deployment stage (e.g., 'development', 'production')\n\t * @returns The state object or null if not found\n\t */\n\tread(stage: string): Promise<DokployStageState | null>;\n\n\t/**\n\t * Write deployment state for a stage.\n\t *\n\t * @param stage - The deployment stage\n\t * @param state - The state object to persist\n\t */\n\twrite(stage: string, state: DokployStageState): Promise<void>;\n}\n\n/**\n * Valid AWS regions.\n */\nexport type AwsRegion =\n\t| 'us-east-1'\n\t| 'us-east-2'\n\t| 'us-west-1'\n\t| 'us-west-2'\n\t| 'af-south-1'\n\t| 'ap-east-1'\n\t| 'ap-south-1'\n\t| 'ap-south-2'\n\t| 'ap-southeast-1'\n\t| 'ap-southeast-2'\n\t| 'ap-southeast-3'\n\t| 'ap-southeast-4'\n\t| 'ap-northeast-1'\n\t| 'ap-northeast-2'\n\t| 'ap-northeast-3'\n\t| 'ca-central-1'\n\t| 'eu-central-1'\n\t| 'eu-central-2'\n\t| 'eu-west-1'\n\t| 'eu-west-2'\n\t| 'eu-west-3'\n\t| 'eu-south-1'\n\t| 'eu-south-2'\n\t| 'eu-north-1'\n\t| 'me-south-1'\n\t| 'me-central-1'\n\t| 'sa-east-1';\n\n/**\n * Local state provider config.\n */\nexport interface LocalStateConfig {\n\tprovider: 'local';\n}\n\n/**\n * SSM state provider config (requires region).\n */\nexport interface SSMStateConfig {\n\tprovider: 'ssm';\n\t/** AWS region (required for SSM provider) */\n\tregion: AwsRegion;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n}\n\n/**\n * S3 state config: one object per stage, written with conditional puts\n * (`If-Match` / `If-None-Match`), and a lock object beside it.\n *\n * Keys: `<prefix>/<workspace name>/<stage>/state.json` and `lock.json`.\n */\nexport interface S3StateConfig {\n\tprovider: 's3';\n\t/** Bucket the state lives in. It must already exist. */\n\tbucket: string;\n\t/** AWS region of the bucket */\n\tregion: AwsRegion;\n\t/** Key prefix inside the bucket (default: `gkm`) */\n\tprefix?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n}\n\n/**\n * Custom state provider config.\n */\nexport interface CustomStateConfig {\n\t/**\n\t * Custom implementation. A `StateStore` is used as is; a `StateProvider`\n\t * still works, but cannot lock — deploy warns `StateStoreWithoutLocking`.\n\t */\n\tprovider: StateProvider | StateStore;\n}\n\n/**\n * State configuration types.\n */\nexport type StateConfig =\n\t| LocalStateConfig\n\t| SSMStateConfig\n\t| S3StateConfig\n\t| CustomStateConfig;\n\n/**\n * Check if value is a StateProvider implementation.\n */\nexport function isStateProvider(value: unknown): value is StateProvider {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\ttypeof (value as StateProvider).read === 'function' &&\n\t\ttypeof (value as StateProvider).write === 'function'\n\t);\n}\n\nexport interface CreateStateStoreConfig {\n\t/** State config from workspace */\n\tconfig?: StateConfig;\n\t/** Workspace root directory (for local provider) */\n\tworkspaceRoot: string;\n\t/** Workspace name (for SSM parameter path) */\n\tworkspaceName: string;\n}\n","/**\n * Deploy state store.\n *\n * `StateProvider` reads and writes a whole stage at once, with nothing to stop\n * two runs of the same stage from overwriting each other and nowhere to record\n * a resource the moment it exists. A `StateStore` adds the three things a\n * deploy needs to be safe against crashes and concurrent runs:\n *\n * - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the\n * holder, instead of racing the first.\n * - Versioned `read` and conditional `write`: a write names the version it was\n * based on, and raises `StateVersionConflict` when the stored state moved on\n * underneath it, so nothing is lost silently even without the lock.\n * - Per-resource records: `putResource` journals one resource at a time, so a\n * run that dies half way leaves behind the ids of everything it created.\n *\n * Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A\n * custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it\n * working but cannot lock.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { hostname, userInfo } from 'node:os';\nimport {\n\ttype CreateStateStoreConfig,\n\tisStateProvider,\n\ttype S3StateConfig,\n\ttype SSMStateConfig,\n} from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/** Opaque: a file hash, an SSM parameter version or an S3 ETag. */\nexport type StateVersion = string;\n\n/**\n * `pending` is written before the call that creates the resource, `ready`\n * once the target has returned its id. A run that finds a `pending` record\n * knows a previous run may have created the resource and should look for it\n * before creating another.\n */\nexport type ResourceStatus = 'pending' | 'ready';\n\nexport interface ResourceRecord {\n\t/** Unique within the stage, `<type>:<name>` — e.g. `application:api`. */\n\tkey: string;\n\t/** What kind of resource it is — e.g. `application`, `postgres`. */\n\ttype: string;\n\t/** The id the target gave it. Absent while `pending`. */\n\tid?: string;\n\tstatus: ResourceStatus;\n\t/** Whatever the target needs to adopt the resource on a later run. */\n\tdata?: Record<string, unknown>;\n\tupdatedAt: string;\n}\n\n/** A resource as a caller hands it to `putResource`; the store stamps it. */\nexport type ResourceInput = Omit<ResourceRecord, 'updatedAt'>;\n\nexport interface StoredStageState {\n\tstate: DokployStageState;\n\tresources: Record<string, ResourceRecord>;\n\t/** Pass back as `expectedVersion` to write on top of exactly this read. */\n\tversion: StateVersion;\n}\n\nexport interface WriteOptions {\n\t/**\n\t * The version this write is based on — `null` to create state that must\n\t * not exist yet.\n\t */\n\texpectedVersion: StateVersion | null;\n}\n\nexport interface ResourceWriteOptions {\n\t/**\n\t * Omitted, the record is applied on top of whatever is stored now (still\n\t * conditionally, so a concurrent write raises rather than being lost).\n\t */\n\texpectedVersion?: StateVersion;\n}\n\nexport interface LockHolder {\n\t/** Unique per acquisition, so a release never removes someone else's lock. */\n\tid: string;\n\towner: string;\n\thost: string;\n\tpid: number;\n\tacquiredAt: string;\n\t/** What the holder is doing, e.g. `deploy`. */\n\toperation?: string;\n}\n\nexport interface LockOptions {\n\toperation?: string;\n}\n\nexport interface StateLock {\n\treadonly stage: string;\n\treadonly holder: LockHolder;\n\t/** Removes the lock if it is still this one; safe to call twice. */\n\trelease(): Promise<void>;\n}\n\nexport interface StateStore {\n\t/** Takes the stage's lock, or raises `StateLocked` naming its holder. */\n\tlock(stage: string, options?: LockOptions): Promise<StateLock>;\n\t/**\n\t * Removes the stage's lock whoever holds it, for a run that crashed with\n\t * it held. Returns the holder it removed, or `null` if there was none.\n\t */\n\tforceUnlock(stage: string): Promise<LockHolder | null>;\n\tread(stage: string): Promise<StoredStageState | null>;\n\t/** Replaces the stage's state, keeping its resource records. */\n\twrite(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion>;\n\tputResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion>;\n\tdeleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion>;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class StateLocked extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly holder: LockHolder | null,\n\t\treadonly location: string,\n\t) {\n\t\tconst by = holder\n\t\t\t? `${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}${holder.operation ? ` for '${holder.operation}'` : ''}`\n\t\t\t: 'a run that did not record who it was';\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' is locked by ${by} (${location}). ` +\n\t\t\t\t`Wait for that run to finish; if it crashed, release the lock with ` +\n\t\t\t\t`\\`gkm state:unlock --stage ${stage}\\`.`,\n\t\t);\n\t\tthis.name = 'StateLocked';\n\t}\n}\n\nexport class StateVersionConflict extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly expectedVersion: StateVersion | null,\n\t\treadonly actualVersion: StateVersion | null,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' changed since it was read ` +\n\t\t\t\t`(expected ${expectedVersion ? `version ${expectedVersion}` : 'no state'}, ` +\n\t\t\t\t`found ${actualVersion ? `version ${actualVersion}` : 'no state'}). ` +\n\t\t\t\t`Another run wrote it: read the state again and re-apply the change, ` +\n\t\t\t\t`and hold the stage's lock so runs take turns.`,\n\t\t);\n\t\tthis.name = 'StateVersionConflict';\n\t}\n}\n\nexport class StageStateMissing extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`There is no deploy state for stage '${stage}' to record a resource in. ` +\n\t\t\t\t`Write the stage's state first (write with expectedVersion: null).`,\n\t\t);\n\t\tthis.name = 'StageStateMissing';\n\t}\n}\n\nexport class StateUnreadable extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly location: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' at ${location} is not valid JSON. ` +\n\t\t\t\t`Restore it from a backup (or the provider's history) rather than ` +\n\t\t\t\t`deploying without it — a deploy without state recreates every resource.`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'StateUnreadable';\n\t}\n}\n\nexport class StateSchemaTooNew extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly schemaVersion: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, ` +\n\t\t\t\t`newer than this @geekmidas/cli understands (${STATE_SCHEMA_VERSION}). ` +\n\t\t\t\t`Upgrade @geekmidas/cli to the version that wrote it.`,\n\t\t);\n\t\tthis.name = 'StateSchemaTooNew';\n\t}\n}\n\n/**\n * Not thrown: emitted (once per store) when a custom `StateProvider` stands\n * in for a store, so a deploy that cannot be protected from a concurrent one\n * says so.\n */\nexport class StateStoreWithoutLocking extends Error {\n\tconstructor(readonly provider: string) {\n\t\tsuper(\n\t\t\t`The custom state provider '${provider}' has no lock and no versions, ` +\n\t\t\t\t`so two deploys of the same stage can still overwrite each other. ` +\n\t\t\t\t`Implement StateStore (lock, versioned read, conditional write) or ` +\n\t\t\t\t`use the 'local', 'ssm' or 's3' state provider.`,\n\t\t);\n\t\tthis.name = 'StateStoreWithoutLocking';\n\t}\n}\n\nexport class StateStoreNeedsWorkspaceName extends Error {\n\tconstructor(readonly provider: string) {\n\t\tsuper(\n\t\t\t`The '${provider}' state provider keys state by workspace name. ` +\n\t\t\t\t`Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'StateStoreNeedsWorkspaceName';\n\t}\n}\n\nexport class UnknownStateProvider extends Error {\n\tconstructor(readonly config: unknown) {\n\t\tsuper(\n\t\t\t`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', ` +\n\t\t\t\t`'s3', or an object implementing StateStore or StateProvider.`,\n\t\t);\n\t\tthis.name = 'UnknownStateProvider';\n\t}\n}\n\n/** A lock or write that could not get at the state backend in time. */\nexport class StateStoreBusy extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly location: string,\n\t) {\n\t\tsuper(\n\t\t\t`Timed out waiting to write deploy state for stage '${stage}' (${location}). ` +\n\t\t\t\t`If no other gkm process is running, remove ${location}.`,\n\t\t);\n\t\tthis.name = 'StateStoreBusy';\n\t}\n}\n\n// ============================================================================\n// Document format\n// ============================================================================\n\nexport const STATE_SCHEMA_VERSION = 2;\n\n/**\n * What a store persists. v1 was `DokployStageState` on its own; v2 wraps it\n * so resource records and a write serial can sit beside it. The serial\n * changes every write, so content-addressed versions (a file hash, an S3\n * ETag) never repeat — a writer holding a version from two writes ago cannot\n * match by accident.\n */\nexport interface StateDocument {\n\tschemaVersion: typeof STATE_SCHEMA_VERSION;\n\tstage: string;\n\tserial: number;\n\tstate: DokployStageState;\n\tresources: Record<string, ResourceRecord>;\n\tupdatedAt: string;\n}\n\nexport type DecodedDocument =\n\t| { schemaVersion: 2; document: StateDocument }\n\t| { schemaVersion: 1; state: DokployStageState };\n\nexport function decodeDocument(\n\tstage: string,\n\tbody: string,\n\tlocation: string,\n): DecodedDocument {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(body);\n\t} catch (error) {\n\t\tthrow new StateUnreadable(stage, location, error);\n\t}\n\n\tif (typeof parsed !== 'object' || parsed === null) {\n\t\tthrow new StateUnreadable(stage, location, parsed);\n\t}\n\n\tconst schemaVersion = (parsed as { schemaVersion?: unknown }).schemaVersion;\n\tif (schemaVersion === undefined) {\n\t\treturn { schemaVersion: 1, state: parsed as DokployStageState };\n\t}\n\tif (schemaVersion === STATE_SCHEMA_VERSION) {\n\t\treturn { schemaVersion: 2, document: parsed as StateDocument };\n\t}\n\tthrow new StateSchemaTooNew(stage, schemaVersion);\n}\n\nexport function encodeDocument(document: StateDocument): string {\n\treturn JSON.stringify(document, null, 2);\n}\n\n/**\n * v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,\n * so the first journaled deploy after an upgrade adopts them rather than\n * treating them as unknown.\n */\nexport function migrateV1(\n\tstage: string,\n\tstate: DokployStageState,\n): StateDocument {\n\tconst now = new Date().toISOString();\n\tconst resources: Record<string, ResourceRecord> = {};\n\tconst seed = (type: string, name: string | null, id: string | undefined) => {\n\t\tif (!id) return;\n\t\tconst key = name ? `${type}:${name}` : type;\n\t\tresources[key] = { key, type, id, status: 'ready', updatedAt: now };\n\t};\n\n\tseed('project', null, state.projectId);\n\tseed('environment', null, state.environmentId);\n\tfor (const [app, id] of Object.entries(state.applications ?? {})) {\n\t\tseed('application', app, id);\n\t}\n\tseed('postgres', null, state.services?.postgresId);\n\tseed('redis', null, state.services?.redisId);\n\tseed('backup-destination', null, state.backups?.destinationId);\n\n\treturn {\n\t\tschemaVersion: STATE_SCHEMA_VERSION,\n\t\tstage,\n\t\tserial: 1,\n\t\tstate,\n\t\tresources,\n\t\tupdatedAt: now,\n\t};\n}\n\nexport function newLockHolder(options?: LockOptions): LockHolder {\n\tlet owner = 'unknown';\n\ttry {\n\t\towner = userInfo().username;\n\t} catch {\n\t\t// No passwd entry (a container running as an arbitrary uid).\n\t}\n\treturn {\n\t\tid: randomUUID(),\n\t\towner,\n\t\thost: hostname(),\n\t\tpid: process.pid,\n\t\tacquiredAt: new Date().toISOString(),\n\t\toperation: options?.operation,\n\t};\n}\n\nexport function contentVersion(body: string): StateVersion {\n\treturn createHash('sha256').update(body).digest('hex').slice(0, 16);\n}\n\n// ============================================================================\n// Shared implementation\n// ============================================================================\n\nexport interface RawState {\n\tbody: string;\n\tversion: StateVersion;\n}\n\n/**\n * A store over a backend that can read and conditionally write one blob per\n * stage, and create a lock object only if it does not exist. Migration and\n * resource records are the same for every such backend, so they live here.\n */\nexport abstract class DocumentStateStore implements StateStore {\n\t/** Where the stage's state lives, for messages. */\n\tprotected abstract location(stage: string): string;\n\tprotected abstract readRaw(stage: string): Promise<RawState | null>;\n\t/**\n\t * Writes only if the stored version is `expectedVersion` (`null`: only if\n\t * nothing is stored), else raises `StateVersionConflict`.\n\t */\n\tprotected abstract writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion>;\n\t/** Keeps the v1 body as it was. Never replaces an existing backup. */\n\tprotected abstract writeV1Backup(stage: string, body: string): Promise<void>;\n\t/** Creates the lock only if absent, else raises `StateLocked`. */\n\tprotected abstract createLock(\n\t\tstage: string,\n\t\tholder: LockHolder,\n\t): Promise<void>;\n\tprotected abstract readLock(stage: string): Promise<LockHolder | null>;\n\t/** Removes the lock — only if it is `holder`'s, when one is given. */\n\tprotected abstract removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void>;\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tconst holder = newLockHolder(options);\n\t\tawait this.createLock(stage, holder);\n\t\tlet released = false;\n\t\treturn {\n\t\t\tstage,\n\t\t\tholder,\n\t\t\trelease: async () => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tawait this.removeLock(stage, holder);\n\t\t\t},\n\t\t};\n\t}\n\n\tasync forceUnlock(stage: string): Promise<LockHolder | null> {\n\t\tconst holder = await this.readLock(stage);\n\t\tawait this.removeLock(stage, null);\n\t\treturn holder;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst current = await this.readDocument(stage);\n\t\tif (!current) return null;\n\t\treturn {\n\t\t\tstate: current.document.state,\n\t\t\tresources: current.document.resources,\n\t\t\tversion: current.version,\n\t\t};\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tif (options.expectedVersion === null) {\n\t\t\tconst document: StateDocument = {\n\t\t\t\tschemaVersion: STATE_SCHEMA_VERSION,\n\t\t\t\tstage,\n\t\t\t\tserial: 1,\n\t\t\t\tstate,\n\t\t\t\tresources: {},\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\treturn this.writeRaw(stage, encodeDocument(document), null);\n\t\t}\n\t\treturn this.mutate(stage, options.expectedVersion, (document) => ({\n\t\t\t...document,\n\t\t\tstate,\n\t\t}));\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\treturn this.mutate(stage, options?.expectedVersion, (document) => ({\n\t\t\t...document,\n\t\t\tresources: {\n\t\t\t\t...document.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t}));\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\treturn this.mutate(stage, options?.expectedVersion, (document) => {\n\t\t\tconst { [key]: _removed, ...resources } = document.resources;\n\t\t\treturn { ...document, resources };\n\t\t});\n\t}\n\n\tprivate async mutate(\n\t\tstage: string,\n\t\texpectedVersion: StateVersion | undefined,\n\t\tchange: (document: StateDocument) => StateDocument,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.readDocument(stage);\n\t\tif (!current) {\n\t\t\tif (expectedVersion !== undefined) {\n\t\t\t\tthrow new StateVersionConflict(stage, expectedVersion, null);\n\t\t\t}\n\t\t\tthrow new StageStateMissing(stage);\n\t\t}\n\t\tif (expectedVersion !== undefined && current.version !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, current.version);\n\t\t}\n\n\t\tconst next = change(current.document);\n\t\tnext.serial = current.document.serial + 1;\n\t\tnext.updatedAt = new Date().toISOString();\n\t\treturn this.writeRaw(stage, encodeDocument(next), current.version);\n\t}\n\n\t/**\n\t * Reads the stage's document, migrating v1 in place on the way. The\n\t * migration is itself a conditional write: if another run migrated (or\n\t * wrote) first, its result is read instead.\n\t */\n\tprivate async readDocument(\n\t\tstage: string,\n\t\tattempt = 0,\n\t): Promise<{ document: StateDocument; version: StateVersion } | null> {\n\t\tconst raw = await this.readRaw(stage);\n\t\tif (!raw) return null;\n\n\t\tconst decoded = decodeDocument(stage, raw.body, this.location(stage));\n\t\tif (decoded.schemaVersion === 2) {\n\t\t\treturn { document: decoded.document, version: raw.version };\n\t\t}\n\n\t\tawait this.writeV1Backup(stage, raw.body);\n\t\tconst document = migrateV1(stage, decoded.state);\n\t\ttry {\n\t\t\tconst version = await this.writeRaw(\n\t\t\t\tstage,\n\t\t\t\tencodeDocument(document),\n\t\t\t\traw.version,\n\t\t\t);\n\t\t\treturn { document, version };\n\t\t} catch (error) {\n\t\t\tif (error instanceof StateVersionConflict && attempt < 2) {\n\t\t\t\treturn this.readDocument(stage, attempt + 1);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Resolution\n// ============================================================================\n\nexport function isStateStore(value: unknown): value is StateStore {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\ttypeof (value as StateStore).lock === 'function' &&\n\t\ttypeof (value as StateStore).read === 'function' &&\n\t\ttypeof (value as StateStore).write === 'function' &&\n\t\ttypeof (value as StateStore).putResource === 'function'\n\t);\n}\n\nexport interface CreateStateStoreOptions extends CreateStateStoreConfig {\n\t/** Where `StateStoreWithoutLocking` goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\n/**\n * The store for a workspace's `state` config:\n *\n * - none / `'local'`: `LocalStateStore` under `.gkm/`\n * - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so\n * a stale local copy can never win over it\n * - `'s3'`: `S3StateStore`\n * - a `StateStore` object: used as is\n * - a `StateProvider` object: wrapped by `LegacyStateStore`\n */\nexport async function createStateStore(\n\toptions: CreateStateStoreOptions,\n): Promise<StateStore> {\n\tconst { config, workspaceRoot, workspaceName } = options;\n\n\tif (!config || config.provider === 'local') {\n\t\tconst { LocalStateStore } = await import('./LocalStateStore');\n\t\treturn new LocalStateStore(workspaceRoot);\n\t}\n\n\tif (isStateStore(config.provider)) {\n\t\treturn config.provider;\n\t}\n\n\tif (isStateProvider(config.provider)) {\n\t\tconst { LegacyStateStore } = await import('./LegacyStateStore');\n\t\treturn new LegacyStateStore(config.provider, { warn: options.warn });\n\t}\n\n\tif (config.provider === 'ssm') {\n\t\tif (!workspaceName) throw new StateStoreNeedsWorkspaceName('ssm');\n\t\tconst { SSMStateStore } = await import('./SSMStateStore');\n\t\tconst ssm = config as SSMStateConfig;\n\t\treturn SSMStateStore.create({\n\t\t\tworkspaceName,\n\t\t\tregion: ssm.region,\n\t\t\tprofile: ssm.profile,\n\t\t});\n\t}\n\n\tif (config.provider === 's3') {\n\t\tif (!workspaceName) throw new StateStoreNeedsWorkspaceName('s3');\n\t\tconst { S3StateStore } = await import('./S3StateStore');\n\t\tconst s3 = config as S3StateConfig;\n\t\treturn S3StateStore.create({\n\t\t\tworkspaceName,\n\t\t\tbucket: s3.bucket,\n\t\t\tprefix: s3.prefix,\n\t\t\tregion: s3.region,\n\t\t\tprofile: s3.profile,\n\t\t});\n\t}\n\n\tthrow new UnknownStateProvider(config);\n}\n"],"mappings":"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"}
|
|
@@ -84,7 +84,7 @@ async function bundleServer(options) {
|
|
|
84
84
|
const { toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets } = await import("./storage-BzaUxmqe.mjs").then((n) => n.r);
|
|
85
85
|
const { FileSecretsStore, secretsStoreFor } = await import("./store-DeGATCS-.mjs").then((n) => n.r);
|
|
86
86
|
const { ConfigNotFound, loadWorkspaceSettings } = await import("./config.mjs");
|
|
87
|
-
const { encryptSecrets, generateDefineOptions } = await import("./index.mjs").then((n) => n.
|
|
87
|
+
const { encryptSecrets, generateDefineOptions } = await import("./index.mjs").then((n) => n.a);
|
|
88
88
|
const store = await loadWorkspaceSettings().then((workspace) => secretsStoreFor(workspace, stage), (error) => {
|
|
89
89
|
if (error instanceof ConfigNotFound) return new FileSecretsStore(process.cwd());
|
|
90
90
|
throw error;
|
|
@@ -151,4 +151,4 @@ async function bundleServer(options) {
|
|
|
151
151
|
//#endregion
|
|
152
152
|
export { bundleServer };
|
|
153
153
|
|
|
154
|
-
//# sourceMappingURL=bundler-
|
|
154
|
+
//# sourceMappingURL=bundler-ouDdNvk9.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundler-URImKj-A.mjs","names":[],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * The esbuild binary, resolved from this package rather than from PATH.\n *\n * It used to be `npx esbuild`, which is two assumptions: that esbuild is\n * installed somewhere npx can find, and that the *app's* directory is where to\n * look. Neither holds — the CLI did not depend on esbuild at all, so a deploy\n * run from an app that had not installed it failed at the bundle step with\n * `sh: esbuild: command not found`, after provisioning had already happened.\n *\n * The same fix `bin/gkm.mjs` makes for tsx: resolve from this package's own\n * `node_modules`, so the tool the CLI shells out to is the one it depends on.\n */\nfunction esbuildBinary(): string {\n\tconst require = createRequire(import.meta.url);\n\n\ttry {\n\t\t// The package root, then its bin — `require.resolve('esbuild')` gives the\n\t\t// JS entry point, which is a library rather than the CLI.\n\t\tconst pkg = require.resolve('esbuild/package.json');\n\t\treturn join(dirname(pkg), 'bin', 'esbuild');\n\t} catch {\n\t\t// Better a PATH lookup than a hard failure: a project that installed\n\t\t// esbuild itself still works.\n\t\treturn 'esbuild';\n\t}\n}\n\n/**\n * Collect the environment variables a build cannot proceed without.\n *\n * Uses the SnifferEnvironmentParser to detect which env vars each service\n * reads, and keeps only the ones it cannot do without. `markOptional` is what\n * separates them: the sniffer already knows which reads went through\n * `.optional()` or `.default()`, and asking for that distinction is the\n * difference between a build that stops and one that proceeds correctly.\n *\n * Without it, a key that is *absent by design* failed the build. A surface\n * publishes `AUTH_COOKIE_DOMAIN` only when there is a domain to widen a cookie\n * to — one host has nothing to share it with, so the derivation correctly\n * yields nothing, and Better Auth reads it as optional for exactly that\n * reason. Treating every sniffed read as required made the honest answer\n * indistinguishable from a missing secret.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated, sorted names of the required variables\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\t// A key read optionally by one construct and required by another stays\n\t// required: the strictest read is the one that fails at runtime, and taking\n\t// the union of the required reads is what says so.\n\tconst required = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tfor (const name of await construct.getEnvironment({ markOptional: true })) {\n\t\t\tif (!name.endsWith('?')) required.add(name);\n\t\t}\n\t}\n\n\treturn Array.from(required).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\tesbuildBinary(),\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { FileSecretsStore, secretsStoreFor } = await import(\n\t\t\t'../secrets/store'\n\t\t);\n\t\tconst { ConfigNotFound, loadWorkspaceSettings } = await import('../config');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\t// The stage's own store — SSM for a deployed stage kept there. Outside\n\t\t// any workspace nothing names one, so it is the file.\n\t\tconst store = await loadWorkspaceSettings().then(\n\t\t\t(workspace) => secretsStoreFor(workspace, stage),\n\t\t\t(error) => {\n\t\t\t\tif (error instanceof ConfigNotFound) {\n\t\t\t\t\treturn new FileSecretsStore(process.cwd());\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\tlet secrets = await store.read(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait store.write(stage, secrets);\n\t\t\tconsole.log(` ✓ Created the \"${stage}\" secrets (${store.name})`);\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to the \"${stage}\" secrets using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bundler-ouDdNvk9.mjs","names":[],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * The esbuild binary, resolved from this package rather than from PATH.\n *\n * It used to be `npx esbuild`, which is two assumptions: that esbuild is\n * installed somewhere npx can find, and that the *app's* directory is where to\n * look. Neither holds — the CLI did not depend on esbuild at all, so a deploy\n * run from an app that had not installed it failed at the bundle step with\n * `sh: esbuild: command not found`, after provisioning had already happened.\n *\n * The same fix `bin/gkm.mjs` makes for tsx: resolve from this package's own\n * `node_modules`, so the tool the CLI shells out to is the one it depends on.\n */\nfunction esbuildBinary(): string {\n\tconst require = createRequire(import.meta.url);\n\n\ttry {\n\t\t// The package root, then its bin — `require.resolve('esbuild')` gives the\n\t\t// JS entry point, which is a library rather than the CLI.\n\t\tconst pkg = require.resolve('esbuild/package.json');\n\t\treturn join(dirname(pkg), 'bin', 'esbuild');\n\t} catch {\n\t\t// Better a PATH lookup than a hard failure: a project that installed\n\t\t// esbuild itself still works.\n\t\treturn 'esbuild';\n\t}\n}\n\n/**\n * Collect the environment variables a build cannot proceed without.\n *\n * Uses the SnifferEnvironmentParser to detect which env vars each service\n * reads, and keeps only the ones it cannot do without. `markOptional` is what\n * separates them: the sniffer already knows which reads went through\n * `.optional()` or `.default()`, and asking for that distinction is the\n * difference between a build that stops and one that proceeds correctly.\n *\n * Without it, a key that is *absent by design* failed the build. A surface\n * publishes `AUTH_COOKIE_DOMAIN` only when there is a domain to widen a cookie\n * to — one host has nothing to share it with, so the derivation correctly\n * yields nothing, and Better Auth reads it as optional for exactly that\n * reason. Treating every sniffed read as required made the honest answer\n * indistinguishable from a missing secret.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated, sorted names of the required variables\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\t// A key read optionally by one construct and required by another stays\n\t// required: the strictest read is the one that fails at runtime, and taking\n\t// the union of the required reads is what says so.\n\tconst required = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tfor (const name of await construct.getEnvironment({ markOptional: true })) {\n\t\t\tif (!name.endsWith('?')) required.add(name);\n\t\t}\n\t}\n\n\treturn Array.from(required).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\tesbuildBinary(),\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { FileSecretsStore, secretsStoreFor } = await import(\n\t\t\t'../secrets/store'\n\t\t);\n\t\tconst { ConfigNotFound, loadWorkspaceSettings } = await import('../config');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\t// The stage's own store — SSM for a deployed stage kept there. Outside\n\t\t// any workspace nothing names one, so it is the file.\n\t\tconst store = await loadWorkspaceSettings().then(\n\t\t\t(workspace) => secretsStoreFor(workspace, stage),\n\t\t\t(error) => {\n\t\t\t\tif (error instanceof ConfigNotFound) {\n\t\t\t\t\treturn new FileSecretsStore(process.cwd());\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\tlet secrets = await store.read(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait store.write(stage, secrets);\n\t\t\tconsole.log(` ✓ Created the \"${stage}\" secrets (${store.name})`);\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to the \"${stage}\" secrets using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as discover } from "./discover-Cf2jAcgF.mjs";
|
|
2
|
-
import { h as isWorkspaceConfig, l as processConfig, o as getAppGkmConfig, t as allConstructGlobs } from "./workspace-
|
|
2
|
+
import { h as isWorkspaceConfig, l as processConfig, o as getAppGkmConfig, t as allConstructGlobs } from "./workspace-Bp1xVaOz.mjs";
|
|
3
3
|
import { existsSync, realpathSync } from "node:fs";
|
|
4
4
|
import { dirname, join, parse, resolve, sep } from "node:path";
|
|
5
5
|
import { kebabCase } from "@geekmidas/manifest";
|
|
@@ -454,4 +454,4 @@ async function loadWorkspaceAppInfo(cwd = process.cwd()) {
|
|
|
454
454
|
//#endregion
|
|
455
455
|
export { defineConfig as a, loadWorkspaceAppInfo as c, parseModuleConfig as d, appKey as f, WorkspaceDeclaresNoConstructs as i, loadWorkspaceConfig as l, NotABackendApp as n, loadAppConfig as o, derivedApps as p, NotInAnApp as r, loadConfig as s, ConfigNotFound as t, loadWorkspaceSettings as u };
|
|
456
456
|
|
|
457
|
-
//# sourceMappingURL=config-
|
|
457
|
+
//# sourceMappingURL=config-EuzFEkcH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-EuzFEkcH.mjs","names":[],"sources":["../src/workspace/derive.ts","../src/config.ts"],"sourcesContent":["/**\n * The apps a manifest implies.\n *\n * `gkm.config.ts` used to carry an `apps` block naming every app: its type, its\n * path, its port, its framework, which other apps it depended on. Every one of\n * those facts was already declared. A `StaticSite('Web', { path: 'apps/web' })`\n * and an `apps.web = { type: 'web', path: 'apps/web', framework: 'vite',\n * dependencies: ['api'] }` are the same statement written twice, and only one of\n * them was checked against anything — so the copy in config was free to drift,\n * and a surface config had no entry for simply never deployed.\n *\n * So the list comes from the graph. A `site` is an app, and so is every\n * `rest-api` — one surface, one container, at the `path` it declared.\n *\n * What config still supplies is what no graph can answer: the backends a cache\n * and a mailer resolve to, the deploy endpoint, the stage's domain.\n */\n\nimport {\n\ttype AppSpec,\n\ttype ConstructManifest,\n\tkebabCase,\n} from '@geekmidas/manifest';\nimport type {\n\tAppDomainConfig,\n\tDeployTarget,\n\tFramework,\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from './types.js';\n\n/** Which frontend framework builds each site variant. */\nconst FRAMEWORKS: Record<string, Framework> = {\n\tstatic: 'vite',\n\tnext: 'nextjs',\n\ttanstack: 'tanstack-start',\n};\n\n/**\n * The app key a construct id becomes.\n *\n * The same kebab form every physical name is built from, so `Api` is `api` in\n * the config, `production-kitchen-sink-api` on Dokploy, and `API_URL` in an\n * environment — one id, three renderings, no second list.\n */\nexport function appKey(id: string): string {\n\treturn kebabCase(id);\n}\n\n/**\n * The app serving a surface: its own, always.\n *\n * Every surface gets a container. There is no arrangement in which one runs\n * inside another — two surfaces in a process share a filesystem, an\n * environment, and every credential either was granted, so an auth server\n * beside an API is one bug in the API away from being read by it.\n */\nexport function hostOf(\n\tmanifest: ConstructManifest,\n\tid: string,\n): string | undefined {\n\tconst declaration = manifest[id];\n\tif (!declaration || declaration.kind !== 'rest-api') return undefined;\n\n\treturn id;\n}\n\n/**\n * Local ports, assigned so that adding a site does not renumber the others.\n *\n * Backends first, then the site holding the base domain, then the rest\n * alphabetically. The ordering is arbitrary but it is *fixed*, which is the\n * property that matters: a port that moves when an unrelated app is added is a\n * port nobody can put in a bookmark. A construct that names its own port keeps\n * it, and is skipped here.\n */\nfunction assignPorts(apps: Record<string, NormalizedAppConfig>): void {\n\tconst rank = (\n\t\tname: string,\n\t\tapp: NormalizedAppConfig,\n\t): [number, number, string] => [\n\t\tapp.type === 'backend' ? 0 : app.type === 'web' ? 1 : 2,\n\t\tapp.root ? 0 : 1,\n\t\tname,\n\t];\n\n\tconst ordered = Object.entries(apps).sort(([aName, a], [bName, b]) => {\n\t\tconst [at, ar, an] = rank(aName, a);\n\t\tconst [bt, br, bn] = rank(bName, b);\n\t\treturn at - bt || ar - br || an.localeCompare(bn);\n\t});\n\n\tconst taken = new Set(\n\t\tordered.map(([, app]) => app.port).filter((p): p is number => !!p),\n\t);\n\n\tlet next = 3000;\n\tfor (const [, app] of ordered) {\n\t\tif (app.port) continue;\n\t\twhile (taken.has(next)) next += 1;\n\t\tapp.port = next;\n\t\ttaken.add(next);\n\t}\n}\n\n/**\n * Whichever site holds the base domain.\n *\n * The convention first — a site named `web` — because it is one people already\n * rely on. Then an explicit `root: true`. A project with one site needs neither.\n */\nfunction markRoot(apps: Record<string, NormalizedAppConfig>): void {\n\tconst sites = Object.entries(apps).filter(([, a]) => a.type === 'web');\n\tif (sites.length === 0) return;\n\tif (sites.some(([, a]) => a.root)) return;\n\n\tconst chosen =\n\t\tsites.find(([name]) => name === 'web') ??\n\t\t(sites.length === 1 ? sites[0] : undefined);\n\tif (chosen) chosen[1].root = true;\n}\n\nexport function derivedApps(\n\tmanifest: ConstructManifest,\n\tworkspace: NormalizedWorkspace,\n): Record<string, NormalizedAppConfig> {\n\tconst apps: Record<string, NormalizedAppConfig> = {};\n\tconst defaultTarget: DeployTarget = workspace.deploy?.default ?? 'dokploy';\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\t// Three kinds are apps: a site, a mobile app and an HTTP surface. A worker\n\t\t// is not one — it names the process that runs a cron or a subscriber, and\n\t\t// that process is the app's server, which already exists. Everything else\n\t\t// is a resource one of them uses.\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\t// No opt-in to be had. Each says where it lives.\n\t\tconst spec: AppSpec =\n\t\t\tdeclaration.kind === 'site' || declaration.kind === 'mobile-app'\n\t\t\t\t? declaration.app\n\t\t\t\t: {\n\t\t\t\t\t\tpath: declaration.path,\n\t\t\t\t\t\t...(declaration.telescope ? { telescope: true } : {}),\n\t\t\t\t\t};\n\n\t\tconst name = appKey(id);\n\t\t// A config entry of the same name still wins, so a workspace can override\n\t\t// one field without restating the app. Nothing in this repo needs to.\n\t\tconst configured = workspace.apps[name];\n\n\t\tapps[name] = {\n\t\t\t...configured,\n\t\t\ttype:\n\t\t\t\tdeclaration.kind === 'site'\n\t\t\t\t\t? 'web'\n\t\t\t\t\t: declaration.kind === 'mobile-app'\n\t\t\t\t\t\t? 'mobile'\n\t\t\t\t\t\t: 'backend',\n\t\t\tpath: spec.path,\n\t\t\tport: spec.port ?? configured?.port ?? 0,\n\t\t\t// The label it answers on, under a stage's domain and locally alike.\n\t\t\t...((declaration.kind === 'rest-api' || declaration.kind === 'site') &&\n\t\t\tdeclaration.subdomain\n\t\t\t\t? { subdomain: declaration.subdomain }\n\t\t\t\t: {}),\n\t\t\tdependencies: [],\n\t\t\tresolvedDeployTarget: configured?.resolvedDeployTarget ?? defaultTarget,\n\t\t\t...(declaration.kind === 'site'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? FRAMEWORKS[declaration.variant],\n\t\t\t\t\t\t...(declaration.root ? { root: true } : {}),\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'mobile-app'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? declaration.variant,\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// One glob fans out to the six the build reads, because each generator\n\t\t\t// already inspects every export and keeps what it recognises. A\n\t\t\t// per-kind field still wins where one was given.\n\t\t\t...(spec.telescope !== undefined ? { telescope: spec.telescope } : {}),\n\t\t\t...(spec.openapi !== undefined ? { openapi: spec.openapi } : {}),\n\t\t\t...(spec.runtime !== undefined ? { runtime: spec.runtime } : {}),\n\t\t\t...(spec.env !== undefined ? { env: spec.env } : {}),\n\t\t\t...(spec.entry !== undefined ? { entry: spec.entry } : {}),\n\t\t\t...(configured?.domain\n\t\t\t\t? { domain: configured.domain as AppDomainConfig }\n\t\t\t\t: {}),\n\t\t} as NormalizedAppConfig;\n\t}\n\n\t// Build order, from the edges rather than from a hand-kept list. A site that\n\t// depends on a surface depends on the app that serves it — which for a\n\t// mounted auth server is its host, not a container that does not exist.\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (\n\t\t\tdeclaration.kind !== 'site' &&\n\t\t\tdeclaration.kind !== 'mobile-app' &&\n\t\t\tdeclaration.kind !== 'rest-api'\n\t\t)\n\t\t\tcontinue;\n\n\t\tconst app = apps[appKey(id)];\n\t\tif (!app) continue;\n\n\t\t// A site names its edges `dependencies`; a surface names them `calls`.\n\t\tconst edges =\n\t\t\tdeclaration.kind === 'rest-api'\n\t\t\t\t? (declaration.calls ?? [])\n\t\t\t\t: (declaration.dependencies ?? []);\n\n\t\tconst names = new Set<string>();\n\t\tfor (const edge of edges) {\n\t\t\tif (edge.kind !== 'rest-api') continue;\n\t\t\tconst host = hostOf(manifest, edge.target);\n\t\t\tif (!host) continue;\n\t\t\tconst key = appKey(host);\n\t\t\tif (key !== appKey(id) && apps[key]) names.add(key);\n\t\t}\n\n\t\tapp.dependencies = [...names].sort();\n\t}\n\n\tmarkRoot(apps);\n\tassignPorts(apps);\n\n\t// Apps config declared that the graph knows nothing about — a `type: 'mobile'`\n\t// entry, or a hand-run process. Kept rather than dropped: config is still the\n\t// escape hatch for what nothing declares.\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\tif (!apps[name]) apps[name] = app;\n\t}\n\n\treturn apps;\n}\n","import { existsSync, realpathSync } from 'node:fs';\nimport { dirname, join, parse, resolve, sep } from 'node:path';\nimport { discover } from './reconcile/discover.js';\nimport type { GkmConfig } from './types.js';\nimport { derivedApps } from './workspace/derive.js';\nimport {\n\tallConstructGlobs,\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\n// What `gkm dev`'s discovery endpoint answers, for the tools that read it.\nexport type {\n\tAppStatus,\n\tDataApiKind,\n\tDiscoveredApp,\n\tDiscoveredDataApi,\n\tDiscoveredWorkspace,\n\tDiscoveryEvent,\n\tDiscoveryResponse,\n} from './dev/discovery.js';\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\nexport type { DevConfig } from './workspace/types.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new ConfigNotFound(cwd);\n}\n\n/** No gkm config in a directory or any of its parents. */\nexport class ConfigNotFound extends Error {\n\tconstructor(readonly cwd: string) {\n\t\tsuper(\n\t\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t\t);\n\t\tthis.name = 'ConfigNotFound';\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\n/**\n * The workspace a directory belongs to, as its config states it — without\n * discovering its constructs.\n *\n * For callers that need a fact the config holds (where a stage's secrets are\n * stored) and nothing the graph adds. `loadWorkspaceConfig` imports every\n * construct module to derive the apps, which is a cost a secrets read should\n * not pay.\n */\nexport async function loadWorkspaceSettings(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig['workspace']> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn processConfig(config, workspaceRoot).workspace;\n}\n\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn withDerivedApps(processConfig(config, workspaceRoot));\n}\n\n/**\n * The loaded config, with its apps read off the graph.\n *\n * Here rather than at each call site because every command needs the same\n * answer, and the ones that forgot to ask were exactly the bugs: a site that\n * never deployed, a surface that could not be its own process. `gkm dev`,\n * `gkm build`, `gkm docker` and `gkm deploy` now all see the same apps without\n * any of them knowing a derivation happened.\n *\n * Discovery imports the construct modules — declarations only, not endpoints —\n * which is the same import the reconciler and the build already do. A workspace\n * with no `constructs` glob discovers nothing and keeps whatever its config\n * said, so nothing that worked before changes.\n *\n * A construct file that throws is reported and skipped rather than taking the\n * command down: `gkm dev` failing to start because a half-written declaration\n * cannot be imported is a worse failure than starting with the app it does not\n * describe yet.\n */\nasync function withDerivedApps(loaded: LoadedConfig): Promise<LoadedConfig> {\n\tconst globs = allConstructGlobs(loaded.workspace);\n\tif (globs.length === 0) {\n\t\tthrow new WorkspaceDeclaresNoConstructs(loaded.workspace.root);\n\t}\n\n\ttry {\n\t\tconst manifest = await discover({\n\t\t\tpatterns: globs,\n\t\t\tcwd: loaded.workspace.root,\n\t\t});\n\n\t\treturn {\n\t\t\t...loaded,\n\t\t\tmanifest,\n\t\t\tworkspace: {\n\t\t\t\t...loaded.workspace,\n\t\t\t\tapps: derivedApps(manifest, loaded.workspace),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.warn(\n\t\t\t`⚠️ Could not read constructs, so apps come from config alone: ${message}`,\n\t\t);\n\t\treturn loaded;\n\t}\n}\n\n/**\n * A workspace with no `constructs` glob.\n *\n * In v10 every address, container and app comes from what the workspace\n * declares — there is no second path that reads a hand-written compose file or\n * an `apps` block's ports. A config that declares nothing has nothing to run.\n */\nexport class WorkspaceDeclaresNoConstructs extends Error {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`The workspace at ${root} declares no constructs. Every workspace names ` +\n\t\t\t\t\"where its constructs live — `constructs: ['./constructs/**/*.ts', \" +\n\t\t\t\t\"'./apps/*/endpoints/**/*.ts']` in gkm.config.ts — and its apps, \" +\n\t\t\t\t'containers and URLs are derived from them.',\n\t\t);\n\t\tthis.name = 'WorkspaceDeclaresNoConstructs';\n\t}\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * The app a directory is part of: the one whose `path` contains it.\n *\n * An app's path is the one its construct declares — `new RestApi('Api',\n * { path: 'apps/api' })` — read off the workspace the constructs were derived\n * into, never off the filesystem's package names. The deepest match wins, so\n * an app inside another app's folder is its own; the workspace root belongs to\n * no app unless one lives there.\n */\nfunction resolveWorkspaceApp(\n\tloadedConfig: LoadedConfig,\n\tcwd: string,\n): { key: string; app: NormalizedAppConfig } | undefined {\n\tconst here = real(cwd);\n\tlet found: { key: string; app: NormalizedAppConfig; dir: string } | undefined;\n\n\tfor (const [key, app] of Object.entries(loadedConfig.workspace.apps)) {\n\t\tconst dir = real(join(loadedConfig.workspace.root, app.path));\n\t\tconst inside = here === dir || here.startsWith(`${dir}${sep}`);\n\t\tif (inside && (!found || dir.length > found.dir.length)) {\n\t\t\tfound = { key, app, dir };\n\t\t}\n\t}\n\n\treturn found && { key: found.key, app: found.app };\n}\n\n/** A path with its symlinks resolved, where it exists — `/tmp` is `/private/tmp`. */\nfunction real(path: string): string {\n\tconst absolute = resolve(path);\n\treturn existsSync(absolute) ? realpathSync(absolute) : absolute;\n}\n\n/** A directory that no app in the workspace lives in. */\nexport class NotInAnApp extends Error {\n\tconstructor(\n\t\treadonly cwd: string,\n\t\treadonly apps: Readonly<Record<string, string>>,\n\t) {\n\t\tsuper(\n\t\t\t`${cwd} is not inside any app's path. Apps: ` +\n\t\t\t\tObject.entries(apps)\n\t\t\t\t\t.map(([key, path]) => `${key} (${path})`)\n\t\t\t\t\t.join(', ') +\n\t\t\t\t'.',\n\t\t);\n\t\tthis.name = 'NotInAnApp';\n\t}\n}\n\n/** Every app's path, by key — what `NotInAnApp` lists. */\nfunction appPaths(loadedConfig: LoadedConfig): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(loadedConfig.workspace.apps).map(([key, app]) => [\n\t\t\tkey,\n\t\t\tapp.path,\n\t\t]),\n\t);\n}\n\n/**\n * The backend app a directory is part of, with its gkm config — found by the\n * `path` its construct declares.\n *\n * @example\n * ```ts\n * // From apps/api, where a construct declares `path: 'apps/api'`\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * ```\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n * @throws {NotABackendApp} when the app it is in has no gkm config to run.\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst { appName, app, workspace, workspaceRoot } =\n\t\tawait loadWorkspaceAppInfo(cwd);\n\n\tconst gkmConfig = getAppGkmConfig(workspace, appName);\n\tif (!gkmConfig) throw new NotABackendApp(appName);\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\n/** The app a directory is in has no gkm config — a site, or an entry app. */\nexport class NotABackendApp extends Error {\n\tconstructor(readonly appName: string) {\n\t\tsuper(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t\tthis.name = 'NotABackendApp';\n\t}\n}\n\nexport interface WorkspaceAppInfo {\n\t/** The app's key in the workspace. */\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * The app a directory is part of, frontend or backend — found by the `path`\n * its construct declares. Unlike `loadAppConfig`, the app need not have a\n * gkm config, which is what `gkm exec` and `gkm test` from a site need.\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = await withDerivedApps(\n\t\tprocessConfig(config, workspaceRoot),\n\t);\n\n\tconst resolved = resolveWorkspaceApp(loadedConfig, cwd);\n\tif (!resolved) throw new NotInAnApp(cwd, appPaths(loadedConfig));\n\n\treturn {\n\t\tappName: resolved.key,\n\t\tapp: resolved.app,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_discover = require("./discover-DXNviAh-.cjs");
|
|
2
|
-
const require_workspace = require("./workspace-
|
|
2
|
+
const require_workspace = require("./workspace-B64Xk1Mb.cjs");
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
@@ -531,4 +531,4 @@ Object.defineProperty(exports, "parseModuleConfig", {
|
|
|
531
531
|
}
|
|
532
532
|
});
|
|
533
533
|
|
|
534
|
-
//# sourceMappingURL=config-
|
|
534
|
+
//# sourceMappingURL=config-WMOB9bLL.cjs.map
|